Chris Tate 5 місяців тому
батько
коміт
6a62971bd1
33 змінених файлів з 967 додано та 687 видалено
  1. 94 398
      apps/web/components/demo.tsx
  2. 27 0
      apps/web/components/demo/alert.tsx
  3. 30 0
      apps/web/components/demo/avatar.tsx
  4. 26 0
      apps/web/components/demo/badge.tsx
  5. 27 0
      apps/web/components/demo/button.tsx
  6. 36 0
      apps/web/components/demo/card.tsx
  7. 20 0
      apps/web/components/demo/checkbox.tsx
  8. 9 0
      apps/web/components/demo/divider.tsx
  9. 15 0
      apps/web/components/demo/fallback.tsx
  10. 22 0
      apps/web/components/demo/form.tsx
  11. 27 0
      apps/web/components/demo/grid.tsx
  12. 24 0
      apps/web/components/demo/heading.tsx
  13. 26 0
      apps/web/components/demo/image.tsx
  14. 77 0
      apps/web/components/demo/index.ts
  15. 24 0
      apps/web/components/demo/input.tsx
  16. 17 0
      apps/web/components/demo/link.tsx
  17. 26 0
      apps/web/components/demo/progress.tsx
  18. 28 0
      apps/web/components/demo/radio.tsx
  19. 31 0
      apps/web/components/demo/rating.tsx
  20. 70 0
      apps/web/components/demo/select.tsx
  21. 20 0
      apps/web/components/demo/stack.tsx
  22. 24 0
      apps/web/components/demo/switch.tsx
  23. 22 0
      apps/web/components/demo/text.tsx
  24. 25 0
      apps/web/components/demo/textarea.tsx
  25. 14 0
      apps/web/components/demo/types.ts
  26. 52 0
      apps/web/components/demo/utils.ts
  27. 5 5
      apps/web/package.json
  28. 1 1
      examples/dashboard/components/ui-components.tsx
  29. 1 1
      examples/dashboard/next-env.d.ts
  30. 5 5
      examples/dashboard/package.json
  31. 2 2
      packages/react/package.json
  32. 4 4
      packages/ui/package.json
  33. 136 271
      pnpm-lock.yaml

+ 94 - 398
apps/web/components/demo.tsx

@@ -1,22 +1,13 @@
 "use client";
 
 import React, { useEffect, useState, useCallback, useRef } from "react";
+import { Renderer, useUIStream, JSONUIProvider } from "@json-render/react";
+import type { UITree } from "@json-render/core";
 import { CodeBlock } from "./code-block";
+import { demoRegistry, fallbackComponent, useInteractiveState } from "./demo/index";
 
 const SIMULATION_PROMPT = "Create a contact form with name, email, and message";
 
-interface UIElement {
-  key: string;
-  type: string;
-  props: Record<string, unknown>;
-  children?: string[];
-}
-
-interface UITree {
-  root: string;
-  elements: Record<string, UIElement>;
-}
-
 interface SimulationStage {
   tree: UITree;
   stream: string;
@@ -45,70 +36,27 @@ const SIMULATION_STAGES: SimulationStage[] = [
   },
 ];
 
-const CODE_EXAMPLE = `import { createCatalog } from '@json-render/core';
-import { z } from 'zod';
+const CODE_EXAMPLE = `import { Renderer, useUIStream } from '@json-render/react';
+import { registry } from './registry';
 
-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(),
-      }),
-    },
-  },
-});`;
+function App() {
+  const { tree, isStreaming, send } = useUIStream({
+    api: '/api/generate',
+  });
+
+  return (
+    <Renderer
+      tree={tree}
+      registry={registry}
+      loading={isStreaming}
+    />
+  );
+}`;
 
 type Mode = "simulation" | "interactive";
 type Phase = "typing" | "streaming" | "complete";
 type Tab = "stream" | "json" | "code";
 
