Hero Developer Terminal
blockmarketingDeveloper-first hero section with split value proposition and an interactive live CLI terminal sandbox with preset command switching and copy controls.
Installation
$pnpm dlx uipkge-ng@latest add hero-developer-terminal$npx uipkge-ng@latest add hero-developer-terminal$yarn dlx uipkge-ng@latest add hero-developer-terminal$bunx uipkge-ng@latest add hero-developer-terminalManifest:
https://uipkge.dev/r/angular/hero-developer-terminal.jsonInstalls to:src/app/components/blocks/hero-developer-terminal/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.
DeveloperTerminalPresetinterface DeveloperTerminalPreset {
id: string
label: string
command: string
output: string[]
}npm dependencies
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">•</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 — 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">></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
export { UiHeroDeveloperTerminalComponent, type DeveloperTerminalPreset } from './hero-developer-terminal.component'
Raw manifest:https://uipkge.dev/r/angular/hero-developer-terminal.json