Menu

Change language

GitHub repo

Conversion CTA

blockmarketing

Interactive developer CTA section with live framework switcher, interactive CLI installation terminal, 1-click clipboard copy, and production guarantees.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/cta-01.json
Manifest:https://uipkge.dev/r/lit/cta-01.jsonInstalls to:src/uip/blocks/cta-01/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-cta-01': UipCta01
  }
}

npm dependencies

Files installed (1)

  • src/uip/blocks/cta-01/cta-01.ts10 kB
    import { LitElement, css, html } from 'lit'
    import {
      ArrowRight,
      Check,
      CheckCircle2,
      Code2,
      Copy,
      Download,
      Globe,
      Layers,
      ShieldCheck,
      Zap,
    } from 'lucide'
    import { cn } from '../../lib/utils'
    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/badge/badge'
    import '../../components/button/button'
    
    type Framework = 'vue' | 'react' | 'nuxt'
    
    const installCommands: Record<Framework, { command: string; subtitle: string }> = {
      vue: {
        command: 'npx shadcn-vue@latest add https://uipkge.dev/r/vue/init.json -y',
        subtitle: 'Zero-config Vue 3.5 + Tailwind v4 + Reka UI primitives',
      },
      react: {
        command: 'npx shadcn@latest add https://uipkge.dev/r/react/init.json -y',
        subtitle: 'Production-ready React 19 + Radix primitives + CVA variants',
      },
      nuxt: {
        command: 'npx shadcn-vue@latest add https://uipkge.dev/r/vue/init.json -y',
        subtitle: 'Nuxt 4 auto-imports, SSR hydration-safe, unbundled ownership',
      },
    }
    
    // Card chrome follows the registry Card verbatim (see the login-01 block):
    // cardVariants default + Card's overflow-hidden + the block's className, with
    // cn() losers (bg-card, text-card-foreground) dropped.
    const terminalCardClass =
      'rounded-xl border transition-colors duration-150 mx-auto max-w-2xl overflow-hidden border-zinc-800 bg-zinc-950 text-left text-zinc-100 shadow-sm'
    
    /**
     * <uip-cta-01> — the registry Cta01 block as a web component.
     *
     * Class strings are React's Cta01 verbatim. React state (useState) becomes
     * @state fields; the copy-label timeout is cleared in
     * `disconnectedCallback`. The registry link button is a <uip-button> with
     * `href` (React's `asChild` anchor; `rel` has no Lit equivalent and is
     * dropped).
     */
    export class UipCta01 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 = {
        activeFramework: { state: true },
        copied: { state: true },
      }
    
      private activeFramework: Framework = 'vue'
      private copied = false
      private readonly pingMs = 14
      private timer?: number
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'cta-01')
      }
    
      disconnectedCallback() {
        super.disconnectedCallback()
        if (this.timer) window.clearTimeout(this.timer)
      }
    
      private copyCommand() {
        try {
          navigator.clipboard?.writeText(installCommands[this.activeFramework].command)?.catch(() => {})
        } catch {
          // Clipboard unavailable (insecure context): still flash the label.
        }
        this.copied = true
        if (this.timer) window.clearTimeout(this.timer)
        this.timer = window.setTimeout(() => {
          this.copied = false
        }, 2000)
      }
    
      render() {
        return html`
          <section
            data-slot="cta-01"
            class="bg-background border-border relative w-full overflow-hidden border-y py-16 lg:py-24"
          >
            <div class="mx-auto max-w-5xl space-y-10 px-4 text-center sm:px-6 lg:px-8">
              <div class="inline-flex items-center gap-2">
                <uip-badge
                  variant="outline"
                  class="border-primary/30 text-primary bg-primary/5 gap-1.5 px-3 py-1 font-mono text-xs tracking-wide uppercase"
                >
                  ${icon(Zap, 'zap', 'size-3.5')} Unbundled Registry Distribution
                </uip-badge>
                <div class="text-muted-foreground flex items-center gap-1.5 font-mono text-xs">
                  <span class="bg-success size-2 rounded-full"></span>
                  <span>CDN Edge: ${this.pingMs}ms P99</span>
                </div>
              </div>
    
              <div class="mx-auto max-w-3xl space-y-4">
                <h2 class="text-foreground text-3xl leading-[1.15] font-bold tracking-tight sm:text-5xl">
                  Own Your UI. No Semver Lock-in. Zero Bloat.
                </h2>
                <p class="text-muted-foreground text-base leading-relaxed sm:text-lg">
                  The components are the product. Source files are copied directly into your workspace. Modify, compose, and
                  refactor without fighting external package boundaries.
                </p>
              </div>
    
              <div data-uipkge="" data-slot="card" class=${terminalCardClass}>
                <div class="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/70 px-4 py-3">
                  <div class="flex items-center gap-2">
                    <div class="flex gap-1.5">
                      <div class="bg-destructive/80 size-3 rounded-full"></div>
                      <div class="bg-warning/80 size-3 rounded-full"></div>
                      <div class="bg-success/80 size-3 rounded-full"></div>
                    </div>
                    <span class="text-muted-foreground ml-2 font-mono text-xs">terminal — bootstrap workspace</span>
                  </div>
    
                  <div class="flex items-center gap-1 rounded-md border border-zinc-800 bg-zinc-950 p-0.5">
                    ${[
                      { id: 'vue', label: 'Vue 3.5' },
                      { id: 'react', label: 'React 19' },
                      { id: 'nuxt', label: 'Nuxt 4' },
                    ].map(
                      (fw) => html`<button
                        type="button"
                        class=${cn(
                          'rounded px-2 py-0.5 font-mono text-xs transition-colors',
                          this.activeFramework === fw.id
                            ? 'bg-zinc-800 font-semibold text-zinc-100 shadow-xs'
                            : 'text-muted-foreground hover:text-zinc-200',
                        )}
                        @click=${() => {
                          this.activeFramework = fw.id as Framework
                        }}
                      >
                        ${fw.label}
                      </button>`,
                    )}
                  </div>
                </div>
    
                <div class="space-y-3 p-4 font-mono text-xs sm:p-5">
                  <div class="flex items-center justify-between gap-3 rounded-lg border border-zinc-800 bg-zinc-900/90 p-3">
                    <div class="text-success flex items-center gap-2.5 overflow-x-auto select-all">
                      <span class="text-muted-foreground shrink-0">$</span>
                      <span class="whitespace-nowrap">${installCommands[this.activeFramework].command}</span>
                    </div>
                    <uip-button
                      size="sm"
                      variant="outline"
                      class="h-7 shrink-0 gap-1.5 border-zinc-700 bg-zinc-800 px-2.5 font-mono text-xs text-zinc-200 hover:bg-zinc-700 [&::part(base)]:h-7 [&::part(base)]:border-zinc-700 [&::part(base)]:bg-zinc-800 [&::part(base)]:px-2.5 [&::part(base)]:text-xs hover:[&::part(base)]:bg-zinc-700"
                      @click=${this.copyCommand}
                    >
                      ${this.copied ? icon(Check, 'check', 'text-success size-3.5') : icon(Copy, 'copy', 'size-3.5')}
                      <span>${this.copied ? 'Copied' : 'Copy'}</span>
                    </uip-button>
                  </div>
    
                  <p class="text-muted-foreground flex items-center gap-1.5 text-xs">
                    <span>${installCommands[this.activeFramework].subtitle}</span>
                  </p>
                </div>
              </div>
    
              <div class="flex flex-wrap items-center justify-center gap-3.5 pt-2">
                <uip-button size="lg" class="h-11 gap-2 px-6 font-semibold shadow-sm" @click=${this.copyCommand}>
                  ${icon(Download, 'download', 'size-4')}
                  ${this.copied ? 'Command Copied to Clipboard' : 'Install Components'}
                </uip-button>
                <uip-button
                  size="lg"
                  variant="outline"
                  class="h-11 gap-2 px-6 font-semibold"
                  href="https://github.com/uday-a/uipkge-registry"
                  target="_blank"
                >
                  ${icon(Code2, 'code-2', 'size-4')} Explore Source Registry ${icon(ArrowRight, 'arrow-right', 'size-4')}
                </uip-button>
              </div>
    
              <div class="border-border grid grid-cols-2 gap-4 border-t pt-8 text-left sm:grid-cols-4">
                <div class="flex items-start gap-2.5">
                  ${icon(CheckCircle2, 'circle-check-big', 'text-success mt-0.5 size-4 shrink-0')}
                  <div>
                    <p class="text-foreground text-xs font-semibold">100% Code Ownership</p>
                    <p class="text-muted-foreground mt-0.5 text-xs">Files live in your repository</p>
                  </div>
                </div>
                <div class="flex items-start gap-2.5">
                  ${icon(ShieldCheck, 'shield-check', 'text-success mt-0.5 size-4 shrink-0')}
                  <div>
                    <p class="text-foreground text-xs font-semibold">Zero Runtime Bloat</p>
                    <p class="text-muted-foreground mt-0.5 text-xs">No opaque wrapper dependencies</p>
                  </div>
                </div>
                <div class="flex items-start gap-2.5">
                  ${icon(Layers, 'layers', 'text-success mt-0.5 size-4 shrink-0')}
                  <div>
                    <p class="text-foreground text-xs font-semibold">Tailwind CSS v4</p>
                    <p class="text-muted-foreground mt-0.5 text-xs">OKLCH color system & tokens</p>
                  </div>
                </div>
                <div class="flex items-start gap-2.5">
                  ${icon(Globe, 'globe', 'text-success mt-0.5 size-4 shrink-0')}
                  <div>
                    <p class="text-foreground text-xs font-semibold">Dual Framework</p>
                    <p class="text-muted-foreground mt-0.5 text-xs">Strict Vue 3.5 & React 19 parity</p>
                  </div>
                </div>
              </div>
            </div>
          </section>
        `
      }
    }
    
    customElements.get('uip-cta-01') || customElements.define('uip-cta-01', UipCta01)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-cta-01': UipCta01
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/cta-01.json