Menu

Change language

GitHub repo

Hero Developer Terminal

blockmarketing

Developer-first hero section with split value proposition and an interactive live CLI terminal sandbox with preset command switching and copy controls.

Installation

$npx uipkge-ng@latest add hero-developer-terminal
Manifest:https://uipkge.dev/r/angular/hero-developer-terminal.jsonInstalls to:src/app/components/blocks/hero-developer-terminal/

Variants

Loading interactive previews…

Schema

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

DeveloperTerminalPreset
interface DeveloperTerminalPreset {
  id: string
  label: string
  command: string
  output: string[]
}

npm dependencies

Includes

Files installed (2)

  • src/app/components/blocks/hero-developer-terminal/hero-developer-terminal.component.ts12.3 kB
    // Developer-first hero with a split value proposition and an interactive live CLI
    // terminal sandbox with preset command switching and copy controls.
    // Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, DestroyRef, Input, OnInit, computed, inject, signal } from '@angular/core'
    import { ArrowRight, Check, Code2, Copy, LucideAngularModule, RotateCcw } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent } from '@/ui/card/card.component'
    
    export interface DeveloperTerminalPreset {
      id: string
      label: string
      command: string
      output: string[]
    }
    
    const presets: DeveloperTerminalPreset[] = [
      {
        id: 'init',
        label: '1. Init Theme',
        command: 'npx shadcn@latest add https://uipkge.dev/r/react/init.json',
        output: [
          '✔ Resolving registry dependencies...',
          '✔ Downloaded packages/shared/styles/tailwind.css (OKLCH @theme inline)',
          '✔ Configured cn() utility in src/lib/utils.ts',
          '✔ Injected useTheme hook with system preference sync',
          '✔ UIPKGE bootstrap complete in 124ms.',
        ],
      },
      {
        id: 'component',
        label: '2. Add KPI Grid',
        command: 'npx shadcn@latest add https://uipkge.dev/r/react/kpi-grid.json',
        output: [
          '✔ Fetched manifest for kpi-grid (registry:ui)',
          '✔ Added components/ui/kpi-grid/KpiGrid.tsx',
          '✔ Verified CVA variants in kpi-grid.variants.ts',
          '✔ Installed transitive primitives: Card, Badge, Sparkline',
          '✔ Zero npm bundle overhead added to node_modules.',
        ],
      },
      {
        id: 'verify',
        label: '3. Parity Check',
        command: 'npm run check:parity',
        output: [
          '✔ Checking 31 shared CVA variant definitions...',
          '✔ Validating Vue 3.5 SFC and React 19 TSX type contracts...',
          '✔ Token synchronization: styles/tailwind.css (100% match)',
          '✔ PASS: Full cross-framework parity confirmed across all 300+ items.',
        ],
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-hero-developer-terminal, [ui-hero-developer-terminal]',
      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, UiButtonComponent, UiCardComponent],
      template: `
        <section data-slot="hero-developer-terminal" [class]="rootClass">
          <div class="mx-auto grid max-w-7xl grid-cols-1 items-center gap-12 lg:grid-cols-12 lg:gap-8">
            <!-- Left Column: Value Proposition & CTAs (6 Cols) -->
            <div class="space-y-6 text-left lg:col-span-6">
              <!-- Parity Badge -->
              <div
                class="border-border bg-muted/40 inline-flex items-center gap-2 rounded-full border px-3 py-1 font-mono text-xs shadow-xs"
              >
                <span class="bg-success size-2 rounded-full"></span>
                <span class="text-foreground font-medium">Dual-Framework UI Registry</span>
                <span class="text-muted-foreground">&bull;</span>
                <span class="text-muted-foreground">Vue 3.5 & React 19</span>
              </div>
    
              <!-- Main Headline -->
              <h1 class="text-foreground text-4xl leading-[1.1] font-bold tracking-tight sm:text-5xl lg:text-6xl">
                The unbundled UI registry for design engineers.
              </h1>
    
              <!-- Subtitle -->
              <p class="text-muted-foreground max-w-xl text-base leading-relaxed sm:text-lg">
                Own your source code. Copy production-grade components and dense workbenches directly into your project with
                zero npm runtime lock-in.
              </p>
    
              <!-- CTA Buttons -->
              <div class="flex flex-wrap items-center gap-3 pt-2">
                <a ui-button size="lg" href="#components" class="gap-2 font-semibold shadow-xs">
                  <span>Explore 300+ Blocks</span>
                  <lucide-icon [img]="ArrowRight" class="size-4" />
                </a>
    
                <a ui-button variant="outline" size="lg" href="#spec" class="gap-2 font-medium">
                  <lucide-icon [img]="Code2" class="text-muted-foreground size-4" />
                  <span>Registry Spec</span>
                </a>
              </div>
    
              <!-- Telemetry Metrics Strip -->
              <div class="border-border/80 grid grid-cols-3 gap-4 border-t pt-6 text-left">
                <div>
                  <p class="text-foreground font-mono text-2xl font-bold">300+</p>
                  <p class="text-muted-foreground mt-0.5 text-xs">Workbenches & Primitives</p>
                </div>
                <div>
                  <p class="text-foreground font-mono text-2xl font-bold">0 kB</p>
                  <p class="text-muted-foreground mt-0.5 text-xs">Runtime Package Bloat</p>
                </div>
                <div>
                  <p class="text-foreground font-mono text-2xl font-bold">100%</p>
                  <p class="text-muted-foreground mt-0.5 text-xs">Cross-Framework Parity</p>
                </div>
              </div>
            </div>
    
            <!-- Right Column: Interactive Developer Terminal Workbench (6 Cols) -->
            <div class="lg:col-span-6">
              <div ui-card class="border-border bg-card overflow-hidden rounded-xl shadow-sm">
                <!-- Terminal Header -->
                <div class="border-border bg-muted/40 flex items-center justify-between border-b px-4 py-2.5">
                  <!-- Window Controls & File Path -->
                  <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">uipkge-cli &mdash; bash</span>
                  </div>
    
                  <!-- Action Controls -->
                  <div class="flex items-center gap-1.5">
                    <!-- Speed Selector -->
                    <button
                      type="button"
                      class="border-border bg-background text-muted-foreground hover:text-foreground focus-visible:ring-ring rounded border px-2 py-0.5 font-mono text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none"
                      (click)="toggleSpeed()"
                    >
                      {{ executionSpeed() }}x speed
                    </button>
    
                    <!-- Copy Button -->
                    <button
                      type="button"
                      class="border-border bg-background text-muted-foreground hover:text-foreground focus-visible:ring-ring flex items-center gap-1 rounded border px-2 py-0.5 font-mono text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none"
                      (click)="copyCommand()"
                    >
                      @if (copied()) {
                        <lucide-icon [img]="Check" class="text-success size-3" />
                      } @else {
                        <lucide-icon [img]="Copy" class="size-3" />
                      }
                      <span>{{ copied() ? 'Copied' : 'Copy' }}</span>
                    </button>
                  </div>
                </div>
    
                <!-- Preset Navigation Tabs -->
                <div class="border-border/80 bg-muted/20 flex items-center overflow-x-auto border-b px-2">
                  @for (preset of presetList; track preset.id; let idx = $index) {
                    <button type="button" [class]="presetTabClass(idx)" (click)="runPreset(idx)">
                      {{ preset.label }}
                    </button>
                  }
                </div>
    
                <!-- Terminal Content Viewport -->
                <div class="bg-card min-h-[220px] space-y-3 p-4 font-mono text-xs sm:p-5">
                  <!-- Command Input Line -->
                  <div class="text-foreground flex items-start gap-2">
                    <span class="text-primary font-bold select-none">&gt;</span>
                    <span class="text-foreground font-medium break-all">{{ currentPreset().command }}</span>
                  </div>
    
                  <!-- Output Lines -->
                  <div class="space-y-1.5 pt-1">
                    @for (line of displayedLines(); track $index) {
                      <div class="text-muted-foreground flex items-center gap-2 transition-colors duration-150">
                        <span class="text-success shrink-0 font-bold select-none">✓</span>
                        <span class="text-xs">{{ stripCheck(line) }}</span>
                      </div>
                    }
    
                    <!-- Typing / Loading Cursor -->
                    @if (isExecuting()) {
                      <div class="text-muted-foreground flex items-center gap-2 pt-1">
                        <span class="bg-primary size-1.5 rounded-full"></span>
                        <span class="text-muted-foreground text-xs italic">Streaming AST payload...</span>
                      </div>
                    }
                  </div>
                </div>
    
                <!-- Terminal Footer Status Bar -->
                <div
                  class="border-border bg-muted/30 text-muted-foreground flex items-center justify-between border-t px-4 py-2 font-mono text-xs"
                >
                  <div class="flex items-center gap-2">
                    <span class="bg-success size-2 rounded-full"></span>
                    <span>Registry v2.4.0 (OKLCH)</span>
                  </div>
                  <button
                    type="button"
                    class="text-primary focus-visible:ring-ring inline-flex items-center gap-1 rounded-xs hover:underline focus-visible:ring-2 focus-visible:outline-none"
                    (click)="runPreset(activePresetIndex())"
                  >
                    <lucide-icon [img]="RotateCcw" class="size-3" />
                    <span>Re-run</span>
                  </button>
                </div>
              </div>
            </div>
          </div>
        </section>
      `,
    })
    export class UiHeroDeveloperTerminalComponent implements OnInit {
      protected readonly ArrowRight = ArrowRight
      protected readonly Check = Check
      protected readonly Code2 = Code2
      protected readonly Copy = Copy
      protected readonly RotateCcw = RotateCcw
    
      protected readonly presetList = presets
    
      @Input('class') className?: string
    
      readonly activePresetIndex = signal(0)
      readonly displayedLines = signal<string[]>([])
      readonly isExecuting = signal(false)
      readonly copied = signal(false)
      readonly executionSpeed = signal<1 | 2>(1)
    
      private executionTimers: ReturnType<typeof setTimeout>[] = []
      private copyTimer: ReturnType<typeof setTimeout> | undefined
    
      constructor() {
        inject(DestroyRef).onDestroy(() => this.clearTimers())
      }
    
      ngOnInit(): void {
        this.runPreset(0)
      }
    
      readonly currentPreset = computed(() => presets[this.activePresetIndex()] ?? presets[0]!)
    
      get rootClass(): string {
        return cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', this.className)
      }
    
      presetTabClass(idx: number): string {
        return cn(
          'border-b-2 px-3 py-2 font-mono text-xs whitespace-nowrap transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none',
          this.activePresetIndex() === idx
            ? 'border-primary text-foreground bg-background/50 font-semibold'
            : 'text-muted-foreground hover:text-foreground border-transparent',
        )
      }
    
      stripCheck(line: string): string {
        return line.replace(/^✔\s*/, '')
      }
    
      toggleSpeed(): void {
        this.executionSpeed.update((s) => (s === 1 ? 2 : 1))
      }
    
      runPreset(index: number): void {
        this.activePresetIndex.set(index)
        this.displayedLines.set([])
        this.isExecuting.set(true)
        this.clearTimers()
    
        const current = presets[index] ?? presets[0]!
        const delay = this.executionSpeed() === 1 ? 160 : 70
        current.output.forEach((line, i) => {
          this.executionTimers.push(
            setTimeout(
              () => {
                this.displayedLines.update((prev) => [...prev, line])
                if (i === current.output.length - 1) this.isExecuting.set(false)
              },
              (i + 1) * delay,
            ),
          )
        })
      }
    
      async copyCommand(): Promise<void> {
        try {
          await navigator.clipboard.writeText(this.currentPreset().command)
          this.copied.set(true)
          clearTimeout(this.copyTimer)
          this.copyTimer = setTimeout(() => this.copied.set(false), 2000)
        } catch {
          // Clipboard unavailable (e.g. insecure context): leave the Copy label.
        }
      }
    
      private clearTimers(): void {
        this.executionTimers.forEach((t) => clearTimeout(t))
        this.executionTimers = []
        clearTimeout(this.copyTimer)
      }
    }
    
  • src/app/components/blocks/hero-developer-terminal/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/hero-developer-terminal.json