"use client"; import { Children, useEffect, 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, TabsContent, } 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"; import { icons as lucideIcons } from "lucide-react"; // ============================================================================= // 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"; let alignClass: string; if (isHorizontal) { alignClass = props.align === "center" ? "items-center" : props.align === "end" ? "items-end" : props.align === "stretch" ? "items-stretch" : "items-start"; } else { // Vertical: items-center/end lets inline elements (Avatar, Badge, Button) // center/align naturally. Block containers (Grid, Accordion, Table) add // their own w-full to stretch regardless. alignClass = props.align === "center" ? "items-center" : props.align === "end" ? "items-end" : props.align === "start" ? "items-start" : "items-stretch"; } const justifyClass = props.justify === "center" ? "justify-center" : props.justify === "end" ? "justify-end" : props.justify === "between" ? "justify-between" : props.justify === "around" ? "justify-around" : ""; // Horizontal stacks wrap by default; wrap:false keeps a single scrolling // row (Kanban boards / column layouts that must sit side by side). const horizontalFlow = props.wrap === false ? "flex-row flex-nowrap overflow-x-auto w-full [&>*]:shrink-0" : "flex-row flex-wrap"; return (
{children}
); }, Grid: ({ props, children }) => { const childCount = Array.isArray(children) ? children.length : children ? 1 : 0; const n = Math.min(props.columns ?? 1, childCount || 1); const cols = n >= 7 ? "grid-cols-7" : 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, children }) => { 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; // Children map positionally to tabs: children[i] is the panel for tabs[i]. const panels = Children.toArray(children); return ( { setValue(v); emit("change"); }} > {tabs.map((tab) => ( {tab.label} ))} {tabs.map((tab, i) => ( {panels[i] ?? null} ))} ); }, 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 tracking-tight" : level === "h3" ? "text-base font-semibold tracking-tight" : level === "h4" ? "text-sm font-medium uppercase tracking-wider text-muted-foreground" : "text-xl font-semibold tracking-tight"; 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 }) => { const src = typeof props.src === "string" ? props.src.trim() : ""; if (src) { return ( // eslint-disable-next-line @next/next/no-img-element {props.alt ); } return (
{props.alt && {props.alt}}
); }, Map: ({ props }) => { const query = String(props.query ?? ""); const zoom = typeof props.zoom === "number" ? props.zoom : 14; const height = typeof props.height === "number" ? props.height : 320; const src = `https://maps.google.com/maps?q=${encodeURIComponent( query, )}&z=${zoom}&output=embed`; return (