Menu

Change language

GitHub repo

Newsletter — Split with Preview

blockmarketing

Signup section pairing the pitch and form with a preview of the last issue — subject line, date, and opening paragraph — so the reader sees what they are subscribing to.

Installation

$npx uipkge-ng@latest add newsletter-split-preview
Manifest:https://uipkge.dev/r/angular/newsletter-split-preview.jsonInstalls to:src/app/components/blocks/newsletter-split-preview/

Variants

Loading interactive previews…

Files installed (2)

  • src/app/components/blocks/newsletter-split-preview/newsletter-split-preview.component.ts4.7 kB
    // Signup section pairing the pitch and form with a preview of the last issue —
    // subject line, date, and opening paragraph. Port of the Vue/React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'
    import { Check, LucideAngularModule, Mail } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'
    import { UiInputComponent } from '@/ui/input/input.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    export const lastIssue = {
      subject: 'Why we version metric definitions instead of dashboards',
      date: 'Feb 18, 2026',
      readTime: '9 min',
      opening:
        'Dashboards are the output. The definition is the thing teams argue about, so that is what belongs under review — with a diff, an owner, and a revert path. This issue walks through what changed when we moved ours into the repo.',
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-newsletter-split-preview, [ui-newsletter-split-preview]',
      standalone: true,
      // The block renders its own root <section>: keep the host out of layout.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiInputComponent,
        UiSeparatorComponent,
      ],
      template: `
        <section data-slot="newsletter-split-preview" [class]="rootClass">
          <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28">
            <div class="grid items-center gap-10 lg:grid-cols-2 lg:gap-16">
              <div>
                <span ui-badge variant="secondary" class="gap-1.5">
                  <lucide-icon [img]="Mail" class="size-3" aria-hidden="true" />
                  Monthly
                </span>
                <h2 class="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
                  Notes from the people who build it
                </h2>
                <p class="text-muted-foreground mt-3 text-lg">
                  One email a month on metric modelling, access control, and warehouse cost. No product announcements.
                </p>
    
                <form class="mt-6 max-w-md" (submit)="subscribe($event)">
                  <div class="flex gap-2">
                    <ui-input
                      [value]="email()"
                      (valueChange)="email.set($event)"
                      type="email"
                      required
                      placeholder="[email protected]"
                      autoComplete="email"
                      aria-label="Email address"
                    />
                    <button ui-button type="submit" class="shrink-0">Subscribe</button>
                  </div>
                  <p class="mt-2 min-h-5 text-xs" aria-live="polite">
                    @if (submitted()) {
                      <span class="text-success inline-flex items-center gap-1.5">
                        <lucide-icon [img]="Check" class="size-3" aria-hidden="true" />
                        Check your inbox to confirm.
                      </span>
                    } @else {
                      <span class="text-muted-foreground">8,400 subscribers · unsubscribe in one click.</span>
                    }
                  </p>
                </form>
              </div>
    
              <!-- Showing the last issue is the point: nobody should have to subscribe
                   to find out what they are subscribing to. -->
              <div ui-card>
                <div ui-card-content class="p-6">
                  <div class="flex flex-wrap items-center gap-3">
                    <span ui-badge variant="outline">Last issue</span>
                    <span class="text-muted-foreground text-xs">{{ issue.date }} · {{ issue.readTime }}</span>
                  </div>
    
                  <h3 class="mt-4 text-lg leading-snug font-semibold tracking-tight text-balance">
                    {{ issue.subject }}
                  </h3>
    
                  <ui-separator class="my-4" />
    
                  <p class="text-muted-foreground text-sm leading-relaxed">{{ issue.opening }}</p>
    
                  <button ui-button variant="link" class="mt-4 h-auto p-0 text-sm">Read the full issue</button>
                </div>
              </div>
            </div>
          </div>
        </section>
      `,
    })
    export class UiNewsletterSplitPreviewComponent {
      protected readonly Check = Check
      protected readonly Mail = Mail
    
      protected readonly issue = lastIssue
    
      @Input('class') className?: string
    
      readonly email = signal('')
      readonly submitted = signal(false)
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    
      subscribe(event?: Event): void {
        event?.preventDefault()
        if (!this.email().trim()) return
        this.submitted.set(true)
      }
    }
    
  • src/app/components/blocks/newsletter-split-preview/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/newsletter-split-preview.json