Waitlist
blockmarketingPre-launch signup page. Split layout with product pitch, logo mark, three benefit bullets on the left and an email capture card on the right that flips to a queue-position confirmation with referral hint after submit.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/waitlist.json$npx shadcn@latest add https://uipkge.dev/r/lit/waitlist.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/waitlist.json$bunx shadcn@latest add https://uipkge.dev/r/lit/waitlist.jsonManifest:
https://uipkge.dev/r/lit/waitlist.jsonInstalls to:src/uip/blocks/waitlist/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-waitlist': UipWaitlist
}
}Files installed (1)
src/uip/blocks/waitlist/waitlist.ts6.8 kB
import { LitElement, css, html } from 'lit' import { Check, Layers, Link2, ShieldCheck, Zap } from 'lucide' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' import { icon } from '../../lib/icon' // Side-effect imports: register the composed elements for standalone use // (consumers who install only this block still get working primitives). import '../../components/button/button' import '../../components/card/card' import '../../components/input/input' import type { UipInput } from '../../components/input/input' const benefits = [ { iconName: 'zap', title: 'Ships in minutes', copy: 'Drop-in blocks and primitives, not boilerplate to rewrite.' }, { iconName: 'shield-check', title: 'Own the code', copy: 'Every file is copied into your repo. Edit it freely, no lock-in.', }, { iconName: 'layers', title: 'Dual framework', copy: 'Vue and React mirrors stay in lockstep across every component.', }, ] as const /** * <uip-waitlist> — the registry Waitlist block as a web component. * * Class strings are React's Waitlist verbatim. React state (useState) becomes * @state fields. React has no submit callback; the form dispatches a * `subscribe` CustomEvent (detail: { email }, bubbles, composed). The benefit * icons resolve by name (React renders component references; lucide nodes * don't carry their kebab-case name, which icon() needs). */ export class UipWaitlist extends LitElement { static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true } // :host display can't be a utility class (the host has no template of its own). static styles = [tailwind, css`:host { display: block; }`] static properties = { email: { state: true }, submitted: { state: true }, position: { state: true }, } private email = '' private submitted = false private position = 214 constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'waitlist') } private benefitIcon(name: string, className: string) { // Mirrors React's <benefit.icon>: Zap / ShieldCheck / Layers in row order. if (name === 'shield-check') return icon(ShieldCheck, 'shield-check', className) if (name === 'layers') return icon(Layers, 'layers', className) return icon(Zap, 'zap', className) } private handleSubmit(e: Event) { e.preventDefault() if (!this.email.includes('@')) return this.submitted = true this.dispatchEvent(new CustomEvent('subscribe', { detail: { email: this.email }, bubbles: true, composed: true })) } render() { return html` <section data-slot="waitlist" class="bg-background grid min-h-screen lg:grid-cols-2"> <div class="bg-muted/40 relative flex items-center px-6 py-16 lg:border-r lg:px-16"> <div class="max-w-lg"> <div class="flex items-center gap-3"> <div class="bg-primary text-primary-foreground grid size-10 place-items-center rounded-lg shadow-sm"> ${icon(Layers, 'layers', 'size-5')} </div> <span class="text-sm font-semibold tracking-tight">Northwind</span> </div> <h1 class="mt-8 text-3xl font-semibold tracking-tight sm:text-4xl">Be first in line when we launch</h1> <p class="text-muted-foreground mt-4 text-base sm:text-lg"> We’re onboarding a small cohort of teams each week. Join the waitlist and we’ll save your spot. </p> <ul class="mt-10 space-y-6"> ${benefits.map( (benefit) => html` <li class="flex gap-4"> <div class="bg-primary/10 text-primary grid size-9 shrink-0 place-items-center rounded-lg"> ${this.benefitIcon(benefit.iconName, 'size-4.5')} </div> <div> <p class="text-sm font-medium">${benefit.title}</p> <p class="text-muted-foreground mt-1 text-sm">${benefit.copy}</p> </div> </li> `, )} </ul> </div> </div> <div class="flex items-center justify-center px-6 py-16"> <uip-card class="w-full max-w-md"> ${!this.submitted ? html` <uip-card-header> <uip-card-title>Join the waitlist</uip-card-title> <uip-card-description> Early access rolls out weekly. No spam — one email when it’s your turn. </uip-card-description> </uip-card-header> <uip-card-content> <form class="space-y-3" @submit=${this.handleSubmit}> <uip-input type="email" placeholder="[email protected]" .value=${this.email} required @input=${(e: Event) => { this.email = (e.target as UipInput).value }} ></uip-input> <uip-button type="submit" class="w-full">Request access</uip-button> </form> <p class="text-muted-foreground mt-4 text-xs">2,300+ teams already in line</p> </uip-card-content> ` : html` <uip-card-content class="pt-6 text-center"> <div class="bg-primary/10 text-primary mx-auto grid size-12 place-items-center rounded-full"> ${icon(Check, 'check', 'size-6')} </div> <h2 class="mt-5 text-2xl font-semibold tracking-tight">You’re #${this.position} in line</h2> <p class="text-muted-foreground mt-2 text-sm"> We’ll email ${this.email} as soon as your spot opens up. </p> <div class="bg-muted/50 mt-6 flex items-start gap-3 rounded-lg border p-4 text-left"> ${icon(Link2, 'link-2', 'text-muted-foreground mt-0.5 size-4 shrink-0')} <p class="text-muted-foreground text-xs"> Want to skip the queue? Share your invite link after launch — every friend who signs up moves you up 5 spots. </p> </div> </uip-card-content> `} </uip-card> </div> </section> ` } } customElements.get('uip-waitlist') || customElements.define('uip-waitlist', UipWaitlist) declare global { interface HTMLElementTagNameMap { 'uip-waitlist': UipWaitlist } }
Raw manifest:https://uipkge.dev/r/lit/waitlist.json