| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- import { defineCatalog } from "@json-render/core";
- import { schema } from "@json-render/react/schema";
- import { z } from "zod";
- /**
- * Web playground component catalog
- *
- * This defines the components available for AI generation in the playground.
- * Components and actions are implemented in lib/registry.tsx via defineRegistry.
- *
- * Keep schemas simple — one format per prop, no unions.
- * Fewer components = less confusion for the AI.
- */
- export const playgroundCatalog = defineCatalog(schema, {
- components: {
- // ── Layout ──────────────────────────────────────────────────────────
- Card: {
- props: z.object({
- title: z.string().nullable(),
- description: z.string().nullable(),
- maxWidth: z.enum(["sm", "md", "lg", "full"]).nullable(),
- centered: z.boolean().nullable(),
- }),
- slots: ["default"],
- description:
- "Container card for content sections. Use for forms/content boxes, NOT for page headers.",
- example: { title: "Overview", description: "Your account summary" },
- },
- Stack: {
- props: z.object({
- direction: z.enum(["horizontal", "vertical"]).nullable(),
- gap: z.enum(["none", "sm", "md", "lg"]).nullable(),
- align: z.enum(["start", "center", "end", "stretch"]).nullable(),
- justify: z
- .enum(["start", "center", "end", "between", "around"])
- .nullable(),
- }),
- slots: ["default"],
- description: "Flex container for layouts",
- example: { direction: "vertical", gap: "md" },
- },
- Grid: {
- props: z.object({
- columns: z.number().nullable(),
- gap: z.enum(["sm", "md", "lg"]).nullable(),
- }),
- slots: ["default"],
- description: "Grid layout (1-6 columns)",
- example: { columns: 3, gap: "md" },
- },
- Separator: {
- props: z.object({
- orientation: z.enum(["horizontal", "vertical"]).nullable(),
- }),
- description: "Visual separator line",
- },
- Tabs: {
- props: z.object({
- tabs: z.array(
- z.object({
- label: z.string(),
- value: z.string(),
- }),
- ),
- defaultValue: z.string().nullable(),
- value: z.string().nullable(),
- }),
- events: ["change"],
- description:
- "Tab navigation. Use { $bindState } on value for active tab binding.",
- },
- Accordion: {
- props: z.object({
- items: z.array(
- z.object({
- title: z.string(),
- content: z.string(),
- }),
- ),
- type: z.enum(["single", "multiple"]).nullable(),
- }),
- description:
- "Collapsible sections. Items as [{title, content}]. Type 'single' (default) or 'multiple'.",
- },
- Collapsible: {
- props: z.object({
- title: z.string(),
- defaultOpen: z.boolean().nullable(),
- }),
- slots: ["default"],
- description: "Collapsible section with trigger. Children render inside.",
- },
- Dialog: {
- props: z.object({
- title: z.string(),
- description: z.string().nullable(),
- openPath: z.string(),
- }),
- slots: ["default"],
- description:
- "Modal dialog. Set openPath to a boolean state path. Use setState to toggle.",
- },
- Drawer: {
- props: z.object({
- title: z.string(),
- description: z.string().nullable(),
- openPath: z.string(),
- }),
- slots: ["default"],
- description:
- "Bottom sheet drawer. Set openPath to a boolean state path. Use setState to toggle.",
- },
- Carousel: {
- props: z.object({
- items: z.array(
- z.object({
- title: z.string().nullable(),
- description: z.string().nullable(),
- }),
- ),
- }),
- description: "Horizontally scrollable carousel of cards.",
- },
- // ── Data Display ────────────────────────────────────────────────────
- Table: {
- props: z.object({
- columns: z.array(z.string()),
- rows: z.array(z.array(z.string())),
- caption: z.string().nullable(),
- }),
- description:
- 'Data table. columns: header labels. rows: 2D array of cell strings, e.g. [["Alice","admin"],["Bob","user"]].',
- example: {
- columns: ["Name", "Role"],
- rows: [
- ["Alice", "Admin"],
- ["Bob", "User"],
- ],
- },
- },
- Heading: {
- props: z.object({
- text: z.string(),
- level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
- }),
- description: "Heading text (h1-h4)",
- example: { text: "Welcome", level: "h1" },
- },
- Text: {
- props: z.object({
- text: z.string(),
- variant: z
- .enum(["body", "caption", "muted", "lead", "code"])
- .nullable(),
- }),
- description:
- 'Paragraph text. In repeat scopes, use { "$template": "${field1} ${field2}" } to interpolate item fields.',
- example: { text: "Hello, world!" },
- },
- Image: {
- props: z.object({
- alt: z.string(),
- width: z.number().nullable(),
- height: z.number().nullable(),
- }),
- description: "Placeholder image (displays alt text in a styled box)",
- },
- Icon: {
- props: z.object({
- name: z.string(),
- size: z.enum(["sm", "md", "lg"]).nullable(),
- color: z
- .enum(["default", "muted", "primary", "success", "warning", "danger"])
- .nullable(),
- }),
- description:
- "Lucide icon by name. PascalCase: MapPin, Mail, Globe, Calendar, Star, Heart, Check, X, ArrowRight, Phone, Building, Clock, Shield, Zap, Users, Eye, Download, Upload, Search, Filter, Settings, Bell, ChevronRight, ExternalLink, Info, AlertTriangle, CheckCircle, XCircle. Use in horizontal Stacks with Text for icon+label patterns. Never use emoji — always use Icon.",
- },
- Avatar: {
- props: z.object({
- src: z.string().nullable(),
- name: z.string(),
- size: z.enum(["sm", "md", "lg"]).nullable(),
- }),
- description: "User avatar with fallback initials",
- example: { name: "Jane Doe", size: "md" },
- },
- Badge: {
- props: z.object({
- text: z.string(),
- variant: z.enum(["default", "success", "warning", "danger"]).nullable(),
- }),
- description: "Status badge",
- example: { text: "Active", variant: "success" },
- },
- Alert: {
- props: z.object({
- title: z.string(),
- message: z.string().nullable(),
- type: z.enum(["info", "success", "warning", "error"]).nullable(),
- }),
- description: "Alert banner",
- example: {
- title: "Note",
- message: "Your changes have been saved.",
- type: "success",
- },
- },
- Progress: {
- props: z.object({
- value: z.number(),
- max: z.number().nullable(),
- label: z.string().nullable(),
- }),
- description: "Progress bar (value 0-100)",
- example: { value: 65, max: 100, label: "Upload progress" },
- },
- Skeleton: {
- props: z.object({
- width: z.string().nullable(),
- height: z.string().nullable(),
- rounded: z.boolean().nullable(),
- }),
- description: "Loading placeholder skeleton",
- },
- Spinner: {
- props: z.object({
- size: z.enum(["sm", "md", "lg"]).nullable(),
- label: z.string().nullable(),
- }),
- description: "Loading spinner indicator",
- },
- Tooltip: {
- props: z.object({
- content: z.string(),
- text: z.string(),
- }),
- description: "Hover tooltip. Shows content on hover over text.",
- },
- Popover: {
- props: z.object({
- trigger: z.string(),
- content: z.string(),
- }),
- description: "Popover that appears on click of trigger.",
- },
- Rating: {
- props: z.object({
- value: z.number(),
- max: z.number().nullable(),
- label: z.string().nullable(),
- interactive: z.boolean().nullable(),
- }),
- events: ["change"],
- description:
- "Interactive star rating. Use { $bindState } on value for binding. Set interactive: false for read-only display.",
- example: { value: 4, max: 5, label: "Rating" },
- },
- Metric: {
- props: z.object({
- label: z.string(),
- value: z.string(),
- change: z.string().nullable(),
- changeType: z.enum(["positive", "negative", "neutral"]).nullable(),
- prefix: z.string().nullable(),
- suffix: z.string().nullable(),
- }),
- description:
- "Key metric / stat display. Shows a large value with label and optional change indicator. Use for dashboard KPIs.",
- example: {
- label: "Total Revenue",
- value: "125,000",
- prefix: "$",
- change: "+12.5%",
- changeType: "positive",
- },
- },
- // ── Charts ──────────────────────────────────────────────────────────
- BarGraph: {
- props: z.object({
- title: z.string().nullable(),
- data: z.array(
- z.object({
- label: z.string(),
- value: z.number(),
- }),
- ),
- }),
- description: "Vertical bar chart",
- },
- LineGraph: {
- props: z.object({
- title: z.string().nullable(),
- data: z.array(
- z.object({
- label: z.string(),
- value: z.number(),
- }),
- ),
- }),
- description: "Line chart with points",
- },
- // ── Form Inputs ─────────────────────────────────────────────────────
- Input: {
- props: z.object({
- label: z.string(),
- name: z.string(),
- type: z.enum(["text", "email", "password", "number"]).nullable(),
- placeholder: z.string().nullable(),
- value: z.string().nullable(),
- checks: z
- .array(
- z.object({
- type: z.string(),
- message: z.string(),
- args: z.record(z.string(), z.unknown()).optional(),
- }),
- )
- .nullable(),
- }),
- events: ["submit", "focus", "blur"],
- description:
- "Text input field. Use { $bindState } on value for two-way binding. Use checks for validation (e.g. required, email, minLength).",
- example: {
- label: "Email",
- name: "email",
- type: "email",
- placeholder: "you@example.com",
- },
- },
- Textarea: {
- props: z.object({
- label: z.string(),
- name: z.string(),
- placeholder: z.string().nullable(),
- rows: z.number().nullable(),
- value: z.string().nullable(),
- checks: z
- .array(
- z.object({
- type: z.string(),
- message: z.string(),
- args: z.record(z.string(), z.unknown()).optional(),
- }),
- )
- .nullable(),
- }),
- description:
- "Multi-line text input. Use { $bindState } on value for binding. Use checks for validation.",
- },
- Select: {
- props: z.object({
- label: z.string(),
- name: z.string(),
- options: z.array(z.string()),
- placeholder: z.string().nullable(),
- value: z.string().nullable(),
- checks: z
- .array(
- z.object({
- type: z.string(),
- message: z.string(),
- args: z.record(z.string(), z.unknown()).optional(),
- }),
- )
- .nullable(),
- }),
- events: ["change"],
- description:
- "Dropdown select input. Use { $bindState } on value for binding. Use checks for validation.",
- },
- Checkbox: {
- props: z.object({
- label: z.string(),
- name: z.string(),
- checked: z.boolean().nullable(),
- }),
- events: ["change"],
- description: "Checkbox input. Use { $bindState } on checked for binding.",
- },
- Radio: {
- props: z.object({
- label: z.string(),
- name: z.string(),
- options: z.array(z.string()),
- value: z.string().nullable(),
- }),
- events: ["change"],
- description:
- "Radio button group. Use { $bindState } on value for binding.",
- },
- Switch: {
- props: z.object({
- label: z.string(),
- name: z.string(),
- checked: z.boolean().nullable(),
- }),
- events: ["change"],
- description: "Toggle switch. Use { $bindState } on checked for binding.",
- },
- Slider: {
- props: z.object({
- label: z.string().nullable(),
- min: z.number().nullable(),
- max: z.number().nullable(),
- step: z.number().nullable(),
- value: z.number().nullable(),
- }),
- events: ["change"],
- description:
- "Range slider input. Use { $bindState } on value for binding.",
- },
- // ── Actions ─────────────────────────────────────────────────────────
- Button: {
- props: z.object({
- label: z.string(),
- variant: z
- .enum(["primary", "secondary", "outline", "danger"])
- .nullable(),
- disabled: z.boolean().nullable(),
- }),
- events: ["press"],
- description:
- "Clickable button. primary = solid fill, outline = bordered/transparent, secondary = muted fill. Bind on.press for handler.",
- example: { label: "Submit", variant: "primary" },
- },
- Link: {
- props: z.object({
- label: z.string(),
- href: z.string(),
- }),
- events: ["press"],
- description: "Anchor link. Bind on.press for click handler.",
- },
- DropdownMenu: {
- props: z.object({
- label: z.string(),
- items: z.array(
- z.object({
- label: z.string(),
- value: z.string(),
- }),
- ),
- }),
- events: ["select"],
- description: "Dropdown menu with trigger button and selectable items.",
- },
- Toggle: {
- props: z.object({
- label: z.string(),
- pressed: z.boolean().nullable(),
- variant: z.enum(["default", "outline"]).nullable(),
- }),
- events: ["change"],
- description:
- "Toggle button. Use { $bindState } on pressed for state binding.",
- },
- ToggleGroup: {
- props: z.object({
- items: z.array(
- z.object({
- label: z.string(),
- value: z.string(),
- }),
- ),
- type: z.enum(["single", "multiple"]).nullable(),
- value: z.string().nullable(),
- }),
- events: ["change"],
- description:
- "Group of toggle buttons. Type 'single' (default) or 'multiple'. Use { $bindState } on value.",
- },
- ButtonGroup: {
- props: z.object({
- buttons: z.array(
- z.object({
- label: z.string(),
- value: z.string(),
- }),
- ),
- selected: z.string().nullable(),
- }),
- events: ["change"],
- description:
- "Segmented button group. Use { $bindState } on selected for selected value.",
- },
- Pagination: {
- props: z.object({
- totalPages: z.number(),
- page: z.number().nullable(),
- }),
- events: ["change"],
- description:
- "Page navigation. Use { $bindState } on page for current page number.",
- },
- },
- actions: {
- setState: {
- params: z.object({
- statePath: z.string(),
- value: z.unknown(),
- }),
- description: "Update a value in the state model at the given statePath.",
- },
- pushState: {
- params: z.object({
- statePath: z.string(),
- value: z.unknown(),
- clearStatePath: z.string().optional(),
- }),
- description:
- 'Append an item to an array in state. Value can contain {"$state":"/statePath"} refs and "$id" for auto IDs. clearStatePath resets another path after pushing.',
- },
- removeState: {
- params: z.object({
- statePath: z.string(),
- index: z.number(),
- }),
- description: "Remove an item from an array in state at the given index.",
- },
- buttonClick: {
- params: z.object({
- message: z.string().nullable(),
- }),
- description: "Shows a toast with the message.",
- },
- formSubmit: {
- params: z.object({
- formName: z.string().nullable(),
- }),
- description: "Shows a toast confirming form submission.",
- },
- linkClick: {
- params: z.object({
- href: z.string(),
- }),
- description: "Shows a toast with the link destination.",
- },
- },
- });
|