Exit Intent — Newsletter
blockmarketingExit-intent dialog capturing an email for the monthly notes rather than pushing a discount, with the last issue named so the ask is concrete.
Installation
$pnpm dlx uipkge-ng@latest add exit-intent-newsletter$npx uipkge-ng@latest add exit-intent-newsletter$yarn dlx uipkge-ng@latest add exit-intent-newsletter$bunx uipkge-ng@latest add exit-intent-newsletterManifest:
https://uipkge.dev/r/angular/exit-intent-newsletter.jsonInstalls to:src/app/components/blocks/exit-intent-newsletter/Variants
Loading interactive previews…
Loading interactive previews…
Files installed (2)
src/app/components/blocks/exit-intent-newsletter/exit-intent-newsletter.component.ts4.4 kB
// Exit-intent capture for the monthly notes rather than a discount, naming the last issue // so the ask is concrete. Fires once per session when the pointer leaves toward the browser // chrome. Port of the Vue/React block 1:1. import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, signal } from '@angular/core' import { Check, LucideAngularModule, Mail } from 'lucide-angular' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiDialogComponent, UiDialogContentComponent, UiDialogDescriptionComponent, UiDialogHeaderComponent, UiDialogTitleComponent, } from '@/ui/dialog/dialog.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-exit-intent-newsletter, [ui-exit-intent-newsletter]', standalone: true, // The block renders its own root <div> (and the dialog portals to body): keep the host out of layout. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiDialogComponent, UiDialogContentComponent, UiDialogDescriptionComponent, UiDialogHeaderComponent, UiDialogTitleComponent, UiInputComponent, UiSeparatorComponent, ], template: ` <div data-slot="exit-intent-newsletter"> <ui-dialog [open]="open()" (openChange)="open.set($event)"> <ui-dialog-content class="sm:max-w-md"> <div ui-dialog-header> <span ui-badge variant="secondary" class="w-fit gap-1.5"> <lucide-icon [img]="Mail" class="size-3" aria-hidden="true" /> Monthly notes </span> <h2 ui-dialog-title class="mt-3 text-xl leading-snug text-balance"> Not ready to trial? Take the writing instead </h2> <p ui-dialog-description class="leading-relaxed"> Last issue: “Why we version metric definitions instead of dashboards.” One email a month, no product announcements, unsubscribe in one click. </p> </div> <ui-separator /> <form (submit)="subscribe($event)"> <div class="flex gap-2"> <ui-input [value]="email()" (valueChange)="email.set($event)" type="email" required placeholder="you@company.com" 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, mostly data and finance engineers.</span> } </p> </form> </ui-dialog-content> </ui-dialog> </div> `, }) export class UiExitIntentNewsletterComponent implements OnInit, OnDestroy { protected readonly Check = Check protected readonly Mail = Mail @Input() storageKey = 'uipkge:exit-newsletter-seen' readonly open = signal(false) readonly email = signal('') readonly submitted = signal(false) private readonly onPointerOut = (event: MouseEvent): void => { // Top edge only: leaving sideways is usually the scrollbar or another window. if (event.relatedTarget || event.clientY > 4) return try { if (window.sessionStorage.getItem(this.storageKey) === '1') return window.sessionStorage.setItem(this.storageKey, '1') } catch { // Blocked storage: shows once more this page view, never in a loop. } this.open.set(true) document.removeEventListener('mouseout', this.onPointerOut) } ngOnInit(): void { document.addEventListener('mouseout', this.onPointerOut) } ngOnDestroy(): void { document.removeEventListener('mouseout', this.onPointerOut) } subscribe(event: Event): void { event.preventDefault() if (!this.email().trim()) return this.submitted.set(true) } }src/app/components/blocks/exit-intent-newsletter/index.ts0.1 kB
export { UiExitIntentNewsletterComponent } from './exit-intent-newsletter.component'
Raw manifest:https://uipkge.dev/r/angular/exit-intent-newsletter.json