Feature Code Preview Split
blockmarketingSplit-pane developer workbench featuring live code generation across Vue 3.5, React 19, and Tailwind v4 tokens synchronized with an interactive component canvas.
Installation
$pnpm dlx uipkge-ng@latest add feature-code-preview-split$npx uipkge-ng@latest add feature-code-preview-split$yarn dlx uipkge-ng@latest add feature-code-preview-split$bunx uipkge-ng@latest add feature-code-preview-splitManifest:
https://uipkge.dev/r/angular/feature-code-preview-split.jsonInstalls to:src/app/components/blocks/feature-code-preview-split/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (2)
src/app/components/blocks/feature-code-preview-split/feature-code-preview-split.component.ts11.7 kB
// Split-pane developer workbench featuring live code generation across Vue 3.5, // React 19, and Tailwind v4 tokens synchronized with an interactive component canvas. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core' import { Check, Code2, Copy, Eye, LucideAngularModule, 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 FrameworkTab = 'vue' | 'react' | 'tailwind' export type PreviewButtonVariant = 'default' | 'secondary' | 'outline' | 'destructive' export type PreviewButtonSize = 'sm' | 'default' | 'lg' const frameworkTabs: { id: FrameworkTab; label: string }[] = [ { id: 'vue', label: 'Vue 3.5 SFC' }, { id: 'react', label: 'React 19 JSX' }, { id: 'tailwind', label: 'Tailwind Tokens' }, ] const variantOptions: PreviewButtonVariant[] = ['default', 'secondary', 'outline', 'destructive'] const sizeOptions: PreviewButtonSize[] = ['sm', 'default', 'lg'] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-feature-code-preview-split, [ui-feature-code-preview-split]', 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="feature-code-preview-split" [class]="rootClass"> <div class="mx-auto max-w-7xl space-y-12"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <span ui-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <lucide-icon [img]="Code2" class="text-primary size-3.5" /> Interactive DX Playground </span> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Real-time code generation with instant live preview. </h2> <p class="text-muted-foreground text-base"> Modify the interactive controls on the right to see the AST code synchronize live across Vue 3.5, React 19, and Tailwind v4. </p> </div> <!-- Split-Pane DX Workbench --> <div class="grid grid-cols-1 items-stretch gap-6 lg:grid-cols-12"> <!-- Left Pane: Interactive Code Sandbox Editor (7 Cols) --> <div ui-card class="border-border bg-card/95 flex flex-col justify-between space-y-4 rounded-2xl p-6 text-left shadow-sm lg:col-span-7" > <!-- Editor Top Bar --> <div class="border-border flex items-center justify-between border-b pb-3"> <!-- Framework Selector Tabs --> <div class="bg-muted/60 border-border flex items-center gap-1 rounded-lg border p-1"> @for (tab of frameworkTabs; track tab.id) { <button type="button" [class]="frameworkTabClass(tab.id)" (click)="activeFramework.set(tab.id)"> {{ tab.label }} </button> } </div> <!-- Copy Code Button --> <button ui-button size="sm" variant="ghost" class="h-8 gap-1.5 px-2 font-mono text-xs" (click)="copyCode()" > @if (isCopied()) { <lucide-icon [img]="Check" class="text-success size-3.5" /> } @else { <lucide-icon [img]="Copy" class="text-muted-foreground size-3.5" /> } <span>{{ isCopied() ? 'Copied' : 'Copy' }}</span> </button> </div> <!-- Code Viewport --> <pre class="border-border bg-muted/40 text-foreground my-2 flex-1 overflow-x-auto rounded-xl border p-4 font-mono text-xs leading-relaxed" ><code>{{ dynamicCode() }}</code></pre> <!-- Footer Metadata --> <div class="text-muted-foreground border-border flex items-center justify-between border-t pt-3 font-mono text-xs" > <span>Syntax: {{ syntaxLabel() }}</span> <span class="text-success font-semibold">✓ 100% Tree-Shakeable</span> </div> </div> <!-- Right Pane: Live Component Render & Controls (5 Cols) --> <div ui-card class="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-sm lg:col-span-5" > <div class="space-y-4"> <div class="border-border flex items-center justify-between border-b pb-3"> <div class="flex items-center gap-2"> <lucide-icon [img]="Eye" class="text-primary size-4" /> <h3 class="text-foreground font-mono text-xs font-bold">Interactive Component Canvas</h3> </div> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success font-mono text-xs"> Live Render </span> </div> <!-- Live Rendered Component Center Stage --> <div class="border-border bg-background/50 flex h-44 items-center justify-center rounded-xl border border-dashed p-6 transition-colors" > <button ui-button [variant]="activeVariant()" [size]="activeSize()" class="gap-2 font-mono shadow-sm"> @if (showIcon()) { <lucide-icon [img]="Zap" class="size-4" /> } <span>Deploy Application</span> </button> </div> </div> <!-- Component Controls Box --> <div class="border-border space-y-4 border-t pt-2"> <p class="text-muted-foreground font-mono text-xs tracking-wider uppercase">Interactive Prop Controls</p> <!-- Variant Selector --> <div class="space-y-1.5"> <span class="text-muted-foreground font-mono text-xs">Variant:</span> <div class="grid grid-cols-2 gap-1.5"> @for (v of variantOptions; track v) { <button type="button" [class]="variantButtonClass(v)" (click)="activeVariant.set(v)"> {{ v }} </button> } </div> </div> <!-- Size Selector & Icon Toggle --> <div class="grid grid-cols-2 gap-4"> <div class="space-y-1.5"> <span class="text-muted-foreground font-mono text-xs">Size:</span> <div class="flex items-center gap-1"> @for (s of sizeOptions; track s) { <button type="button" [class]="sizeButtonClass(s)" (click)="activeSize.set(s)"> {{ s === 'default' ? 'md' : s }} </button> } </div> </div> <div class="space-y-1.5"> <span class="text-muted-foreground font-mono text-xs">Leading Icon:</span> <button ui-button size="sm" variant="outline" [class]="iconToggleClass" (click)="showIcon.set(!showIcon())" > {{ showIcon() ? 'Enabled' : 'Disabled' }} </button> </div> </div> </div> </div> </div> </div> </section> `, }) export class UiFeatureCodePreviewSplitComponent { protected readonly Check = Check protected readonly Code2 = Code2 protected readonly Copy = Copy protected readonly Eye = Eye protected readonly Zap = Zap protected readonly frameworkTabs = frameworkTabs protected readonly variantOptions = variantOptions protected readonly sizeOptions = sizeOptions @Input('class') className?: string readonly activeFramework = signal<FrameworkTab>('vue') readonly activeVariant = signal<PreviewButtonVariant>('default') readonly activeSize = signal<PreviewButtonSize>('default') readonly showIcon = signal(true) readonly isCopied = signal(false) private copyTimer: ReturnType<typeof setTimeout> | undefined constructor() { inject(DestroyRef).onDestroy(() => clearTimeout(this.copyTimer)) } readonly dynamicCode = computed(() => { const variant = this.activeVariant() const size = this.activeSize() const showIcon = this.showIcon() if (this.activeFramework() === 'vue') { const iconImport = showIcon ? "\nimport { Zap } from 'lucide-vue-next'" : '' const iconElement = showIcon ? '\n <Zap class="size-4" />' : '' return `<script setup lang="ts">\nimport { Button } from '@/components/ui/button'${iconImport}\n</scr${'ipt'}>\n\n<template>\n <Button\n variant="${variant}"\n size="${size}"\n class="font-mono"\n >${iconElement}\n <span>Deploy Application</span>\n </Button>\n</template>` } if (this.activeFramework() === 'react') { const iconImport = showIcon ? "\nimport { Zap } from 'lucide-react'" : '' const iconElement = showIcon ? '\n <Zap className="size-4" />' : '' return `'use client'\n\nimport { Button } from '@/components/ui/button'${iconImport}\n\nexport function DeployTrigger() {\n return (\n <Button\n variant="${variant}"\n size="${size}"\n className="font-mono"\n >${iconElement}\n <span>Deploy Application</span>\n </Button>\n )\n}` } return `@theme inline {\n --color-primary: oklch(0.205 0 0);\n --color-primary-foreground: oklch(0.985 0 0);\n --color-destructive: oklch(0.577 0.245 27.325);\n --radius-lg: 0.625rem;\n --ease-spring: cubic-bezier(0.16, 1, 0.3, 1);\n}` }) readonly syntaxLabel = computed(() => this.activeFramework() === 'vue' ? 'Vue SFC + TS' : this.activeFramework() === 'react' ? 'TypeScript TSX' : 'CSS @theme', ) 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) } get iconToggleClass(): string { return cn('h-7 w-full font-mono text-xs', this.showIcon() ? 'border-primary text-primary' : 'text-muted-foreground') } frameworkTabClass(id: FrameworkTab): string { return cn( 'rounded-md px-2.5 py-1 font-mono text-xs transition-colors', this.activeFramework() === id ? 'bg-primary text-primary-foreground font-semibold shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } variantButtonClass(v: PreviewButtonVariant): string { return cn( 'rounded-lg border px-2.5 py-1 text-center font-mono text-xs capitalize transition-colors', this.activeVariant() === v ? 'border-primary bg-primary text-primary-foreground font-semibold shadow-xs' : 'border-border bg-card text-muted-foreground hover:text-foreground', ) } sizeButtonClass(s: PreviewButtonSize): string { return cn( 'flex-1 rounded-lg border py-1 text-center font-mono text-xs uppercase transition-colors', this.activeSize() === s ? 'border-primary bg-primary text-primary-foreground font-semibold shadow-xs' : 'border-border bg-card text-muted-foreground', ) } copyCode(): void { try { void navigator.clipboard?.writeText(this.dynamicCode()) } catch { // fallback } this.isCopied.set(true) clearTimeout(this.copyTimer) this.copyTimer = setTimeout(() => this.isCopied.set(false), 2000) } }src/app/components/blocks/feature-code-preview-split/index.ts0.2 kB
export { UiFeatureCodePreviewSplitComponent, type FrameworkTab, type PreviewButtonVariant, type PreviewButtonSize, } from './feature-code-preview-split.component'
Raw manifest:https://uipkge.dev/r/angular/feature-code-preview-split.json