-function parsePatch(line: string): { op: string; path: string; value: unknown } | null {
-  try {
-    const trimmed = line.trim();
-    if (!trimmed || trimmed.startsWith("//")) return null;
-    return JSON.parse(trimmed);
-  } catch {
-    return null;
-  }
-}
-
-function applyPatch(tree: UITree, patch: { op: string; path: string; value: unknown }): UITree {
-  const newTree = { ...tree, elements: { ...tree.elements } };
-
-  if (patch.path === "/root") {
-    newTree.root = patch.value as string;
-    return newTree;
-  }
-
-  if (patch.path.startsWith("/elements/")) {
-    const key = patch.path.slice("/elements/".length).split("/")[0];
-    if (key && (patch.op === "set" || patch.op === "add")) {
-      newTree.elements[key] = patch.value as UIElement;
-    }
-  }
-
-  return newTree;
-}
-
 export function Demo() {
   const [mode, setMode] = useState<Mode>("simulation");
   const [phase, setPhase] = useState<Phase>("typing");
@@ -118,25 +66,36 @@ export function Demo() {
   const [streamLines, setStreamLines] = useState<string[]>([]);
   const [activeTab, setActiveTab] = useState<Tab>("json");
   const [actionFired, setActionFired] = useState(false);
-  const [tree, setTree] = useState<UITree | null>(null);
-  const [isLoading, setIsLoading] = useState(false);
-  const [openSelect, setOpenSelect] = useState<string | null>(null);
-  const [selectValues, setSelectValues] = useState<Record<string, string>>({});
-  const abortRef = useRef<AbortController | null>(null);
+  const [simulationTree, setSimulationTree] = useState<UITree | null>(null);
+
+  // Use the library's useUIStream hook for real API calls
+  const {
+    tree: apiTree,
+    isStreaming,
+    send,
+    clear,
+  } = useUIStream({
+    api: "/api/generate",
+    onError: (err: Error) => console.error("Generation error:", err),
+  } as Parameters<typeof useUIStream>[0]);
+
+  // Initialize interactive state for Select components
+  useInteractiveState();
 
   const currentSimulationStage = stageIndex >= 0 ? SIMULATION_STAGES[stageIndex] : null;
 
+  // Determine which tree to display
+  const currentTree = mode === "simulation" ? (currentSimulationStage?.tree || simulationTree) : apiTree;
+
   const stopGeneration = useCallback(() => {
-    abortRef.current?.abort();
     if (mode === "simulation") {
-      // Skip to interactive mode
       setMode("interactive");
       setPhase("complete");
       setTypedPrompt(SIMULATION_PROMPT);
       setUserPrompt("");
     }
-    setIsLoading(false);
-  }, [mode]);
+    clear();
+  }, [mode, clear]);
 
   // Typing effect for simulation
   useEffect(() => {
@@ -167,7 +126,7 @@ export function Demo() {
         if (stage) {
           setStageIndex(i);
           setStreamLines((prev) => [...prev, stage.stream]);
-          setTree(stage.tree);
+          setSimulationTree(stage.tree);
         }
         i++;
       } else {
@@ -183,330 +142,45 @@ export function Demo() {
     return () => clearInterval(interval);
   }, [mode, phase]);
 
-  const handleSubmit = useCallback(async () => {
-    if (!userPrompt.trim() || isLoading) return;
-
-    abortRef.current?.abort();
-    abortRef.current = new AbortController();
-
-    setIsLoading(true);
-    setStreamLines([]);
-    setTree({ root: "", elements: {} });
-
-    try {
-      const response = await fetch("/api/generate", {
-        method: "POST",
-        headers: { "Content-Type": "application/json" },
-        body: JSON.stringify({ prompt: userPrompt }),
-        signal: abortRef.current.signal,
-      });
-
-      if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
-
-      const reader = response.body?.getReader();
-      if (!reader) throw new Error("No response body");
-
-      const decoder = new TextDecoder();
-      let buffer = "";
-      let currentTree: UITree = { root: "", elements: {} };
-
-      while (true) {
-        const { done, value } = await reader.read();
-        if (done) break;
-
-        buffer += decoder.decode(value, { stream: true });
-        const lines = buffer.split("\n");
-        buffer = lines.pop() ?? "";
-
-        for (const line of lines) {
-          const patch = parsePatch(line);
-          if (patch) {
-            currentTree = applyPatch(currentTree, patch);
-            setTree({ ...currentTree });
-            setStreamLines((prev) => [...prev, line.trim()]);
+  // Track stream lines from real API
+  useEffect(() => {
+    if (mode === "interactive" && apiTree) {
+      // Convert tree to stream line for display
+      const streamLine = JSON.stringify({ tree: apiTree });
+      if (!streamLines.includes(streamLine) && Object.keys(apiTree.elements).length > 0) {
+        setStreamLines((prev) => {
+          const lastLine = prev[prev.length - 1];
+          if (lastLine !== streamLine) {
+            return [...prev, streamLine];
           }
-        }
+          return prev;
+        });
       }
-
-      if (buffer.trim()) {
-        const patch = parsePatch(buffer);
-        if (patch) {
-          currentTree = applyPatch(currentTree, patch);
-          setTree({ ...currentTree });
-          setStreamLines((prev) => [...prev, buffer.trim()]);
-        }
-      }
-    } catch (err) {
-      if ((err as Error).name !== "AbortError") {
-        console.error("Generation error:", err);
-      }
-    } finally {
-      setIsLoading(false);
-    }
-  }, [userPrompt, isLoading]);
-
-  const handleAction = () => {
-    setActionFired(true);
-    setTimeout(() => setActionFired(false), 2000);
-  };
-
-  // Render a single element
-  const renderElement = (element: UIElement, elements: Record<string, UIElement>): React.ReactNode => {
-    const { type, props, children: childKeys = [] } = element;
-    const renderChildren = () => childKeys.map((key) => {
-      const child = elements[key];
-      return child ? renderElement(child, elements) : null;
-    });
-
-    const customClass = Array.isArray(props.className) ? (props.className as string[]).join(" ") : "";
-    const baseClass = "animate-in fade-in slide-in-from-bottom-1 duration-200";
-
-    switch (type) {
-      // Layout
-      case "Card":
-        const maxWidthClass = props.maxWidth === "sm" ? "max-w-xs sm:min-w-[280px]" : props.maxWidth === "md" ? "max-w-sm sm:min-w-[320px]" : props.maxWidth === "lg" ? "max-w-md sm:min-w-[360px]" : "w-full";
-        const centeredClass = props.centered ? "mx-auto" : "";
-        return (
-          <div key={element.key} className={`border border-border rounded-lg p-3 bg-background overflow-hidden ${maxWidthClass} ${centeredClass} ${baseClass} ${customClass}`}>
-            {props.title ? <div className="font-semibold text-sm mb-1 text-left">{props.title as string}</div> : null}
-            {props.description ? <div className="text-[10px] text-muted-foreground mb-2 text-left">{props.description as string}</div> : null}
-            <div className="space-y-2">{renderChildren()}</div>
-          </div>
-        );
-      case "Stack":
-        const isHorizontal = props.direction === "horizontal";
-        const stackGap = props.gap === "lg" ? "gap-3" : props.gap === "sm" ? "gap-1" : "gap-2";
-        return (
-          <div key={element.key} className={`flex ${isHorizontal ? "flex-row flex-wrap items-center" : "flex-col"} ${stackGap} ${baseClass} ${customClass}`}>
-            {renderChildren()}
-          </div>
-        );
-      case "Grid":
-        const hasCustomCols = customClass.includes("grid-cols-");
-        const cols = hasCustomCols ? "" : (props.columns === 4 ? "grid-cols-4" : props.columns === 3 ? "grid-cols-3" : props.columns === 2 ? "grid-cols-2" : "grid-cols-1");
-        const gridGap = props.gap === "lg" ? "gap-3" : props.gap === "sm" ? "gap-1" : "gap-2";
-        return (
-          <div key={element.key} className={`grid ${cols} ${gridGap} ${baseClass} ${customClass}`}>
-            {renderChildren()}
-          </div>
-        );
-      case "Divider":
-        return <hr key={element.key} className={`border-border my-2 ${baseClass} ${customClass}`} />;
-
-      // Form Inputs
-      case "Input":
-        return (
-          <div key={element.key} className={`${baseClass} ${customClass}`}>
-            {props.label ? <label className="text-[10px] text-muted-foreground block mb-0.5 text-left">{props.label as string}</label> : null}
-            <input
-              type={(props.type as string) || "text"}
-              placeholder={props.placeholder as string || ""}
-              className="h-7 w-full bg-card border border-border rounded px-2 text-xs focus:outline-none focus:ring-1 focus:ring-foreground/20"
-            />
-          </div>
-        );
-      case "Textarea":
-        const rows = (props.rows as number) || 3;
-        return (
-          <div key={element.key} className={`${baseClass} ${customClass}`}>
-            {props.label ? <label className="text-[10px] text-muted-foreground block mb-0.5 text-left">{props.label as string}</label> : null}
-            <textarea
-              placeholder={props.placeholder as string || ""}
-              rows={rows}
-              className="w-full bg-card border border-border rounded px-2 py-1 text-xs resize-none focus:outline-none focus:ring-1 focus:ring-foreground/20"
-            />
-          </div>
-        );
-      case "Select":
-        const selectOptions = (props.options as string[]) || [];
-        const selectedValue = selectValues[element.key];
-        const isOpen = openSelect === element.key;
-        return (
-          <div key={element.key} className={`relative ${baseClass} ${customClass}`}>
-            {props.label ? <label className="text-[10px] text-muted-foreground block mb-0.5 text-left">{props.label as string}</label> : null}
-            <div
-              onClick={() => setOpenSelect(isOpen ? null : element.key)}
-              className="h-7 w-full bg-card border border-border rounded px-2 text-xs flex items-center justify-between cursor-pointer hover:border-foreground/30 transition-colors"
-            >
-              <span className={selectedValue ? "text-foreground" : "text-muted-foreground/50"}>
-                {selectedValue || props.placeholder as string || "Select..."}
-              </span>
-              <svg className={`w-3 h-3 transition-transform ${isOpen ? "rotate-180" : ""}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
-            </div>
-            {isOpen && selectOptions.length > 0 && (
-              <div className="absolute z-10 top-full left-0 right-0 mt-1 bg-card border border-border rounded shadow-lg overflow-hidden">
-                {selectOptions.map((opt, i) => (
-                  <div
-                    key={i}
-                    onClick={() => {
-                      setSelectValues((prev) => ({ ...prev, [element.key]: opt }));
-                      setOpenSelect(null);
-                    }}
-                    className={`px-2 py-1.5 text-xs text-left cursor-pointer hover:bg-muted transition-colors ${selectedValue === opt ? "bg-muted" : ""}`}
-                  >
-                    {opt}
-                  </div>
-                ))}
-              </div>
-            )}
-          </div>
-        );
-      case "Checkbox":
-        return (
-          <label key={element.key} className={`flex items-center gap-2 text-xs ${baseClass} ${customClass}`}>
-            <div className={`w-3.5 h-3.5 border border-border rounded-sm ${props.checked ? "bg-foreground" : "bg-card"}`} />
-            {props.label as string}
-          </label>
-        );
-      case "Radio":
-        const options = (props.options as string[]) || [];
-        return (
-          <div key={element.key} className={`space-y-1 ${baseClass} ${customClass}`}>
-            {props.label ? <div className="text-[10px] text-muted-foreground mb-1 text-left">{props.label as string}</div> : null}
-            {options.map((opt, i) => (
-              <label key={i} className="flex items-center gap-2 text-xs">
-                <div className={`w-3.5 h-3.5 border border-border rounded-full ${i === 0 ? "bg-foreground" : "bg-card"}`} />
-                {opt}
-              </label>
-            ))}
-          </div>
-        );
-      case "Switch":
-        return (
-          <label key={element.key} className={`flex items-center justify-between gap-2 text-xs ${baseClass} ${customClass}`}>
-            <span>{props.label as string}</span>
-            <div className={`w-8 h-4 rounded-full relative ${props.checked ? "bg-foreground" : "bg-border"}`}>
-              <div className={`absolute w-3 h-3 rounded-full bg-background top-0.5 transition-all ${props.checked ? "right-0.5" : "left-0.5"}`} />
-            </div>
-          </label>
-        );
-
-      // Actions
-      case "Button":
-        const variant = props.variant as string;
-        const btnClass = variant === "danger" ? "bg-red-500 text-white" : variant === "secondary" ? "bg-card border border-border text-foreground" : "bg-foreground text-background";
-        return (
-          <button key={element.key} onClick={handleAction} className={`self-start px-3 py-1.5 rounded text-xs font-medium hover:opacity-90 transition-opacity ${btnClass} ${baseClass} ${customClass}`}>
-            {props.label as string}
-          </button>
-        );
-      case "Link":
-        return (
-          <span key={element.key} className={`text-xs text-blue-500 underline cursor-pointer ${baseClass} ${customClass}`}>
-            {props.label as string}
-          </span>
-        );
-
-      // Typography
-      case "Heading":
-        const level = (props.level as number) || 2;
-        const headingClass = level === 1 ? "text-lg font-bold" : level === 3 ? "text-xs font-semibold" : level === 4 ? "text-[10px] font-semibold" : "text-sm font-semibold";
-        return <div key={element.key} className={`${headingClass} text-left ${baseClass} ${customClass}`}>{props.text as string}</div>;
-      case "Text":
-        const textVariant = props.variant as string;
-        const textClass = textVariant === "caption" ? "text-[10px]" : textVariant === "muted" ? "text-xs text-muted-foreground" : "text-xs";
-        return <p key={element.key} className={`${textClass} text-left ${baseClass} ${customClass}`}>{props.content as string}</p>;
-
-      // Data Display
-      case "Image":
-        const hasCustomSize = customClass.includes("w-") || customClass.includes("h-");
-        const imgStyle = hasCustomSize ? {} : { width: (props.width as number) || 80, height: (props.height as number) || 60 };
-        return (
-          <div key={element.key} className={`bg-card border border-border rounded flex items-center justify-center text-[10px] text-muted-foreground aspect-video ${baseClass} ${customClass}`} style={imgStyle}>
-            {props.alt as string || "img"}
-          </div>
-        );
-      case "Avatar":
-        const name = props.name as string || "?";
-        const initials = name.split(" ").map(n => n[0]).join("").slice(0, 2).toUpperCase();
-        const avatarSize = props.size === "lg" ? "w-10 h-10 text-sm" : props.size === "sm" ? "w-6 h-6 text-[8px]" : "w-8 h-8 text-[10px]";
-        return (
-          <div key={element.key} className={`${avatarSize} rounded-full bg-muted flex items-center justify-center font-medium ${baseClass} ${customClass}`}>
-            {initials}
-          </div>
-        );
-      case "Badge":
-        const badgeVariant = props.variant as string;
-        const badgeClass = badgeVariant === "success" ? "bg-green-100 text-green-800" : badgeVariant === "warning" ? "bg-yellow-100 text-yellow-800" : badgeVariant === "danger" ? "bg-red-100 text-red-800" : "bg-muted text-foreground";
-        return <span key={element.key} className={`px-1.5 py-0.5 rounded text-[10px] font-medium ${badgeClass} ${baseClass} ${customClass}`}>{props.text as string}</span>;
-      case "Alert":
-        const alertType = props.type as string;
-        const alertClass = alertType === "success" ? "bg-green-50 border-green-200" : alertType === "warning" ? "bg-yellow-50 border-yellow-200" : alertType === "error" ? "bg-red-50 border-red-200" : "bg-blue-50 border-blue-200";
-        return (
-          <div key={element.key} className={`p-2 rounded border ${alertClass} ${baseClass} ${customClass}`}>
-            <div className="text-xs font-medium">{props.title as string}</div>
-            {props.message ? <div className="text-[10px] mt-0.5">{props.message as string}</div> : null}
-          </div>
-        );
-      case "Progress":
-        const value = Math.min(100, Math.max(0, (props.value as number) || 0));
-        return (
-          <div key={element.key} className={`${baseClass} ${customClass}`}>
-            {props.label ? <div className="text-[10px] text-muted-foreground mb-1 text-left">{props.label as string}</div> : null}
-            <div className="h-2 bg-muted rounded-full overflow-hidden">
-              <div className="h-full bg-foreground rounded-full transition-all" style={{ width: `${value}%` }} />
-            </div>
-          </div>
-        );
-      case "Rating":
-        const ratingValue = (props.value as number) || 0;
-        const maxRating = (props.max as number) || 5;
-        return (
-          <div key={element.key} className={`${baseClass} ${customClass}`}>
-            {props.label ? <div className="text-[10px] text-muted-foreground mb-1 text-left">{props.label as string}</div> : null}
-            <div className="flex gap-0.5">
-              {Array.from({ length: maxRating }).map((_, i) => (
-                <span key={i} className={`text-sm ${i < ratingValue ? "text-yellow-400" : "text-muted"}`}>*</span>
-              ))}
-            </div>
-          </div>
-        );
-
-      // Fallback for Form type (legacy)
-      case "Form":
-        return (
-          <div key={element.key} className={`border border-border rounded-lg p-3 bg-background ${baseClass} ${customClass}`}>
-            {props.title ? <div className="font-semibold text-sm mb-2 text-left">{props.title as string}</div> : null}
-            <div className="space-y-2">{renderChildren()}</div>
-          </div>
-        );
-
-      default:
-        return <div key={element.key} className={`text-[10px] text-muted-foreground ${baseClass} ${customClass}`}>[{type}]</div>;
-    }
-  };
-
-  // Render preview from tree
-  const renderPreview = () => {
-    const currentTree = mode === "simulation" ? currentSimulationStage?.tree : tree;
-
-    if (!currentTree || !currentTree.root || !currentTree.elements[currentTree.root]) {
-      return <div className="h-full flex items-center justify-center text-muted-foreground/50 text-sm">{isLoading ? "generating..." : "waiting..."}</div>;
     }
+  }, [mode, apiTree, streamLines]);
 
-    const root = currentTree.elements[currentTree.root];
-    if (!root) return null;
+  const handleSubmit = useCallback(async () => {
+    if (!userPrompt.trim() || isStreaming) return;
+    setStreamLines([]);
+    await send(userPrompt);
+  }, [userPrompt, isStreaming, send]);
 
-    return (
-      <div className="animate-in fade-in duration-200 w-full flex flex-col items-center py-4">
-        <div className="my-auto">
-          {renderElement(root, currentTree.elements)}
-          {actionFired && (
-            <div className="mt-3 text-xs font-mono text-muted-foreground text-center animate-in fade-in slide-in-from-bottom-2">
-              onAction()
-            </div>
-          )}
-        </div>
-      </div>
-    );
-  };
+  // Expose action handler for registry components
+  useEffect(() => {
+    (window as unknown as { __demoAction?: () => void }).__demoAction = () => {
+      setActionFired(true);
+      setTimeout(() => setActionFired(false), 2000);
+    };
+    return () => {
+      delete (window as unknown as { __demoAction?: () => void }).__demoAction;
+    };
+  }, []);
 
-  const currentTree = mode === "simulation" ? currentSimulationStage?.tree : tree;
   const jsonCode = currentTree ? JSON.stringify(currentTree, null, 2) : "// waiting...";
 
   const isTypingSimulation = mode === "simulation" && phase === "typing";
   const isStreamingSimulation = mode === "simulation" && phase === "streaming";
-  const showLoadingDots = isStreamingSimulation || isLoading;
+  const showLoadingDots = isStreamingSimulation || isStreaming;
 
   return (
     <div className="w-full max-w-4xl mx-auto">
@@ -534,13 +208,13 @@ export function Demo() {
                 onChange={(e) => setUserPrompt(e.target.value)}
                 placeholder="Describe what you want to build..."
                 className="flex-1 bg-transparent outline-none placeholder:text-muted-foreground/50"
-                disabled={isLoading}
+                disabled={isStreaming}
                 maxLength={140}
                 autoFocus
               />
             </form>
           )}
-          {(mode === "simulation" || isLoading) ? (
+          {(mode === "simulation" || isStreaming) ? (
             <button
               onClick={stopGeneration}
               className="ml-2 p-1 text-muted-foreground hover:text-foreground transition-colors"
@@ -632,11 +306,33 @@ export function Demo() {
           </div>
         </div>
 
-        {/* Rendered output */}
+        {/* Rendered output using json-render */}
         <div>
           <div className="text-xs text-muted-foreground mb-2 font-mono">render</div>
           <div className="border border-border rounded p-3 bg-card h-96 overflow-auto flex flex-col">
-            {renderPreview()}
+            {currentTree && currentTree.root ? (
+              <div className="animate-in fade-in duration-200 w-full flex flex-col items-center py-4">
+                <div className="my-auto">
+                  <JSONUIProvider registry={demoRegistry as Parameters<typeof JSONUIProvider>[0]['registry']}>
+                    <Renderer
+                      tree={currentTree}
+                      registry={demoRegistry as Parameters<typeof Renderer>[0]['registry']}
+                      loading={isStreaming || isStreamingSimulation}
+                      fallback={fallbackComponent as Parameters<typeof Renderer>[0]['fallback']}
+                    />
+                  </JSONUIProvider>
+                  {actionFired && (
+                    <div className="mt-3 text-xs font-mono text-muted-foreground text-center animate-in fade-in slide-in-from-bottom-2">
+                      onAction()
+                    </div>
+                  )}
+                </div>
+              </div>
+            ) : (
+              <div className="h-full flex items-center justify-center text-muted-foreground/50 text-sm">
+                {isStreaming ? "generating..." : "waiting..."}
+              </div>
+            )}
           </div>
         </div>
       </div>

+ 27 - 0
apps/web/components/demo/alert.tsx

@@ -0,0 +1,27 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Alert({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const alertType = props.type as string;
+  const alertClass =
+    alertType === "success"
+      ? "bg-green-50 border-green-200"
+      : alertType === "warning"
+        ? "bg-yellow-50 border-yellow-200"
+        : alertType === "error"
+          ? "bg-red-50 border-red-200"
+          : "bg-blue-50 border-blue-200";
+
+  return (
+    <div className={`p-2 rounded border ${alertClass} ${baseClass} ${customClass}`}>
+      <div className="text-xs font-medium">{props.title as string}</div>
+      {props.message ? (
+        <div className="text-[10px] mt-0.5">{props.message as string}</div>
+      ) : null}
+    </div>
+  );
+}

+ 30 - 0
apps/web/components/demo/avatar.tsx

@@ -0,0 +1,30 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Avatar({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const name = (props.name as string) || "?";
+  const initials = name
+    .split(" ")
+    .map((n) => n[0])
+    .join("")
+    .slice(0, 2)
+    .toUpperCase();
+  const avatarSize =
+    props.size === "lg"
+      ? "w-10 h-10 text-sm"
+      : props.size === "sm"
+        ? "w-6 h-6 text-[8px]"
+        : "w-8 h-8 text-[10px]";
+
+  return (
+    <div
+      className={`${avatarSize} rounded-full bg-muted flex items-center justify-center font-medium ${baseClass} ${customClass}`}
+    >
+      {initials}
+    </div>
+  );
+}

+ 26 - 0
apps/web/components/demo/badge.tsx

@@ -0,0 +1,26 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Badge({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const badgeVariant = props.variant as string;
+  const badgeClass =
+    badgeVariant === "success"
+      ? "bg-green-100 text-green-800"
+      : badgeVariant === "warning"
+        ? "bg-yellow-100 text-yellow-800"
+        : badgeVariant === "danger"
+          ? "bg-red-100 text-red-800"
+          : "bg-muted text-foreground";
+
+  return (
+    <span
+      className={`px-1.5 py-0.5 rounded text-[10px] font-medium ${badgeClass} ${baseClass} ${customClass}`}
+    >
+      {props.text as string}
+    </span>
+  );
+}

+ 27 - 0
apps/web/components/demo/button.tsx

@@ -0,0 +1,27 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Button({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const variant = props.variant as string;
+  const btnClass =
+    variant === "danger"
+      ? "bg-red-500 text-white"
+      : variant === "secondary"
+        ? "bg-card border border-border text-foreground"
+        : "bg-foreground text-background";
+
+  return (
+    <button
+      onClick={() =>
+        (window as unknown as { __demoAction?: () => void }).__demoAction?.()
+      }
+      className={`self-start px-3 py-1.5 rounded text-xs font-medium hover:opacity-90 transition-opacity ${btnClass} ${baseClass} ${customClass}`}
+    >
+      {props.label as string}
+    </button>
+  );
+}

+ 36 - 0
apps/web/components/demo/card.tsx

@@ -0,0 +1,36 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Card({ element, children }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const maxWidthClass =
+    props.maxWidth === "sm"
+      ? "max-w-xs sm:min-w-[280px]"
+      : props.maxWidth === "md"
+        ? "max-w-sm sm:min-w-[320px]"
+        : props.maxWidth === "lg"
+          ? "max-w-md sm:min-w-[360px]"
+          : "w-full";
+  const centeredClass = props.centered ? "mx-auto" : "";
+
+  return (
+    <div
+      className={`border border-border rounded-lg p-3 bg-background overflow-hidden ${maxWidthClass} ${centeredClass} ${baseClass} ${customClass}`}
+    >
+      {props.title ? (
+        <div className="font-semibold text-sm mb-1 text-left">
+          {props.title as string}
+        </div>
+      ) : null}
+      {props.description ? (
+        <div className="text-[10px] text-muted-foreground mb-2 text-left">
+          {props.description as string}
+        </div>
+      ) : null}
+      <div className="space-y-2">{children}</div>
+    </div>
+  );
+}

+ 20 - 0
apps/web/components/demo/checkbox.tsx

@@ -0,0 +1,20 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Checkbox({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+
+  return (
+    <label
+      className={`flex items-center gap-2 text-xs ${baseClass} ${customClass}`}
+    >
+      <div
+        className={`w-3.5 h-3.5 border border-border rounded-sm ${props.checked ? "bg-foreground" : "bg-card"}`}
+      />
+      {props.label as string}
+    </label>
+  );
+}

+ 9 - 0
apps/web/components/demo/divider.tsx

@@ -0,0 +1,9 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Divider({ element }: ComponentRenderProps) {
+  const customClass = getCustomClass(element.props);
+  return <hr className={`border-border my-2 ${baseClass} ${customClass}`} />;
+}

+ 15 - 0
apps/web/components/demo/fallback.tsx

@@ -0,0 +1,15 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Fallback({ element }: ComponentRenderProps) {
+  const customClass = getCustomClass(element.props);
+  return (
+    <div
+      className={`text-[10px] text-muted-foreground ${baseClass} ${customClass}`}
+    >
+      [{element.type}]
+    </div>
+  );
+}

+ 22 - 0
apps/web/components/demo/form.tsx

@@ -0,0 +1,22 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Form({ element, children }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+
+  return (
+    <div
+      className={`border border-border rounded-lg p-3 bg-background ${baseClass} ${customClass}`}
+    >
+      {props.title ? (
+        <div className="font-semibold text-sm mb-2 text-left">
+          {props.title as string}
+        </div>
+      ) : null}
+      <div className="space-y-2">{children}</div>
+    </div>
+  );
+}

+ 27 - 0
apps/web/components/demo/grid.tsx

@@ -0,0 +1,27 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Grid({ element, children }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const hasCustomCols = customClass.includes("grid-cols-");
+  const cols = hasCustomCols
+    ? ""
+    : props.columns === 4
+      ? "grid-cols-4"
+      : props.columns === 3
+        ? "grid-cols-3"
+        : props.columns === 2
+          ? "grid-cols-2"
+          : "grid-cols-1";
+  const gridGap =
+    props.gap === "lg" ? "gap-3" : props.gap === "sm" ? "gap-1" : "gap-2";
+
+  return (
+    <div className={`grid ${cols} ${gridGap} ${baseClass} ${customClass}`}>
+      {children}
+    </div>
+  );
+}

+ 24 - 0
apps/web/components/demo/heading.tsx

@@ -0,0 +1,24 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Heading({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const level = (props.level as number) || 2;
+  const headingClass =
+    level === 1
+      ? "text-lg font-bold"
+      : level === 3
+        ? "text-xs font-semibold"
+        : level === 4
+          ? "text-[10px] font-semibold"
+          : "text-sm font-semibold";
+
+  return (
+    <div className={`${headingClass} text-left ${baseClass} ${customClass}`}>
+      {props.text as string}
+    </div>
+  );
+}

+ 26 - 0
apps/web/components/demo/image.tsx

@@ -0,0 +1,26 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Image({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const hasCustomSize =
+    customClass.includes("w-") || customClass.includes("h-");
+  const imgStyle = hasCustomSize
+    ? {}
+    : {
+        width: (props.width as number) || 80,
+        height: (props.height as number) || 60,
+      };
+
+  return (
+    <div
+      className={`bg-card border border-border rounded flex items-center justify-center text-[10px] text-muted-foreground aspect-video ${baseClass} ${customClass}`}
+      style={imgStyle}
+    >
+      {(props.alt as string) || "img"}
+    </div>
+  );
+}

+ 77 - 0
apps/web/components/demo/index.ts

@@ -0,0 +1,77 @@
+"use client";
+
+export type { ComponentRenderProps, ComponentRegistry } from "./types";
+export { useInteractiveState } from "./utils";
+
+export { Alert } from "./alert";
+export { Avatar } from "./avatar";
+export { Badge } from "./badge";
+export { Button } from "./button";
+export { Card } from "./card";
+export { Checkbox } from "./checkbox";
+export { Divider } from "./divider";
+export { Fallback } from "./fallback";
+export { Form } from "./form";
+export { Grid } from "./grid";
+export { Heading } from "./heading";
+export { Image } from "./image";
+export { Input } from "./input";
+export { Link } from "./link";
+export { Progress } from "./progress";
+export { Radio } from "./radio";
+export { Rating } from "./rating";
+export { Select } from "./select";
+export { Stack } from "./stack";
+export { Switch } from "./switch";
+export { Text } from "./text";
+export { Textarea } from "./textarea";
+
+import type { ComponentRegistry } from "./types";
+import { Alert } from "./alert";
+import { Avatar } from "./avatar";
+import { Badge } from "./badge";
+import { Button } from "./button";
+import { Card } from "./card";
+import { Checkbox } from "./checkbox";
+import { Divider } from "./divider";
+import { Fallback } from "./fallback";
+import { Form } from "./form";
+import { Grid } from "./grid";
+import { Heading } from "./heading";
+import { Image } from "./image";
+import { Input } from "./input";
+import { Link } from "./link";
+import { Progress } from "./progress";
+import { Radio } from "./radio";
+import { Rating } from "./rating";
+import { Select } from "./select";
+import { Stack } from "./stack";
+import { Switch } from "./switch";
+import { Text } from "./text";
+import { Textarea } from "./textarea";
+
+export const demoRegistry: ComponentRegistry = {
+  Alert,
+  Avatar,
+  Badge,
+  Button,
+  Card,
+  Checkbox,
+  Divider,
+  Form,
+  Grid,
+  Heading,
+  Image,
+  Input,
+  Link,
+  Progress,
+  Radio,
+  Rating,
+  Select,
+  Stack,
+  Switch,
+  Text,
+  Textarea,
+};
+
+export const fallbackComponent = Fallback;

+ 24 - 0
apps/web/components/demo/input.tsx

@@ -0,0 +1,24 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Input({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+
+  return (
+    <div className={`${baseClass} ${customClass}`}>
+      {props.label ? (
+        <label className="text-[10px] text-muted-foreground block mb-0.5 text-left">
+          {props.label as string}
+        </label>
+      ) : null}
+      <input
+        type={(props.type as string) || "text"}
+        placeholder={(props.placeholder as string) || ""}
+        className="h-7 w-full bg-card border border-border rounded px-2 text-xs focus:outline-none focus:ring-1 focus:ring-foreground/20"
+      />
+    </div>
+  );
+}

+ 17 - 0
apps/web/components/demo/link.tsx

@@ -0,0 +1,17 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Link({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+
+  return (
+    <span
+      className={`text-xs text-blue-500 underline cursor-pointer ${baseClass} ${customClass}`}
+    >
+      {props.label as string}
+    </span>
+  );
+}

+ 26 - 0
apps/web/components/demo/progress.tsx

@@ -0,0 +1,26 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Progress({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const value = Math.min(100, Math.max(0, (props.value as number) || 0));
+
+  return (
+    <div className={`${baseClass} ${customClass}`}>
+      {props.label ? (
+        <div className="text-[10px] text-muted-foreground mb-1 text-left">
+          {props.label as string}
+        </div>
+      ) : null}
+      <div className="h-2 bg-muted rounded-full overflow-hidden">
+        <div
+          className="h-full bg-foreground rounded-full transition-all"
+          style={{ width: `${value}%` }}
+        />
+      </div>
+    </div>
+  );
+}

+ 28 - 0
apps/web/components/demo/radio.tsx

@@ -0,0 +1,28 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Radio({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const options = (props.options as string[]) || [];
+
+  return (
+    <div className={`space-y-1 ${baseClass} ${customClass}`}>
+      {props.label ? (
+        <div className="text-[10px] text-muted-foreground mb-1 text-left">
+          {props.label as string}
+        </div>
+      ) : null}
+      {options.map((opt, i) => (
+        <label key={i} className="flex items-center gap-2 text-xs">
+          <div
+            className={`w-3.5 h-3.5 border border-border rounded-full ${i === 0 ? "bg-foreground" : "bg-card"}`}
+          />
+          {opt}
+        </label>
+      ))}
+    </div>
+  );
+}

+ 31 - 0
apps/web/components/demo/rating.tsx

@@ -0,0 +1,31 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Rating({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const ratingValue = (props.value as number) || 0;
+  const maxRating = (props.max as number) || 5;
+
+  return (
+    <div className={`${baseClass} ${customClass}`}>
+      {props.label ? (
+        <div className="text-[10px] text-muted-foreground mb-1 text-left">
+          {props.label as string}
+        </div>
+      ) : null}
+      <div className="flex gap-0.5">
+        {Array.from({ length: maxRating }).map((_, i) => (
+          <span
+            key={i}
+            className={`text-sm ${i < ratingValue ? "text-yellow-400" : "text-muted"}`}
+          >
+            *
+          </span>
+        ))}
+      </div>
+    </div>
+  );
+}

+ 70 - 0
apps/web/components/demo/select.tsx

@@ -0,0 +1,70 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import {
+  baseClass,
+  getCustomClass,
+  getOpenSelect,
+  setOpenSelectValue,
+  getSelectValue,
+  setSelectValueForKey,
+} from "./utils";
+
+export function Select({ element }: ComponentRenderProps) {
+  const { props, key } = element;
+  const customClass = getCustomClass(props);
+  const options = (props.options as string[]) || [];
+  const selectedValue = getSelectValue(key);
+  const isOpen = getOpenSelect() === key;
+
+  return (
+    <div className={`relative ${baseClass} ${customClass}`}>
+      {props.label ? (
+        <label className="text-[10px] text-muted-foreground block mb-0.5 text-left">
+          {props.label as string}
+        </label>
+      ) : null}
+      <div
+        onClick={() => setOpenSelectValue(isOpen ? null : key)}
+        className="h-7 w-full bg-card border border-border rounded px-2 text-xs flex items-center justify-between cursor-pointer hover:border-foreground/30 transition-colors"
+      >
+        <span
+          className={
+            selectedValue ? "text-foreground" : "text-muted-foreground/50"
+          }
+        >
+          {selectedValue || (props.placeholder as string) || "Select..."}
+        </span>
+        <svg
+          className={`w-3 h-3 transition-transform ${isOpen ? "rotate-180" : ""}`}
+          fill="none"
+          stroke="currentColor"
+          viewBox="0 0 24 24"
+        >
+          <path
+            strokeLinecap="round"
+            strokeLinejoin="round"
+            strokeWidth={2}
+            d="M19 9l-7 7-7-7"
+          />
+        </svg>
+      </div>
+      {isOpen && options.length > 0 && (
+        <div className="absolute z-10 top-full left-0 right-0 mt-1 bg-card border border-border rounded shadow-lg overflow-hidden">
+          {options.map((opt, i) => (
+            <div
+              key={i}
+              onClick={() => {
+                setSelectValueForKey(key, opt);
+                setOpenSelectValue(null);
+              }}
+              className={`px-2 py-1.5 text-xs text-left cursor-pointer hover:bg-muted transition-colors ${selectedValue === opt ? "bg-muted" : ""}`}
+            >
+              {opt}
+            </div>
+          ))}
+        </div>
+      )}
+    </div>
+  );
+}

+ 20 - 0
apps/web/components/demo/stack.tsx

@@ -0,0 +1,20 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Stack({ element, children }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const isHorizontal = props.direction === "horizontal";
+  const stackGap =
+    props.gap === "lg" ? "gap-3" : props.gap === "sm" ? "gap-1" : "gap-2";
+
+  return (
+    <div
+      className={`flex ${isHorizontal ? "flex-row flex-wrap items-center" : "flex-col"} ${stackGap} ${baseClass} ${customClass}`}
+    >
+      {children}
+    </div>
+  );
+}

+ 24 - 0
apps/web/components/demo/switch.tsx

@@ -0,0 +1,24 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Switch({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+
+  return (
+    <label
+      className={`flex items-center justify-between gap-2 text-xs ${baseClass} ${customClass}`}
+    >
+      <span>{props.label as string}</span>
+      <div
+        className={`w-8 h-4 rounded-full relative ${props.checked ? "bg-foreground" : "bg-border"}`}
+      >
+        <div
+          className={`absolute w-3 h-3 rounded-full bg-background top-0.5 transition-all ${props.checked ? "right-0.5" : "left-0.5"}`}
+        />
+      </div>
+    </label>
+  );
+}

+ 22 - 0
apps/web/components/demo/text.tsx

@@ -0,0 +1,22 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Text({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const textVariant = props.variant as string;
+  const textClass =
+    textVariant === "caption"
+      ? "text-[10px]"
+      : textVariant === "muted"
+        ? "text-xs text-muted-foreground"
+        : "text-xs";
+
+  return (
+    <p className={`${textClass} text-left ${baseClass} ${customClass}`}>
+      {props.content as string}
+    </p>
+  );
+}

+ 25 - 0
apps/web/components/demo/textarea.tsx

@@ -0,0 +1,25 @@
+"use client";
+
+import type { ComponentRenderProps } from "./types";
+import { baseClass, getCustomClass } from "./utils";
+
+export function Textarea({ element }: ComponentRenderProps) {
+  const { props } = element;
+  const customClass = getCustomClass(props);
+  const rows = (props.rows as number) || 3;
+
+  return (
+    <div className={`${baseClass} ${customClass}`}>
+      {props.label ? (
+        <label className="text-[10px] text-muted-foreground block mb-0.5 text-left">
+          {props.label as string}
+        </label>
+      ) : null}
+      <textarea
+        placeholder={(props.placeholder as string) || ""}
+        rows={rows}
+        className="w-full bg-card border border-border rounded px-2 py-1 text-xs resize-none focus:outline-none focus:ring-1 focus:ring-foreground/20"
+      />
+    </div>
+  );
+}

+ 14 - 0
apps/web/components/demo/types.ts

@@ -0,0 +1,14 @@
+import type { ReactNode } from "react";
+import type { UIElement, Action } from "@json-render/core";
+
+export interface ComponentRenderProps {
+  element: UIElement;
+  children?: ReactNode;
+  onAction?: (action: Action) => void;
+  loading?: boolean;
+}
+
+export type ComponentRegistry = Record<
+  string,
+  React.ComponentType<ComponentRenderProps>
+>;

+ 52 - 0
apps/web/components/demo/utils.ts

@@ -0,0 +1,52 @@
+"use client";
+
+import { useState } from "react";
+
+// Shared animation class
+export const baseClass =
+  "animate-in fade-in slide-in-from-bottom-1 duration-200";
+
+// Helper to get custom classes
+export function getCustomClass(props: Record<string, unknown>): string {
+  return Array.isArray(props.className)
+    ? (props.className as string[]).join(" ")
+    : "";
+}
+
+// State for interactive components
+let openSelect: string | null = null;
+let setOpenSelect: (v: string | null) => void = () => {};
+let selectValues: Record<string, string> = {};
+let setSelectValues: (
+  fn: (prev: Record<string, string>) => Record<string, string>
+) => void = () => {};
+
+export function useInteractiveState() {
+  const [_openSelect, _setOpenSelect] = useState<string | null>(null);
+  const [_selectValues, _setSelectValues] = useState<Record<string, string>>(
+    {}
+  );
+
+  openSelect = _openSelect;
+  setOpenSelect = _setOpenSelect;
+  selectValues = _selectValues;
+  setSelectValues = _setSelectValues;
+
+  return { openSelect, selectValues };
+}
+
+export function getOpenSelect() {
+  return openSelect;
+}
+
+export function setOpenSelectValue(v: string | null) {
+  setOpenSelect(v);
+}
+
+export function getSelectValue(key: string) {
+  return selectValues[key];
+}
+
+export function setSelectValueForKey(key: string, value: string) {
+  setSelectValues((prev) => ({ ...prev, [key]: value }));
+}

+ 5 - 5
apps/web/package.json

@@ -20,9 +20,9 @@
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "lucide-react": "^0.562.0",
-    "next": "16.1.0",
-    "react": "^19.2.0",
-    "react-dom": "^19.2.0",
+    "next": "16.1.1",
+    "react": "19.2.3",
+    "react-dom": "19.2.3",
     "shiki": "^3.21.0",
     "tailwind-merge": "^3.4.0",
     "zod": "^3.24.0"
@@ -32,8 +32,8 @@
     "@repo/typescript-config": "workspace:*",
     "@tailwindcss/postcss": "^4.1.18",
     "@types/node": "^22.15.3",
-    "@types/react": "19.2.2",
-    "@types/react-dom": "19.2.2",
+    "@types/react": "19.2.3",
+    "@types/react-dom": "19.2.3",
     "eslint": "^9.39.1",
     "postcss": "^8.5.6",
     "tailwindcss": "^4.1.18",

+ 1 - 1
examples/dashboard/components/ui-components.tsx

@@ -92,7 +92,7 @@ export function Heading({ element }: ComponentRenderProps) {
     level?: string | null;
   };
 
-  const Tag = (level || 'h2') as keyof JSX.IntrinsicElements;
+  const Tag = (level || 'h2') as keyof React.JSX.IntrinsicElements;
   const sizes: Record<string, string> = {
     h1: '28px',
     h2: '24px',

+ 1 - 1
examples/dashboard/next-env.d.ts

@@ -1,6 +1,6 @@
 /// <reference types="next" />
 /// <reference types="next/image-types/global" />
-/// <reference path="./.next/types/routes.d.ts" />
+import "./.next/types/routes.d.ts";
 
 // NOTE: This file should not be edited
 // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

+ 5 - 5
examples/dashboard/package.json

@@ -13,15 +13,15 @@
     "ai": "^6.0.33",
     "@json-render/core": "workspace:*",
     "@json-render/react": "workspace:*",
-    "next": "^15.3.0",
-    "react": "^19.0.0",
-    "react-dom": "^19.0.0",
+    "next": "16.1.1",
+    "react": "19.2.3",
+    "react-dom": "19.2.3",
     "zod": "^3.24.0"
   },
   "devDependencies": {
     "@types/node": "^22.10.0",
-    "@types/react": "^19.0.0",
-    "@types/react-dom": "^19.0.0",
+    "@types/react": "19.2.3",
+    "@types/react-dom": "19.2.3",
     "typescript": "^5.7.2"
   }
 }

+ 2 - 2
packages/react/package.json

@@ -25,11 +25,11 @@
   },
   "devDependencies": {
     "@repo/typescript-config": "workspace:*",
-    "@types/react": "^18.2.0",
+    "@types/react": "19.2.3",
     "tsup": "^8.0.2",
     "typescript": "^5.4.5"
   },
   "peerDependencies": {
-    "react": "^18.0.0 || ^19.0.0"
+    "react": "^19.0.0"
   }
 }

+ 4 - 4
packages/ui/package.json

@@ -14,13 +14,13 @@
     "@repo/eslint-config": "workspace:*",
     "@repo/typescript-config": "workspace:*",
     "@types/node": "^22.15.3",
-    "@types/react": "19.2.2",
-    "@types/react-dom": "19.2.2",
+    "@types/react": "19.2.3",
+    "@types/react-dom": "19.2.3",
     "eslint": "^9.39.1",
     "typescript": "5.9.2"
   },
   "dependencies": {
-    "react": "^19.2.0",
-    "react-dom": "^19.2.0"
+    "react": "19.2.3",
+    "react-dom": "19.2.3"
   }
 }

+ 136 - 271
pnpm-lock.yaml

@@ -31,10 +31,10 @@ importers:
         version: link:../../packages/react
       '@radix-ui/react-slot':
         specifier: ^1.2.4
-        version: 1.2.4(@types/react@19.2.2)(react@19.2.3)
+        version: 1.2.4(@types/react@19.2.3)(react@19.2.3)
       '@radix-ui/react-tabs':
         specifier: ^1.1.13
-        version: 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+        version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       ai:
         specifier: ^6.0.33
         version: 6.0.33(zod@3.25.76)
@@ -48,13 +48,13 @@ importers:
         specifier: ^0.562.0
         version: 0.562.0(react@19.2.3)
       next:
-        specifier: 16.1.0
-        version: 16.1.0(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+        specifier: 16.1.1
+        version: 16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       react:
-        specifier: ^19.2.0
+        specifier: 19.2.3
         version: 19.2.3
       react-dom:
-        specifier: ^19.2.0
+        specifier: 19.2.3
         version: 19.2.3(react@19.2.3)
       shiki:
         specifier: ^3.21.0
@@ -79,11 +79,11 @@ importers:
         specifier: ^22.15.3
         version: 22.19.6
       '@types/react':
-        specifier: 19.2.2
-        version: 19.2.2
+        specifier: 19.2.3
+        version: 19.2.3
       '@types/react-dom':
-        specifier: 19.2.2
-        version: 19.2.2(@types/react@19.2.2)
+        specifier: 19.2.3
+        version: 19.2.3(@types/react@19.2.3)
       eslint:
         specifier: ^9.39.1
         version: 9.39.2(jiti@2.6.1)
@@ -115,13 +115,13 @@ importers:
         specifier: ^6.0.33
         version: 6.0.33(zod@3.25.76)
       next:
-        specifier: ^15.3.0
-        version: 15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+        specifier: 16.1.1
+        version: 16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       react:
-        specifier: ^19.0.0
+        specifier: 19.2.3
         version: 19.2.3
       react-dom:
-        specifier: ^19.0.0
+        specifier: 19.2.3
         version: 19.2.3(react@19.2.3)
       zod:
         specifier: ^3.24.0
@@ -131,11 +131,11 @@ importers:
         specifier: ^22.10.0
         version: 22.19.6
       '@types/react':
-        specifier: ^19.0.0
-        version: 19.2.2
+        specifier: 19.2.3
+        version: 19.2.3
       '@types/react-dom':
-        specifier: ^19.0.0
-        version: 19.2.2(@types/react@19.2.2)
+        specifier: 19.2.3
+        version: 19.2.3(@types/react@19.2.3)
       typescript:
         specifier: ^5.7.2
         version: 5.9.2
@@ -198,15 +198,15 @@ importers:
         specifier: workspace:*
         version: link:../core
       react:
-        specifier: ^18.0.0 || ^19.0.0
+        specifier: ^19.0.0
         version: 19.2.3
     devDependencies:
       '@repo/typescript-config':
         specifier: workspace:*
         version: link:../typescript-config
       '@types/react':
-        specifier: ^18.2.0
-        version: 18.3.27
+        specifier: 19.2.3
+        version: 19.2.3
       tsup:
         specifier: ^8.0.2
         version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.2)
@@ -219,10 +219,10 @@ importers:
   packages/ui:
     dependencies:
       react:
-        specifier: ^19.2.0
+        specifier: 19.2.3
         version: 19.2.3
       react-dom:
-        specifier: ^19.2.0
+        specifier: 19.2.3
         version: 19.2.3(react@19.2.3)
     devDependencies:
       '@repo/eslint-config':
@@ -235,11 +235,11 @@ importers:
         specifier: ^22.15.3
         version: 22.19.6
       '@types/react':
-        specifier: 19.2.2
-        version: 19.2.2
+        specifier: 19.2.3
+        version: 19.2.3
       '@types/react-dom':
-        specifier: 19.2.2
-        version: 19.2.2(@types/react@19.2.2)
+        specifier: 19.2.3
+        version: 19.2.3(@types/react@19.2.3)
       eslint:
         specifier: ^9.39.1
         version: 9.39.2(jiti@2.6.1)
@@ -635,107 +635,56 @@ packages:
   '@jridgewell/trace-mapping@0.3.31':
     resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
 
-  '@next/env@15.5.9':
-    resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==}
-
-  '@next/env@16.1.0':
-    resolution: {integrity: sha512-Dd23XQeFHmhf3KBW76leYVkejHlCdB7erakC2At2apL1N08Bm+dLYNP+nNHh0tzUXfPQcNcXiQyacw0PG4Fcpw==}
+  '@next/env@16.1.1':
+    resolution: {integrity: sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==}
 
   '@next/eslint-plugin-next@15.5.9':
     resolution: {integrity: sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==}
 
-  '@next/swc-darwin-arm64@15.5.7':
-    resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==}
+  '@next/swc-darwin-arm64@16.1.1':
+    resolution: {integrity: sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [darwin]
 
-  '@next/swc-darwin-arm64@16.1.0':
-    resolution: {integrity: sha512-onHq8dl8KjDb8taANQdzs3XmIqQWV3fYdslkGENuvVInFQzZnuBYYOG2HGHqqtvgmEU7xWzhgndXXxnhk4Z3fQ==}
-    engines: {node: '>= 10'}
-    cpu: [arm64]
-    os: [darwin]
-
-  '@next/swc-darwin-x64@15.5.7':
-    resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==}
-    engines: {node: '>= 10'}
-    cpu: [x64]
-    os: [darwin]
-
-  '@next/swc-darwin-x64@16.1.0':
-    resolution: {integrity: sha512-Am6VJTp8KhLuAH13tPrAoVIXzuComlZlMwGr++o2KDjWiKPe3VwpxYhgV6I4gKls2EnsIMggL4y7GdXyDdJcFA==}
+  '@next/swc-darwin-x64@16.1.1':
+    resolution: {integrity: sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
-  '@next/swc-linux-arm64-gnu@15.5.7':
-    resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==}
-    engines: {node: '>= 10'}
-    cpu: [arm64]
-    os: [linux]
-
-  '@next/swc-linux-arm64-gnu@16.1.0':
-    resolution: {integrity: sha512-fVicfaJT6QfghNyg8JErZ+EMNQ812IS0lmKfbmC01LF1nFBcKfcs4Q75Yy8IqnsCqH/hZwGhqzj3IGVfWV6vpA==}
+  '@next/swc-linux-arm64-gnu@16.1.1':
+    resolution: {integrity: sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
-  '@next/swc-linux-arm64-musl@15.5.7':
-    resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==}
+  '@next/swc-linux-arm64-musl@16.1.1':
+    resolution: {integrity: sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
-  '@next/swc-linux-arm64-musl@16.1.0':
-    resolution: {integrity: sha512-TojQnDRoX7wJWXEEwdfuJtakMDW64Q7NrxQPviUnfYJvAx5/5wcGE+1vZzQ9F17m+SdpFeeXuOr6v3jbyusYMQ==}
-    engines: {node: '>= 10'}
-    cpu: [arm64]
-    os: [linux]
-
-  '@next/swc-linux-x64-gnu@15.5.7':
-    resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==}
-    engines: {node: '>= 10'}
-    cpu: [x64]
-    os: [linux]
-
-  '@next/swc-linux-x64-gnu@16.1.0':
-    resolution: {integrity: sha512-quhNFVySW4QwXiZkZ34SbfzNBm27vLrxZ2HwTfFFO1BBP0OY1+pI0nbyewKeq1FriqU+LZrob/cm26lwsiAi8Q==}
-    engines: {node: '>= 10'}
-    cpu: [x64]
-    os: [linux]
-
-  '@next/swc-linux-x64-musl@15.5.7':
-    resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==}
+  '@next/swc-linux-x64-gnu@16.1.1':
+    resolution: {integrity: sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
-  '@next/swc-linux-x64-musl@16.1.0':
-    resolution: {integrity: sha512-6JW0z2FZUK5iOVhUIWqE4RblAhUj1EwhZ/MwteGb//SpFTOHydnhbp3868gxalwea+mbOLWO6xgxj9wA9wNvNw==}
+  '@next/swc-linux-x64-musl@16.1.1':
+    resolution: {integrity: sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
-  '@next/swc-win32-arm64-msvc@15.5.7':
-    resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==}
-    engines: {node: '>= 10'}
-    cpu: [arm64]
-    os: [win32]
-
-  '@next/swc-win32-arm64-msvc@16.1.0':
-    resolution: {integrity: sha512-+DK/akkAvvXn5RdYN84IOmLkSy87SCmpofJPdB8vbLmf01BzntPBSYXnMvnEEv/Vcf3HYJwt24QZ/s6sWAwOMQ==}
+  '@next/swc-win32-arm64-msvc@16.1.1':
+    resolution: {integrity: sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
 
-  '@next/swc-win32-x64-msvc@15.5.7':
-    resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==}
-    engines: {node: '>= 10'}
-    cpu: [x64]
-    os: [win32]
-
-  '@next/swc-win32-x64-msvc@16.1.0':
-    resolution: {integrity: sha512-Tr0j94MphimCCks+1rtYPzQFK+faJuhHWCegU9S9gDlgyOk8Y3kPmO64UcjyzZAlligeBtYZ/2bEyrKq0d2wqQ==}
+  '@next/swc-win32-x64-msvc@16.1.1':
+    resolution: {integrity: sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
@@ -1169,19 +1118,13 @@ packages:
   '@types/node@22.19.6':
     resolution: {integrity: sha512-qm+G8HuG6hOHQigsi7VGuLjUVu6TtBo/F05zvX04Mw2uCg9Dv0Qxy3Qw7j41SidlTcl5D/5yg0SEZqOB+EqZnQ==}
 
-  '@types/prop-types@15.7.15':
-    resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
-
-  '@types/react-dom@19.2.2':
-    resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==}
+  '@types/react-dom@19.2.3':
+    resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
     peerDependencies:
       '@types/react': ^19.2.0
 
-  '@types/react@18.3.27':
-    resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
-
-  '@types/react@19.2.2':
-    resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
+  '@types/react@19.2.3':
+    resolution: {integrity: sha512-k5dJVszUiNr1DSe8Cs+knKR6IrqhqdhpUwzqhkS8ecQTSf3THNtbfIp/umqHMpX2bv+9dkx3fwDv/86LcSfvSg==}
 
   '@types/unist@3.0.3':
     resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -2062,29 +2005,8 @@ packages:
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
-  next@15.5.9:
-    resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==}
-    engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
-    hasBin: true
-    peerDependencies:
-      '@opentelemetry/api': ^1.1.0
-      '@playwright/test': ^1.51.1
-      babel-plugin-react-compiler: '*'
-      react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
-      react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
-      sass: ^1.3.0
-    peerDependenciesMeta:
-      '@opentelemetry/api':
-        optional: true
-      '@playwright/test':
-        optional: true
-      babel-plugin-react-compiler:
-        optional: true
-      sass:
-        optional: true
-
-  next@16.1.0:
-    resolution: {integrity: sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw==}
+  next@16.1.1:
+    resolution: {integrity: sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==}
     engines: {node: '>=20.9.0'}
     hasBin: true
     peerDependencies:
@@ -2915,60 +2837,34 @@ snapshots:
       '@jridgewell/resolve-uri': 3.1.2
       '@jridgewell/sourcemap-codec': 1.5.5
 
-  '@next/env@15.5.9': {}
-
-  '@next/env@16.1.0': {}
+  '@next/env@16.1.1': {}
 
   '@next/eslint-plugin-next@15.5.9':
     dependencies:
       fast-glob: 3.3.1
 
-  '@next/swc-darwin-arm64@15.5.7':
-    optional: true
-
-  '@next/swc-darwin-arm64@16.1.0':
-    optional: true
-
-  '@next/swc-darwin-x64@15.5.7':
-    optional: true
-
-  '@next/swc-darwin-x64@16.1.0':
-    optional: true
-
-  '@next/swc-linux-arm64-gnu@15.5.7':
-    optional: true
-
-  '@next/swc-linux-arm64-gnu@16.1.0':
+  '@next/swc-darwin-arm64@16.1.1':
     optional: true
 
-  '@next/swc-linux-arm64-musl@15.5.7':
+  '@next/swc-darwin-x64@16.1.1':
     optional: true
 
-  '@next/swc-linux-arm64-musl@16.1.0':
+  '@next/swc-linux-arm64-gnu@16.1.1':
     optional: true
 
-  '@next/swc-linux-x64-gnu@15.5.7':
+  '@next/swc-linux-arm64-musl@16.1.1':
     optional: true
 
-  '@next/swc-linux-x64-gnu@16.1.0':
+  '@next/swc-linux-x64-gnu@16.1.1':
     optional: true
 
-  '@next/swc-linux-x64-musl@15.5.7':
+  '@next/swc-linux-x64-musl@16.1.1':
     optional: true
 
-  '@next/swc-linux-x64-musl@16.1.0':
+  '@next/swc-win32-arm64-msvc@16.1.1':
     optional: true
 
-  '@next/swc-win32-arm64-msvc@15.5.7':
-    optional: true
-
-  '@next/swc-win32-arm64-msvc@16.1.0':
-    optional: true
-
-  '@next/swc-win32-x64-msvc@15.5.7':
-    optional: true
-
-  '@next/swc-win32-x64-msvc@16.1.0':
+  '@next/swc-win32-x64-msvc@16.1.1':
     optional: true
 
   '@nodelib/fs.scandir@2.1.5':
@@ -2987,135 +2883,135 @@ snapshots:
 
   '@radix-ui/primitive@1.1.3': {}
 
-  '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 19.2.2
-      '@types/react-dom': 19.2.2(@types/react@19.2.2)
+      '@types/react': 19.2.3
+      '@types/react-dom': 19.2.3(@types/react@19.2.3)
 
-  '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-context@1.1.2(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-direction@1.1.1(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-id@1.1.1(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 19.2.2
-      '@types/react-dom': 19.2.2(@types/react@19.2.2)
+      '@types/react': 19.2.3
+      '@types/react-dom': 19.2.3(@types/react@19.2.3)
 
-  '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 19.2.2
-      '@types/react-dom': 19.2.2(@types/react@19.2.2)
+      '@types/react': 19.2.3
+      '@types/react-dom': 19.2.3(@types/react@19.2.3)
 
-  '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 19.2.2
-      '@types/react-dom': 19.2.2(@types/react@19.2.2)
+      '@types/react': 19.2.3
+      '@types/react-dom': 19.2.3(@types/react@19.2.3)
 
-  '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-slot@1.2.3(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-slot@1.2.4(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-direction': 1.1.1(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 19.2.2
-      '@types/react-dom': 19.2.2(@types/react@19.2.2)
+      '@types/react': 19.2.3
+      '@types/react-dom': 19.2.3(@types/react@19.2.3)
 
-  '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.3)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.3)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.3)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
-  '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.3)':
+  '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.3)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 19.2.2
+      '@types/react': 19.2.3
 
   '@rollup/rollup-android-arm-eabi@4.55.1':
     optional: true
@@ -3316,18 +3212,11 @@ snapshots:
     dependencies:
       undici-types: 6.21.0
 
-  '@types/prop-types@15.7.15': {}
-
-  '@types/react-dom@19.2.2(@types/react@19.2.2)':
-    dependencies:
-      '@types/react': 19.2.2
-
-  '@types/react@18.3.27':
+  '@types/react-dom@19.2.3(@types/react@19.2.3)':
     dependencies:
-      '@types/prop-types': 15.7.15
-      csstype: 3.2.3
+      '@types/react': 19.2.3
 
-  '@types/react@19.2.2':
+  '@types/react@19.2.3':
     dependencies:
       csstype: 3.2.3
 
@@ -4380,33 +4269,9 @@ snapshots:
 
   natural-compare@1.4.0: {}
 
-  next@15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
-    dependencies:
-      '@next/env': 15.5.9
-      '@swc/helpers': 0.5.15
-      caniuse-lite: 1.0.30001764
-      postcss: 8.4.31
-      react: 19.2.3
-      react-dom: 19.2.3(react@19.2.3)
-      styled-jsx: 5.1.6(react@19.2.3)
-    optionalDependencies:
-      '@next/swc-darwin-arm64': 15.5.7
-      '@next/swc-darwin-x64': 15.5.7
-      '@next/swc-linux-arm64-gnu': 15.5.7
-      '@next/swc-linux-arm64-musl': 15.5.7
-      '@next/swc-linux-x64-gnu': 15.5.7
-      '@next/swc-linux-x64-musl': 15.5.7
-      '@next/swc-win32-arm64-msvc': 15.5.7
-      '@next/swc-win32-x64-msvc': 15.5.7
-      '@opentelemetry/api': 1.9.0
-      sharp: 0.34.5
-    transitivePeerDependencies:
-      - '@babel/core'
-      - babel-plugin-macros
-
-  next@16.1.0(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+  next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
     dependencies:
-      '@next/env': 16.1.0
+      '@next/env': 16.1.1
       '@swc/helpers': 0.5.15
       baseline-browser-mapping: 2.9.14
       caniuse-lite: 1.0.30001764
@@ -4415,14 +4280,14 @@ snapshots:
       react-dom: 19.2.3(react@19.2.3)
       styled-jsx: 5.1.6(react@19.2.3)
     optionalDependencies:
-      '@next/swc-darwin-arm64': 16.1.0
-      '@next/swc-darwin-x64': 16.1.0
-      '@next/swc-linux-arm64-gnu': 16.1.0
-      '@next/swc-linux-arm64-musl': 16.1.0
-      '@next/swc-linux-x64-gnu': 16.1.0
-      '@next/swc-linux-x64-musl': 16.1.0
-      '@next/swc-win32-arm64-msvc': 16.1.0
-      '@next/swc-win32-x64-msvc': 16.1.0
+      '@next/swc-darwin-arm64': 16.1.1
+      '@next/swc-darwin-x64': 16.1.1
+      '@next/swc-linux-arm64-gnu': 16.1.1
+      '@next/swc-linux-arm64-musl': 16.1.1
+      '@next/swc-linux-x64-gnu': 16.1.1
+      '@next/swc-linux-x64-musl': 16.1.1
+      '@next/swc-win32-arm64-msvc': 16.1.1
+      '@next/swc-win32-x64-msvc': 16.1.1
       '@opentelemetry/api': 1.9.0
       sharp: 0.34.5
     transitivePeerDependencies: