"use client"; import { useEffect, useState, useCallback } from "react"; import { CodeBlock } from "./code-block"; const PROMPT = "Create a contact form with name, email, and message"; interface StageJson { key: string; type: string; props: Record; children?: StageJson[]; } interface Stage { json: StageJson; stream: string; } const STAGES: Stage[] = [ { json: { key: "form", type: "Form", props: { title: "Contact Us" }, children: [] }, stream: '{"op":"set","path":"/form","value":{"type":"Form","props":{"title":"Contact Us"}}}' }, { json: { key: "form", type: "Form", props: { title: "Contact Us" }, children: [{ key: "name", type: "Input", props: { label: "Name", name: "name" } }] }, stream: '{"op":"add","path":"/form/children/0","value":{"type":"Input","props":{"label":"Name"}}}' }, { json: { key: "form", type: "Form", props: { title: "Contact Us" }, children: [{ key: "name", type: "Input", props: { label: "Name", name: "name" } }, { key: "email", type: "Input", props: { label: "Email", name: "email" } }] }, stream: '{"op":"add","path":"/form/children/1","value":{"type":"Input","props":{"label":"Email"}}}' }, { json: { key: "form", type: "Form", props: { title: "Contact Us" }, children: [{ key: "name", type: "Input", props: { label: "Name", name: "name" } }, { key: "email", type: "Input", props: { label: "Email", name: "email" } }, { key: "message", type: "Textarea", props: { label: "Message", name: "message" } }] }, stream: '{"op":"add","path":"/form/children/2","value":{"type":"Textarea","props":{"label":"Message"}}}' }, { json: { key: "form", type: "Form", props: { title: "Contact Us" }, children: [{ key: "name", type: "Input", props: { label: "Name", name: "name" } }, { key: "email", type: "Input", props: { label: "Email", name: "email" } }, { key: "message", type: "Textarea", props: { label: "Message", name: "message" } }, { key: "submit", type: "Button", props: { label: "Send Message", action: "submit" } }] }, stream: '{"op":"add","path":"/form/children/3","value":{"type":"Button","props":{"label":"Send Message"}}}' }, ]; const CODE_EXAMPLE = `import { createCatalog } from '@json-render/core'; import { z } from 'zod'; export const catalog = createCatalog({ components: { Form: { props: z.object({ title: z.string(), }), hasChildren: true, }, Input: { props: z.object({ label: z.string(), name: z.string(), }), }, Textarea: { props: z.object({ label: z.string(), name: z.string(), }), }, Button: { props: z.object({ label: z.string(), action: z.string(), }), }, }, });`; 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); } }, 20); 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]); 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 title = props.title as string | undefined; const nameField = children.find(c => c.key === "name"); const emailField = children.find(c => c.key === "email"); const messageField = children.find(c => c.key === "message"); const submitBtn = children.find(c => c.key === "submit"); return (
{title && (

{title}

)}
{nameField && (
)} {emailField && (
)} {messageField && (
)} {submitBtn && ( )}
{actionFired && (
onAction("submit")
)}
); }; 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...
)}
)}
{/* Rendered output */}
render
{renderPreview()}
); }