catalog.ts 12 KB

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