catalog.ts 12 KB

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