catalog.ts 13 KB

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