catalog.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. import { defineCatalog } from "@json-render/core";
  2. import { schema } from "@json-render/react/schema";
  3. import { z } from "zod";
  4. /**
  5. * Dashboard Catalog
  6. *
  7. * Components map directly to shadcn/ui components.
  8. * Actions correspond to real API endpoints in /api/v1/*
  9. */
  10. export const dashboardCatalog = defineCatalog(schema, {
  11. components: {
  12. // Layout
  13. Stack: {
  14. props: z.object({
  15. direction: z.enum(["horizontal", "vertical"]).nullable(),
  16. gap: z.enum(["sm", "md", "lg"]).nullable(),
  17. }),
  18. slots: ["default"],
  19. description: "Flex layout container",
  20. example: { direction: "vertical", gap: "md" },
  21. },
  22. Accordion: {
  23. props: z.object({
  24. type: z.enum(["single", "multiple"]).nullable(),
  25. }),
  26. slots: ["default"],
  27. description: "Collapsible accordion container",
  28. },
  29. AccordionItem: {
  30. props: z.object({
  31. value: z.string(),
  32. title: z.string(),
  33. }),
  34. slots: ["default"],
  35. description: "Accordion item with trigger and content",
  36. },
  37. // Form
  38. Button: {
  39. props: z.object({
  40. label: z.string(),
  41. variant: z
  42. .enum(["default", "secondary", "destructive", "outline", "ghost"])
  43. .nullable(),
  44. action: z.string(),
  45. actionParams: z.record(z.string(), z.unknown()).nullable(),
  46. disabled: z.boolean().nullable(),
  47. }),
  48. description:
  49. "Clickable button. Use actionParams to pass parameters to the action (e.g., { limit: 5, sort: 'newest' })",
  50. example: { label: "Save", variant: "default", action: "formSubmit" },
  51. },
  52. Input: {
  53. props: z.object({
  54. label: z.string().nullable(),
  55. value: z.string().nullable(),
  56. placeholder: z.string().nullable(),
  57. type: z.enum(["text", "email", "password", "number", "tel"]).nullable(),
  58. }),
  59. description:
  60. "Text input field. Use value with $bindState for two-way binding",
  61. example: {
  62. label: "Email",
  63. value: { $bindState: "/form/email" },
  64. placeholder: "you@example.com",
  65. type: "email",
  66. },
  67. },
  68. Form: {
  69. props: z.object({
  70. submitAction: z.string(),
  71. submitActionParams: z.record(z.string(), z.unknown()).nullable(),
  72. }),
  73. slots: ["default"],
  74. description:
  75. "Form container that enables Enter key submission. Wrap form inputs (Input, Select, Checkbox, etc.) and a submit Button inside this component.",
  76. },
  77. // Display
  78. Badge: {
  79. props: z.object({
  80. text: z.string(),
  81. variant: z
  82. .enum(["default", "secondary", "destructive", "outline"])
  83. .nullable(),
  84. }),
  85. description: "Status badge",
  86. example: { text: "Active", variant: "default" },
  87. },
  88. Alert: {
  89. props: z.object({
  90. variant: z.enum(["default", "destructive"]).nullable(),
  91. title: z.string(),
  92. description: z.string().nullable(),
  93. }),
  94. description: "Alert message",
  95. },
  96. Separator: {
  97. props: z.object({}),
  98. description: "Visual divider",
  99. },
  100. Avatar: {
  101. props: z.object({
  102. src: z.string().nullable(),
  103. alt: z.string().nullable(),
  104. fallback: z.string(),
  105. }),
  106. description: "User avatar image with fallback initials",
  107. },
  108. Checkbox: {
  109. props: z.object({
  110. label: z.string().nullable(),
  111. checked: z.boolean().nullable(),
  112. defaultChecked: z.boolean().nullable(),
  113. }),
  114. description:
  115. "Checkbox input. Use checked with $bindState for two-way binding",
  116. },
  117. Dialog: {
  118. props: z.object({
  119. trigger: z.string(),
  120. title: z.string(),
  121. description: z.string().nullable(),
  122. }),
  123. slots: ["default"],
  124. description: "Modal dialog with trigger button",
  125. },
  126. Drawer: {
  127. props: z.object({
  128. trigger: z.string(),
  129. title: z.string(),
  130. description: z.string().nullable(),
  131. side: z.enum(["top", "bottom", "left", "right"]).nullable(),
  132. }),
  133. slots: ["default"],
  134. description: "Slide-out drawer panel",
  135. },
  136. DropdownMenu: {
  137. props: z.object({
  138. trigger: z.string(),
  139. items: z.array(
  140. z.object({
  141. label: z.string(),
  142. action: z.string().nullable(),
  143. actionParams: z.record(z.string(), z.unknown()).nullable(),
  144. }),
  145. ),
  146. }),
  147. description: "Dropdown menu with action items",
  148. },
  149. Label: {
  150. props: z.object({
  151. text: z.string(),
  152. htmlFor: z.string().nullable(),
  153. }),
  154. description: "Form label",
  155. },
  156. Pagination: {
  157. props: z.object({
  158. currentPage: z.number(),
  159. totalPages: z.number(),
  160. onPageChange: z.string().nullable(),
  161. }),
  162. description: "Page navigation",
  163. },
  164. Popover: {
  165. props: z.object({
  166. trigger: z.string(),
  167. }),
  168. slots: ["default"],
  169. description: "Popover with trigger",
  170. },
  171. Progress: {
  172. props: z.object({
  173. value: z.number(),
  174. max: z.number().nullable(),
  175. }),
  176. description: "Progress bar",
  177. },
  178. RadioGroup: {
  179. props: z.object({
  180. value: z.string().nullable(),
  181. options: z.array(
  182. z.object({
  183. value: z.string(),
  184. label: z.string(),
  185. }),
  186. ),
  187. defaultValue: z.string().nullable(),
  188. }),
  189. description:
  190. "Radio button group. Use value with $bindState for two-way binding",
  191. },
  192. Select: {
  193. props: z.object({
  194. value: z.string().nullable(),
  195. placeholder: z.string().nullable(),
  196. options: z.array(
  197. z.object({
  198. value: z.string(),
  199. label: z.string(),
  200. }),
  201. ),
  202. }),
  203. description:
  204. "Dropdown select input. Use value with $bindState for two-way binding",
  205. },
  206. Skeleton: {
  207. props: z.object({
  208. width: z.string().nullable(),
  209. height: z.string().nullable(),
  210. }),
  211. description: "Loading placeholder",
  212. },
  213. Spinner: {
  214. props: z.object({
  215. size: z.enum(["sm", "md", "lg"]).nullable(),
  216. }),
  217. description: "Loading spinner",
  218. },
  219. Switch: {
  220. props: z.object({
  221. label: z.string().nullable(),
  222. checked: z.boolean().nullable(),
  223. defaultChecked: z.boolean().nullable(),
  224. }),
  225. description:
  226. "Toggle switch. Use checked with $bindState for two-way binding",
  227. },
  228. Tabs: {
  229. props: z.object({
  230. defaultValue: z.string().nullable(),
  231. tabs: z.array(
  232. z.object({
  233. value: z.string(),
  234. label: z.string(),
  235. }),
  236. ),
  237. }),
  238. slots: ["default"],
  239. description: "Tabbed content container",
  240. },
  241. TabContent: {
  242. props: z.object({
  243. value: z.string(),
  244. }),
  245. slots: ["default"],
  246. description: "Content for a specific tab",
  247. },
  248. Textarea: {
  249. props: z.object({
  250. label: z.string().nullable(),
  251. value: z.string().nullable(),
  252. placeholder: z.string().nullable(),
  253. rows: z.number().nullable(),
  254. }),
  255. description:
  256. "Multi-line text input. Use value with $bindState for two-way binding",
  257. },
  258. Tooltip: {
  259. props: z.object({
  260. content: z.string(),
  261. }),
  262. slots: ["default"],
  263. description: "Tooltip on hover",
  264. },
  265. Table: {
  266. props: z.object({
  267. data: z.array(z.record(z.string(), z.unknown())),
  268. columns: z.array(
  269. z.object({
  270. key: z.string(),
  271. label: z.string(),
  272. }),
  273. ),
  274. rowActions: z
  275. .array(
  276. z.object({
  277. label: z.string(),
  278. action: z.string(),
  279. variant: z
  280. .enum([
  281. "default",
  282. "secondary",
  283. "destructive",
  284. "outline",
  285. "ghost",
  286. ])
  287. .nullable(),
  288. }),
  289. )
  290. .nullable(),
  291. emptyMessage: z.string().nullable(),
  292. }),
  293. description:
  294. "Data table with optional row actions. Use { $state } on data to bind to an array of objects.",
  295. example: {
  296. data: { $state: "/customers/data" },
  297. columns: [
  298. { key: "name", label: "Name" },
  299. { key: "email", label: "Email" },
  300. ],
  301. },
  302. },
  303. // Typography
  304. Heading: {
  305. props: z.object({
  306. text: z.string(),
  307. level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
  308. }),
  309. description: "Section heading",
  310. example: { text: "Dashboard", level: "h1" },
  311. },
  312. Text: {
  313. props: z.object({
  314. content: z.string(),
  315. muted: z.boolean().nullable(),
  316. }),
  317. description: "Text content",
  318. example: { content: "Welcome back! Here is your overview." },
  319. },
  320. // Charts
  321. BarChart: {
  322. props: z.object({
  323. title: z.string().nullable(),
  324. data: z.array(z.record(z.string(), z.unknown())),
  325. xKey: z.string(),
  326. yKey: z.string(),
  327. aggregate: z.enum(["sum", "count", "avg"]).nullable(),
  328. color: z.string().nullable(),
  329. height: z.number().nullable(),
  330. }),
  331. description:
  332. "Bar chart visualization. Use { $state } on data to point to array of objects, xKey is the category/group field, yKey is the numeric value field. Use aggregate='count' to count items grouped by xKey (yKey becomes the count). For dates, xKey values are auto-formatted.",
  333. },
  334. LineChart: {
  335. props: z.object({
  336. title: z.string().nullable(),
  337. data: z.array(z.record(z.string(), z.unknown())),
  338. xKey: z.string(),
  339. yKey: z.string(),
  340. aggregate: z.enum(["sum", "count", "avg"]).nullable(),
  341. color: z.string().nullable(),
  342. height: z.number().nullable(),
  343. }),
  344. description:
  345. "Line chart visualization. Use { $state } on data to point to array of objects, xKey is the x-axis field, yKey is the numeric value field. Use aggregate='count' to count items grouped by xKey. For dates, xKey values are auto-formatted.",
  346. },
  347. },
  348. actions: {
  349. // Customers - use these for customer-related widgets
  350. viewCustomers: {
  351. params: z.object({
  352. status: z.enum(["active", "inactive"]).nullable(),
  353. limit: z.number().nullable(),
  354. sort: z.enum(["newest", "oldest"]).nullable(),
  355. }),
  356. description:
  357. "Fetch customers from GET /api/v1/customers. Supports ?limit=N&sort=newest|oldest. Data available at 'customers.data'",
  358. },
  359. refreshCustomers: {
  360. params: z.object({
  361. limit: z.number().nullable(),
  362. sort: z.enum(["newest", "oldest"]).nullable(),
  363. }),
  364. description:
  365. "Refresh customers from GET /api/v1/customers. Supports ?limit=N&sort=newest|oldest. Data available at 'customers.data'",
  366. },
  367. createCustomer: {
  368. params: z.object({
  369. name: z.string(),
  370. email: z.string(),
  371. phone: z.string().nullable(),
  372. }),
  373. description: "Create new customer via POST /api/v1/customers",
  374. },
  375. deleteCustomer: {
  376. params: z.object({
  377. customerId: z.string(),
  378. }),
  379. description: "Delete a customer via DELETE /api/v1/customers/:id",
  380. },
  381. // Invoices - use these for invoice-related widgets
  382. viewInvoices: {
  383. params: z.object({
  384. status: z.enum(["draft", "sent", "paid", "overdue"]).nullable(),
  385. }),
  386. description:
  387. "Fetch invoices from GET /api/v1/invoices. Data available at 'invoices.data'",
  388. },
  389. refreshInvoices: {
  390. params: z.object({}),
  391. description:
  392. "Refresh invoices from GET /api/v1/invoices. Data available at 'invoices.data'",
  393. },
  394. createInvoice: {
  395. params: z.object({
  396. customerId: z.string(),
  397. dueDate: z.string(),
  398. }),
  399. description: "Create new invoice via POST /api/v1/invoices",
  400. },
  401. sendInvoice: {
  402. params: z.object({
  403. invoiceId: z.string(),
  404. }),
  405. description: "Send invoice to customer",
  406. },
  407. markInvoicePaid: {
  408. params: z.object({
  409. invoiceId: z.string(),
  410. }),
  411. description: "Mark invoice as paid",
  412. },
  413. // Expenses - use these for expense-related widgets
  414. viewExpenses: {
  415. params: z.object({
  416. status: z.enum(["pending", "approved", "rejected"]).nullable(),
  417. }),
  418. description:
  419. "Fetch expenses from GET /api/v1/expenses. Data available at 'expenses.data'",
  420. },
  421. refreshExpenses: {
  422. params: z.object({}),
  423. description:
  424. "Refresh expenses from GET /api/v1/expenses. Data available at 'expenses.data'",
  425. },
  426. createExpense: {
  427. params: z.object({
  428. vendor: z.string(),
  429. category: z.string(),
  430. amount: z.number(),
  431. description: z.string().nullable(),
  432. }),
  433. description: "Create new expense via POST /api/v1/expenses",
  434. },
  435. approveExpense: {
  436. params: z.object({
  437. expenseId: z.string(),
  438. }),
  439. description: "Approve expense",
  440. },
  441. },
  442. });