"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 */}
);
}