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 uipkge-ng@latest add cta-01
Manifest:https://uipkge.dev/r/angular/cta-01.jsonInstalls to:src/app/components/blocks/cta-01/

Variants

Loading interactive previews…

npm dependencies

Files installed (2)

  • src/app/components/blocks/cta-01/cta-01.component.ts9.7 kB
    // Interactive developer CTA: eyebrow pill with edge latency, headline narrative,
    // framework-switching install terminal with clipboard copy, primary actions, and
    // a guarantees grid. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, DestroyRef, Input, inject, signal } from '@angular/core'
    import {
      ArrowRight,
      Check,
      CheckCircle2,
      Code2,
      Copy,
      Download,
      Globe,
      Layers,
      LucideAngularModule,
      ShieldCheck,
      Zap,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent } from '@/ui/card/card.component'
    
    export type CtaFramework = 'vue' | 'react' | 'nuxt'
    
    export const installCommands: Record<CtaFramework, { 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',
      },
    }
    
    const frameworks: { id: CtaFramework; label: string }[] = [
      { id: 'vue', label: 'Vue 3.5' },
      { id: 'react', label: 'React 19' },
      { id: 'nuxt', label: 'Nuxt 4' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-cta-01, [ui-cta-01]',
      standalone: true,
      // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent],
      template: `
        <section data-slot="cta-01" [class]="rootClass">
          <div class="mx-auto max-w-5xl space-y-10 px-4 text-center sm:px-6 lg:px-8">
            <!-- Eyebrow Pill -->
            <div class="inline-flex items-center gap-2">
              <span
                ui-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"
              >
                <lucide-icon [img]="Zap" class="size-3.5" />
                Unbundled Registry Distribution
              </span>
              <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: {{ pingMs() }}ms P99</span>
              </div>
            </div>
    
            <!-- Main Headline & Narrative -->
            <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>
    
            <!-- Interactive Developer Terminal Box -->
            <div
              ui-card
              class="mx-auto max-w-2xl overflow-hidden border-zinc-800 bg-zinc-950 text-left text-zinc-100 shadow-sm"
            >
              <!-- Terminal Titlebar -->
              <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>
    
                <!-- Framework selector tabs -->
                <div class="flex items-center gap-1 rounded-md border border-zinc-800 bg-zinc-950 p-0.5">
                  @for (fw of frameworks; track fw.id) {
                    <button type="button" [class]="frameworkTabClass(fw.id)" (click)="activeFramework.set(fw.id)">
                      {{ fw.label }}
                    </button>
                  }
                </div>
              </div>
    
              <!-- Terminal Command Runner Body -->
              <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[activeFramework()].command }}</span>
                  </div>
                  <button
                    ui-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"
                    (click)="copyCommand()"
                  >
                    @if (copied()) {
                      <lucide-icon [img]="Check" class="text-success size-3.5" />
                    } @else {
                      <lucide-icon [img]="Copy" class="size-3.5" />
                    }
                    <span>{{ copied() ? 'Copied' : 'Copy' }}</span>
                  </button>
                </div>
    
                <p class="text-muted-foreground flex items-center gap-1.5 text-xs">
                  <span>{{ installCommands[activeFramework()].subtitle }}</span>
                </p>
              </div>
            </div>
    
            <!-- Primary Action Buttons -->
            <div class="flex flex-wrap items-center justify-center gap-3.5 pt-2">
              <button ui-button size="lg" class="h-11 gap-2 px-6 font-semibold shadow-sm" (click)="copyCommand()">
                <lucide-icon [img]="Download" class="size-4" />
                {{ copied() ? 'Command Copied to Clipboard' : 'Install Components' }}
              </button>
              <a
                ui-button
                size="lg"
                variant="outline"
                class="h-11 gap-2 px-6 font-semibold"
                href="https://github.com/uday-a/uipkge-registry"
                target="_blank"
                rel="noreferrer"
              >
                <lucide-icon [img]="Code2" class="size-4" />
                Explore Source Registry
                <lucide-icon [img]="ArrowRight" class="size-4" />
              </a>
            </div>
    
            <!-- Trust Badges & Guarantees Grid -->
            <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">
                <lucide-icon [img]="CheckCircle2" class="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">
                <lucide-icon [img]="ShieldCheck" class="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">
                <lucide-icon [img]="Layers" class="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">
                <lucide-icon [img]="Globe" class="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>
      `,
    })
    export class UiCta01Component {
      protected readonly ArrowRight = ArrowRight
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Code2 = Code2
      protected readonly Copy = Copy
      protected readonly Download = Download
      protected readonly Globe = Globe
      protected readonly Layers = Layers
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Zap = Zap
    
      protected readonly frameworks = frameworks
      protected readonly installCommands = installCommands
    
      @Input('class') className?: string
    
      readonly activeFramework = signal<CtaFramework>('vue')
      readonly copied = signal(false)
      readonly pingMs = signal(14)
    
      private copyTimer: ReturnType<typeof setTimeout> | undefined
    
      constructor() {
        inject(DestroyRef).onDestroy(() => clearTimeout(this.copyTimer))
      }
    
      get rootClass(): string {
        return cn('bg-background border-border relative w-full overflow-hidden border-y py-16 lg:py-24', this.className)
      }
    
      frameworkTabClass(id: CtaFramework): string {
        return cn(
          'rounded px-2 py-0.5 font-mono text-xs transition-colors',
          this.activeFramework() === id
            ? 'bg-zinc-800 font-semibold text-zinc-100 shadow-xs'
            : 'text-muted-foreground hover:text-zinc-200',
        )
      }
    
      async copyCommand(): Promise<void> {
        try {
          await navigator.clipboard.writeText(installCommands[this.activeFramework()].command)
        } catch {
          // Clipboard fallback
        }
        this.copied.set(true)
        clearTimeout(this.copyTimer)
        this.copyTimer = setTimeout(() => this.copied.set(false), 2000)
      }
    }
    
  • src/app/components/blocks/cta-01/index.ts0.1 kB

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