catalog.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. import { defineCatalog } from "@json-render/core";
  2. import { schema } from "@json-render/react/schema";
  3. import { z } from "zod";
  4. /**
  5. * Web playground component catalog
  6. *
  7. * This defines the components available for AI generation in the playground.
  8. * Components and actions are implemented in lib/registry.tsx via defineRegistry.
  9. *
  10. * Keep schemas simple — one format per prop, no unions.
  11. * Fewer components = less confusion for the AI.
  12. */
  13. export const playgroundCatalog = defineCatalog(schema, {
  14. components: {
  15. // ── Layout ──────────────────────────────────────────────────────────
  16. Card: {
  17. props: z.object({
  18. title: z.string().nullable(),
  19. description: z.string().nullable(),
  20. maxWidth: z.enum(["sm", "md", "lg", "full"]).nullable(),
  21. centered: z.boolean().nullable(),
  22. }),
  23. slots: ["default"],
  24. description:
  25. "Container card for content sections. Use for forms/content boxes, NOT for page headers.",
  26. example: { title: "Overview", description: "Your account summary" },
  27. },
  28. Stack: {
  29. props: z.object({
  30. direction: z.enum(["horizontal", "vertical"]).nullable(),
  31. gap: z.enum(["none", "sm", "md", "lg"]).nullable(),
  32. align: z.enum(["start", "center", "end", "stretch"]).nullable(),
  33. justify: z
  34. .enum(["start", "center", "end", "between", "around"])
  35. .nullable(),
  36. }),
  37. slots: ["default"],
  38. description: "Flex container for layouts",
  39. example: { direction: "vertical", gap: "md" },
  40. },
  41. Grid: {
  42. props: z.object({
  43. columns: z.number().nullable(),
  44. gap: z.enum(["sm", "md", "lg"]).nullable(),
  45. }),
  46. slots: ["default"],
  47. description: "Grid layout (1-6 columns)",
  48. example: { columns: 3, gap: "md" },
  49. },
  50. Separator: {
  51. props: z.object({
  52. orientation: z.enum(["horizontal", "vertical"]).nullable(),
  53. }),
  54. description: "Visual separator line",
  55. },
  56. Tabs: {
  57. props: z.object({
  58. tabs: z.array(
  59. z.object({
  60. label: z.string(),
  61. value: z.string(),
  62. }),
  63. ),
  64. defaultValue: z.string().nullable(),
  65. statePath: z.string().nullable(),
  66. }),
  67. events: ["change"],
  68. description:
  69. "Tab navigation. Use statePath to bind the active tab value.",
  70. },
  71. Accordion: {
  72. props: z.object({
  73. items: z.array(
  74. z.object({
  75. title: z.string(),
  76. content: z.string(),
  77. }),
  78. ),
  79. type: z.enum(["single", "multiple"]).nullable(),
  80. }),
  81. description:
  82. "Collapsible sections. Items as [{title, content}]. Type 'single' (default) or 'multiple'.",
  83. },
  84. Collapsible: {
  85. props: z.object({
  86. title: z.string(),
  87. defaultOpen: z.boolean().nullable(),
  88. }),
  89. slots: ["default"],
  90. description: "Collapsible section with trigger. Children render inside.",
  91. },
  92. Dialog: {
  93. props: z.object({
  94. title: z.string(),
  95. description: z.string().nullable(),
  96. openPath: z.string(),
  97. }),
  98. slots: ["default"],
  99. description:
  100. "Modal dialog. Set openPath to a boolean state path. Use setState to toggle.",
  101. },
  102. Drawer: {
  103. props: z.object({
  104. title: z.string(),
  105. description: z.string().nullable(),
  106. openPath: z.string(),
  107. }),
  108. slots: ["default"],
  109. description:
  110. "Bottom sheet drawer. Set openPath to a boolean state path. Use setState to toggle.",
  111. },
  112. Carousel: {
  113. props: z.object({
  114. items: z.array(
  115. z.object({
  116. title: z.string().nullable(),
  117. description: z.string().nullable(),
  118. }),
  119. ),
  120. }),
  121. description: "Horizontally scrollable carousel of cards.",
  122. },
  123. // ── Data Display ────────────────────────────────────────────────────
  124. Table: {
  125. props: z.object({
  126. columns: z.array(z.string()),
  127. rows: z.array(z.array(z.string())),
  128. caption: z.string().nullable(),
  129. }),
  130. description:
  131. 'Data table. columns: header labels. rows: 2D array of cell strings, e.g. [["Alice","admin"],["Bob","user"]].',
  132. example: {
  133. columns: ["Name", "Role"],
  134. rows: [
  135. ["Alice", "Admin"],
  136. ["Bob", "User"],
  137. ],
  138. },
  139. },
  140. Heading: {
  141. props: z.object({
  142. text: z.string(),
  143. level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
  144. }),
  145. description: "Heading text (h1-h4)",
  146. example: { text: "Welcome", level: "h1" },
  147. },
  148. Text: {
  149. props: z.object({
  150. text: z.string(),
  151. variant: z
  152. .enum(["body", "caption", "muted", "lead", "code"])
  153. .nullable(),
  154. }),
  155. description: "Paragraph text",
  156. example: { text: "Hello, world!" },
  157. },
  158. Image: {
  159. props: z.object({
  160. alt: z.string(),
  161. width: z.number().nullable(),
  162. height: z.number().nullable(),
  163. }),
  164. description: "Placeholder image (displays alt text in a styled box)",
  165. },
  166. Avatar: {
  167. props: z.object({
  168. src: z.string().nullable(),
  169. name: z.string(),
  170. size: z.enum(["sm", "md", "lg"]).nullable(),
  171. }),
  172. description: "User avatar with fallback initials",
  173. example: { name: "Jane Doe", size: "md" },
  174. },
  175. Badge: {
  176. props: z.object({
  177. text: z.string(),
  178. variant: z.enum(["default", "success", "warning", "danger"]).nullable(),
  179. }),
  180. description: "Status badge",
  181. example: { text: "Active", variant: "success" },
  182. },
  183. Alert: {
  184. props: z.object({
  185. title: z.string(),
  186. message: z.string().nullable(),
  187. type: z.enum(["info", "success", "warning", "error"]).nullable(),
  188. }),
  189. description: "Alert banner",
  190. example: {
  191. title: "Note",
  192. message: "Your changes have been saved.",
  193. type: "success",
  194. },
  195. },
  196. Progress: {
  197. props: z.object({
  198. value: z.number(),
  199. max: z.number().nullable(),
  200. label: z.string().nullable(),
  201. }),
  202. description: "Progress bar (value 0-100)",
  203. example: { value: 65, max: 100, label: "Upload progress" },
  204. },
  205. Skeleton: {
  206. props: z.object({
  207. width: z.string().nullable(),
  208. height: z.string().nullable(),
  209. rounded: z.boolean().nullable(),
  210. }),
  211. description: "Loading placeholder skeleton",
  212. },
  213. Spinner: {
  214. props: z.object({
  215. size: z.enum(["sm", "md", "lg"]).nullable(),
  216. label: z.string().nullable(),
  217. }),
  218. description: "Loading spinner indicator",
  219. },
  220. Tooltip: {
  221. props: z.object({
  222. content: z.string(),
  223. text: z.string(),
  224. }),
  225. description: "Hover tooltip. Shows content on hover over text.",
  226. },
  227. Popover: {
  228. props: z.object({
  229. trigger: z.string(),
  230. content: z.string(),
  231. }),
  232. description: "Popover that appears on click of trigger.",
  233. },
  234. Rating: {
  235. props: z.object({
  236. value: z.number(),
  237. max: z.number().nullable(),
  238. label: z.string().nullable(),
  239. }),
  240. description: "Star rating display",
  241. },
  242. // ── Charts ──────────────────────────────────────────────────────────
  243. BarGraph: {
  244. props: z.object({
  245. title: z.string().nullable(),
  246. data: z.array(
  247. z.object({
  248. label: z.string(),
  249. value: z.number(),
  250. }),
  251. ),
  252. }),
  253. description: "Vertical bar chart",
  254. },
  255. LineGraph: {
  256. props: z.object({
  257. title: z.string().nullable(),
  258. data: z.array(
  259. z.object({
  260. label: z.string(),
  261. value: z.number(),
  262. }),
  263. ),
  264. }),
  265. description: "Line chart with points",
  266. },
  267. // ── Form Inputs ─────────────────────────────────────────────────────
  268. Input: {
  269. props: z.object({
  270. label: z.string(),
  271. name: z.string(),
  272. type: z.enum(["text", "email", "password", "number"]).nullable(),
  273. placeholder: z.string().nullable(),
  274. statePath: z.string().nullable(),
  275. }),
  276. events: ["submit", "focus", "blur"],
  277. description: "Text input field. Use statePath for two-way binding.",
  278. example: {
  279. label: "Email",
  280. name: "email",
  281. type: "email",
  282. placeholder: "you@example.com",
  283. },
  284. },
  285. Textarea: {
  286. props: z.object({
  287. label: z.string(),
  288. name: z.string(),
  289. placeholder: z.string().nullable(),
  290. rows: z.number().nullable(),
  291. statePath: z.string().nullable(),
  292. }),
  293. description: "Multi-line text input. Use statePath for binding.",
  294. },
  295. Select: {
  296. props: z.object({
  297. label: z.string(),
  298. name: z.string(),
  299. options: z.array(z.string()),
  300. placeholder: z.string().nullable(),
  301. statePath: z.string().nullable(),
  302. }),
  303. events: ["change"],
  304. description: "Dropdown select input. Use statePath for binding.",
  305. },
  306. Checkbox: {
  307. props: z.object({
  308. label: z.string(),
  309. name: z.string(),
  310. checked: z.boolean().nullable(),
  311. statePath: z.string().nullable(),
  312. }),
  313. events: ["change"],
  314. description: "Checkbox input. Use statePath for binding.",
  315. },
  316. Radio: {
  317. props: z.object({
  318. label: z.string(),
  319. name: z.string(),
  320. options: z.array(z.string()),
  321. statePath: z.string().nullable(),
  322. }),
  323. events: ["change"],
  324. description: "Radio button group. Use statePath for binding.",
  325. },
  326. Switch: {
  327. props: z.object({
  328. label: z.string(),
  329. name: z.string(),
  330. checked: z.boolean().nullable(),
  331. statePath: z.string().nullable(),
  332. }),
  333. events: ["change"],
  334. description: "Toggle switch. Use statePath for binding.",
  335. },
  336. Slider: {
  337. props: z.object({
  338. label: z.string().nullable(),
  339. min: z.number().nullable(),
  340. max: z.number().nullable(),
  341. step: z.number().nullable(),
  342. statePath: z.string().nullable(),
  343. }),
  344. events: ["change"],
  345. description: "Range slider input. Use statePath for binding.",
  346. },
  347. // ── Actions ─────────────────────────────────────────────────────────
  348. Button: {
  349. props: z.object({
  350. label: z.string(),
  351. variant: z.enum(["primary", "secondary", "danger"]).nullable(),
  352. disabled: z.boolean().nullable(),
  353. }),
  354. events: ["press"],
  355. description: "Clickable button. Bind on.press for handler.",
  356. example: { label: "Submit", variant: "primary" },
  357. },
  358. Link: {
  359. props: z.object({
  360. label: z.string(),
  361. href: z.string(),
  362. }),
  363. events: ["press"],
  364. description: "Anchor link. Bind on.press for click handler.",
  365. },
  366. DropdownMenu: {
  367. props: z.object({
  368. label: z.string(),
  369. items: z.array(
  370. z.object({
  371. label: z.string(),
  372. value: z.string(),
  373. }),
  374. ),
  375. }),
  376. events: ["select"],
  377. description: "Dropdown menu with trigger button and selectable items.",
  378. },
  379. Toggle: {
  380. props: z.object({
  381. label: z.string(),
  382. pressed: z.boolean().nullable(),
  383. statePath: z.string().nullable(),
  384. variant: z.enum(["default", "outline"]).nullable(),
  385. }),
  386. events: ["change"],
  387. description: "Toggle button. Use statePath for pressed state binding.",
  388. },
  389. ToggleGroup: {
  390. props: z.object({
  391. items: z.array(
  392. z.object({
  393. label: z.string(),
  394. value: z.string(),
  395. }),
  396. ),
  397. type: z.enum(["single", "multiple"]).nullable(),
  398. statePath: z.string().nullable(),
  399. }),
  400. events: ["change"],
  401. description:
  402. "Group of toggle buttons. Type 'single' (default) or 'multiple'.",
  403. },
  404. ButtonGroup: {
  405. props: z.object({
  406. buttons: z.array(
  407. z.object({
  408. label: z.string(),
  409. value: z.string(),
  410. }),
  411. ),
  412. statePath: z.string().nullable(),
  413. }),
  414. events: ["change"],
  415. description: "Segmented button group. Use statePath for selected value.",
  416. },
  417. Pagination: {
  418. props: z.object({
  419. totalPages: z.number(),
  420. statePath: z.string(),
  421. }),
  422. events: ["change"],
  423. description:
  424. "Page navigation. Bind statePath to a number for current page.",
  425. },
  426. },
  427. actions: {
  428. setState: {
  429. params: z.object({
  430. path: z.string(),
  431. value: z.unknown(),
  432. }),
  433. description: "Update a value in the state model at the given path.",
  434. },
  435. pushState: {
  436. params: z.object({
  437. path: z.string(),
  438. value: z.unknown(),
  439. clearPath: z.string().optional(),
  440. }),
  441. description:
  442. 'Append an item to an array in state. Value can contain {path:"/statePath"} refs and "$id" for auto IDs. clearPath resets another path after pushing.',
  443. },
  444. removeState: {
  445. params: z.object({
  446. path: z.string(),
  447. index: z.number(),
  448. }),
  449. description: "Remove an item from an array in state at the given index.",
  450. },
  451. buttonClick: {
  452. params: z.object({
  453. message: z.string().nullable(),
  454. }),
  455. description: "Shows a toast with the message.",
  456. },
  457. formSubmit: {
  458. params: z.object({
  459. formName: z.string().nullable(),
  460. }),
  461. description: "Shows a toast confirming form submission.",
  462. },
  463. linkClick: {
  464. params: z.object({
  465. href: z.string(),
  466. }),
  467. description: "Shows a toast with the link destination.",
  468. },
  469. },
  470. });