"use client"; import { useState } from "react"; import { defineRegistry, useBoundProp, useStateBinding, useFieldValidation, } from "@json-render/react"; import { toast } from "sonner"; import { playgroundCatalog } from "./catalog"; // shadcn components import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Switch } from "@/components/ui/switch"; import { Progress } from "@/components/ui/progress"; import { Separator } from "@/components/ui/separator"; import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; import { Dialog as DialogPrimitive, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Accordion as AccordionPrimitive, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { Badge } from "@/components/ui/badge"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Carousel as CarouselPrimitive, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from "@/components/ui/carousel"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Table as TablePrimitive, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Drawer as DrawerPrimitive, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; import { DropdownMenu as DropdownMenuPrimitive, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Pagination as PaginationPrimitive, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import { Popover as PopoverPrimitive, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Skeleton } from "@/components/ui/skeleton"; import { Slider } from "@/components/ui/slider"; import { Tabs as TabsPrimitive, TabsList, TabsTrigger, } from "@/components/ui/tabs"; import { Toggle } from "@/components/ui/toggle"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Tooltip as TooltipPrimitive, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; // ============================================================================= // Registry — components + actions, types inferred from catalog // ============================================================================= export const { registry, executeAction } = defineRegistry(playgroundCatalog, { components: { // ── Layout ──────────────────────────────────────────────────────── Card: ({ props, children }) => { 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 (
{(props.title || props.description) && (
{props.title && (

{props.title}

)} {props.description && (

{props.description}

)}
)}
{children}
); }, Stack: ({ props, children }) => { const isHorizontal = props.direction === "horizontal"; const gapClass = props.gap === "lg" ? "gap-4" : props.gap === "md" ? "gap-3" : props.gap === "sm" ? "gap-2" : props.gap === "none" ? "gap-0" : "gap-3"; const alignClass = props.align === "center" ? "items-center" : props.align === "end" ? "items-end" : props.align === "stretch" ? "items-stretch" : "items-start"; const justifyClass = props.justify === "center" ? "justify-center" : props.justify === "end" ? "justify-end" : props.justify === "between" ? "justify-between" : props.justify === "around" ? "justify-around" : ""; return (
{children}
); }, Grid: ({ props, children }) => { const n = props.columns ?? 1; const cols = n >= 6 ? "grid-cols-6" : n >= 5 ? "grid-cols-5" : n >= 4 ? "grid-cols-4" : n >= 3 ? "grid-cols-3" : n >= 2 ? "grid-cols-2" : "grid-cols-1"; const gridGap = props.gap === "lg" ? "gap-4" : props.gap === "sm" ? "gap-2" : "gap-3"; return
{children}
; }, Separator: ({ props }) => ( ), Tabs: ({ props, bindings, emit }) => { const tabs = props.tabs ?? []; const [boundValue, setBoundValue] = useBoundProp( props.value as string | undefined, bindings?.value, ); const [localValue, setLocalValue] = useState( props.defaultValue ?? tabs[0]?.value ?? "", ); const isBound = !!bindings?.value; const value = isBound ? (boundValue ?? tabs[0]?.value ?? "") : localValue; const setValue = isBound ? setBoundValue : setLocalValue; return ( { setValue(v); emit("change"); }} > {tabs.map((tab) => ( {tab.label} ))} ); }, Accordion: ({ props }) => { const items = props.items ?? []; const accordionType = props.type ?? "single"; if (accordionType === "multiple") { return ( {items.map((item, i) => ( {item.title} {item.content} ))} ); } return ( {items.map((item, i) => ( {item.title} {item.content} ))} ); }, Collapsible: ({ props, children }) => { const [open, setOpen] = useState(props.defaultOpen ?? false); return ( {children} ); }, Dialog: ({ props, children }) => { const [open, setOpen] = useStateBinding(props.openPath); return ( setOpen(v)}> {props.title} {props.description && ( {props.description} )} {children} ); }, Drawer: ({ props, children }) => { const [open, setOpen] = useStateBinding(props.openPath); return ( setOpen(v)}> {props.title} {props.description && ( {props.description} )}
{children}
); }, Carousel: ({ props }) => { const items = props.items ?? []; return ( {items.map((item, i) => (
{item.title && (

{item.title}

)} {item.description && (

{item.description}

)}
))}
); }, // ── Data Display ────────────────────────────────────────────────── Table: ({ props }) => { const columns = props.columns ?? []; const rawRows: unknown[] = Array.isArray(props.rows) ? props.rows : []; const rows = rawRows.map((row) => { if (Array.isArray(row)) return row.map(String); if (row && typeof row === "object") { const obj = row as Record; return columns.map((col) => String(obj[col] ?? obj[col.toLowerCase()] ?? ""), ); } return columns.map(() => ""); }); return (
{props.caption && {props.caption}} {columns.map((col) => ( {col} ))} {rows.map((row, i) => ( {row.map((cell, j) => ( {cell} ))} ))}
); }, Heading: ({ props }) => { const level = props.level ?? "h2"; const headingClass = level === "h1" ? "text-2xl font-bold" : level === "h3" ? "text-base font-semibold" : level === "h4" ? "text-sm font-semibold" : "text-lg font-semibold"; if (level === "h1") return

{props.text}

; if (level === "h3") return

{props.text}

; if (level === "h4") return

{props.text}

; return

{props.text}

; }, Text: ({ props }) => { const textClass = props.variant === "caption" ? "text-xs" : props.variant === "muted" ? "text-sm text-muted-foreground" : props.variant === "lead" ? "text-xl text-muted-foreground" : props.variant === "code" ? "font-mono text-sm bg-muted px-1.5 py-0.5 rounded" : "text-sm"; if (props.variant === "code") { return {props.text}; } return

{props.text}

; }, Image: ({ props }) => (
{props.alt || "img"}
), Avatar: ({ props }) => { const name = props.name || "?"; const initials = name .split(" ") .map((n) => n[0]) .join("") .slice(0, 2) .toUpperCase(); const avatarSize = props.size === "lg" ? "w-12 h-12 text-base" : props.size === "sm" ? "w-8 h-8 text-xs" : "w-10 h-10 text-sm"; return (
{initials}
); }, Badge: ({ props }) => { const variant = props.variant === "success" || props.variant === "warning" ? "secondary" : props.variant === "danger" ? "destructive" : "default"; const customClass = props.variant === "success" ? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100" : props.variant === "warning" ? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100" : ""; return ( {props.text} ); }, Alert: ({ props }) => { const variant = props.type === "error" ? "destructive" : "default"; const customClass = props.type === "success" ? "border-green-200 bg-green-50 text-green-900 dark:border-green-800 dark:bg-green-950 dark:text-green-100" : props.type === "warning" ? "border-yellow-200 bg-yellow-50 text-yellow-900 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-100" : props.type === "info" ? "border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-100" : ""; return ( {props.title} {props.message && ( {props.message} )} ); }, Progress: ({ props }) => { const value = Math.min(100, Math.max(0, props.value || 0)); return (
{props.label && ( )}
); }, Skeleton: ({ props }) => ( ), Spinner: ({ props }) => { const sizeClass = props.size === "lg" ? "h-8 w-8" : props.size === "sm" ? "h-4 w-4" : "h-6 w-6"; return (
{props.label && ( {props.label} )}
); }, Tooltip: ({ props }) => ( {props.text}

{props.content}

), Popover: ({ props }) => (

{props.content}

), Rating: ({ props }) => { const ratingValue = props.value || 0; const maxRating = props.max ?? 5; return (
{props.label && ( )}
{Array.from({ length: maxRating }).map((_, i) => ( * ))}
); }, // ── Charts ──────────────────────────────────────────────────────── BarGraph: ({ props }) => { const data = props.data || []; const maxValue = Math.max(...data.map((d) => d.value), 1); return (
{props.title && (
{props.title}
)}
{data.map((d, i) => (
{d.value}
{d.label}
))}
); }, LineGraph: ({ props }) => { const data = props.data || []; const maxValue = Math.max(...data.map((d) => d.value)); const minValue = Math.min(...data.map((d) => d.value)); const range = maxValue - minValue || 1; const width = 300; const height = 100; const padding = { top: 10, right: 10, bottom: 10, left: 10 }; const chartWidth = width - padding.left - padding.right; const chartHeight = height - padding.top - padding.bottom; const points = data.map((d, i) => { const x = padding.left + (data.length > 1 ? (i / (data.length - 1)) * chartWidth : chartWidth / 2); const y = padding.top + chartHeight - ((d.value - minValue) / range) * chartHeight; return { x, y, ...d }; }); const pathD = points.length > 0 ? `M ${points.map((p) => `${p.x} ${p.y}`).join(" L ")}` : ""; return (
{props.title && (
{props.title}
)}
{pathD && ( )} {points.map((p, i) => ( ))}
{data.length > 0 && (
{data.map((d, i) => (
{d.label}
))}
)}
); }, // ── Form Inputs ─────────────────────────────────────────────────── Input: ({ props, bindings, emit }) => { const [boundValue, setBoundValue] = useBoundProp( props.value as string | undefined, bindings?.value, ); const [localValue, setLocalValue] = useState(""); const isBound = !!bindings?.value; const value = isBound ? (boundValue ?? "") : localValue; const setValue = isBound ? setBoundValue : setLocalValue; const hasValidation = !!(bindings?.value && props.checks?.length); const { errors, validate } = useFieldValidation( bindings?.value ?? "", hasValidation ? { checks: props.checks ?? [] } : undefined, ); return (
{props.label && } setValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") emit("submit"); }} onFocus={() => emit("focus")} onBlur={() => { if (hasValidation) validate(); emit("blur"); }} /> {errors.length > 0 && (

{errors[0]}

)}
); }, Textarea: ({ props, bindings }) => { const [boundValue, setBoundValue] = useBoundProp( props.value as string | undefined, bindings?.value, ); const [localValue, setLocalValue] = useState(""); const isBound = !!bindings?.value; const value = isBound ? (boundValue ?? "") : localValue; const setValue = isBound ? setBoundValue : setLocalValue; const hasValidation = !!(bindings?.value && props.checks?.length); const { errors, validate } = useFieldValidation( bindings?.value ?? "", hasValidation ? { checks: props.checks ?? [] } : undefined, ); return (
{props.label && }