"use client"; import { useEffect, useState, useCallback } from "react"; import { CodeBlock } from "./code-block"; const PROMPT = "Create a welcome card with a get started button"; interface StageJson { key: string; type: string; props: { title?: string; description?: string; label?: string; action?: string; }; children?: StageJson[]; } interface Stage { json: StageJson; stream: string; } const STAGES: Stage[] = [ { json: { key: "root", type: "Card", props: {} }, stream: '{"op":"set","path":"/root","value":{"key":"root","type":"Card"}}' }, { json: { key: "root", type: "Card", props: { title: "Welcome" } }, stream: '{"op":"replace","path":"/root/props/title","value":"Welcome"}' }, { json: { key: "root", type: "Card", props: { title: "Welcome", description: "Get started with json-render" } }, stream: '{"op":"replace","path":"/root/props/description","value":"Get started..."}' }, { json: { key: "root", type: "Card", props: { title: "Welcome", description: "Get started with json-render" }, children: [{ key: "btn", type: "Button", props: {} }] }, stream: '{"op":"add","path":"/root/children","value":{"key":"btn","type":"Button"}}' }, { json: { key: "root", type: "Card", props: { title: "Welcome", description: "Get started with json-render" }, children: [{ key: "btn", type: "Button", props: { label: "Get Started", action: "start" } }] }, stream: '{"op":"replace","path":"/root/children/0/props","value":{"label":"Get Started"}}' }, ]; const CODE_EXAMPLE = `const registry = { Card: ({ element, children }) => (

{element.props.title}

{element.props.description}

{children}
), Button: ({ element, onAction }) => ( ), }; `; type Phase = "typing" | "streaming" | "complete"; type Tab = "stream" | "json" | "code"; export function Demo() { const [phase, setPhase] = useState("typing"); const [typedPrompt, setTypedPrompt] = useState(""); const [stageIndex, setStageIndex] = useState(-1); const [streamLines, setStreamLines] = useState([]); const [activeTab, setActiveTab] = useState("stream"); const [actionFired, setActionFired] = useState(false); const currentStage = stageIndex >= 0 ? STAGES[stageIndex] : null; const reset = useCallback(() => { setPhase("typing"); setTypedPrompt(""); setStageIndex(-1); setStreamLines([]); setActiveTab("stream"); setActionFired(false); }, []); // Typing effect useEffect(() => { if (phase !== "typing") return; let i = 0; const interval = setInterval(() => { if (i < PROMPT.length) { setTypedPrompt(PROMPT.slice(0, i + 1)); i++; } else { clearInterval(interval); setTimeout(() => setPhase("streaming"), 500); } }, 40); return () => clearInterval(interval); }, [phase]); // Streaming effect useEffect(() => { if (phase !== "streaming") return; let i = 0; const interval = setInterval(() => { if (i < STAGES.length) { const currentIndex = i; const stage = STAGES[currentIndex]; if (stage) { setStageIndex(currentIndex); setStreamLines((prev) => [...prev, stage.stream]); } i++; } else { clearInterval(interval); setTimeout(() => { setPhase("complete"); setActiveTab("json"); }, 500); } }, 600); return () => clearInterval(interval); }, [phase]); // Auto-restart useEffect(() => { if (phase !== "complete") return; const timeout = setTimeout(() => { reset(); }, 8000); return () => clearTimeout(timeout); }, [phase, reset]); const handleAction = () => { setActionFired(true); setTimeout(() => setActionFired(false), 2000); }; // Progressive render based on current stage const renderPreview = () => { if (!currentStage) { return
waiting...
; } const { props, children } = currentStage.json; const hasTitle = props.title; const hasDesc = props.description; const buttonLabel = children?.[0]?.props?.label; return (
{hasTitle ? (

{props.title}

) : (
)} {hasDesc ? (

{props.description}

) : hasTitle ? (
) : null} {buttonLabel ? ( ) : hasDesc ? (
) : null}
{actionFired && (
onAction("start")
)}
); }; const jsonCode = currentStage ? JSON.stringify(currentStage.json, null, 2) : "// waiting..."; return (
{/* Prompt input */}
{typedPrompt} {phase === "typing" && ( )}
{/* Tabbed code/stream/json panel */}
{(["stream", "json", "code"] as const).map((tab) => ( ))}
{activeTab === "stream" && (
{streamLines.map((line, i) => (
{line}
))} {phase === "streaming" && streamLines.length < STAGES.length && (
)} {streamLines.length === 0 && phase !== "streaming" && (
waiting...
)}
)} {activeTab === "json" && ( )} {activeTab === "code" && ( )}
{/* Rendered output */}
render
{renderPreview()}
); }