makersclaw 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -1
  3. package/THIRD-PARTY-NOTICES.md +180 -0
  4. package/package.json +15 -8
  5. package/r/annotate.json +62 -0
  6. package/r/approval-request.json +52 -0
  7. package/r/audio-player.json +74 -0
  8. package/r/auth-form.json +55 -0
  9. package/r/board.json +55 -0
  10. package/r/calendar-view.json +105 -0
  11. package/r/cart.json +62 -0
  12. package/r/chart.json +83 -0
  13. package/r/chat.json +95 -0
  14. package/r/code-editor.json +94 -0
  15. package/r/comment-box.json +64 -0
  16. package/r/contact-section.json +53 -0
  17. package/r/core.json +341 -0
  18. package/r/csv-import.json +63 -0
  19. package/r/data-table.json +69 -0
  20. package/r/diagram.json +74 -0
  21. package/r/doc-editor.json +82 -0
  22. package/r/doc-page.json +61 -0
  23. package/r/email-render.json +73 -0
  24. package/r/faq.json +50 -0
  25. package/r/file-upload.json +54 -0
  26. package/r/flow-canvas.json +93 -0
  27. package/r/footer.json +52 -0
  28. package/r/gantt.json +70 -0
  29. package/r/job-panel.json +56 -0
  30. package/r/json-editor.json +66 -0
  31. package/r/map.json +89 -0
  32. package/r/menubar.json +52 -0
  33. package/r/mind-map.json +67 -0
  34. package/r/navigation-menu.json +52 -0
  35. package/r/order-detail.json +54 -0
  36. package/r/org-chart.json +66 -0
  37. package/r/page-builder.json +105 -0
  38. package/r/payment-methods.json +51 -0
  39. package/r/pdf-document.json +79 -0
  40. package/r/pdf-viewer.json +88 -0
  41. package/r/product-detail.json +76 -0
  42. package/r/product-grid.json +65 -0
  43. package/r/record-form.json +67 -0
  44. package/r/record-notes.json +55 -0
  45. package/r/registry.json +3612 -0
  46. package/r/search-dialog.json +51 -0
  47. package/r/settings-list.json +66 -0
  48. package/r/setup-checklist.json +51 -0
  49. package/r/share-dialog.json +52 -0
  50. package/r/sidebar.json +69 -0
  51. package/r/signature-pad.json +72 -0
  52. package/r/sketch-canvas.json +99 -0
  53. package/r/spreadsheet.json +90 -0
  54. package/r/stat-cards.json +66 -0
  55. package/r/task-list.json +63 -0
  56. package/r/task-model.json +98 -0
  57. package/r/testimonials.json +52 -0
  58. package/r/transaction-list.json +53 -0
  59. package/r/whiteboard.json +66 -0
  60. package/registry/blocks/annotate/README.md +179 -0
  61. package/registry/blocks/annotate/annotate-data.js +250 -0
  62. package/registry/blocks/annotate/annotate.jsx +646 -0
  63. package/registry/blocks/annotate/block.json +10 -0
  64. package/registry/blocks/annotate/demo.jsx +135 -0
  65. package/registry/blocks/annotate/index.js +15 -0
  66. package/registry/blocks/approval-request/README.md +116 -0
  67. package/registry/blocks/approval-request/approval-request.jsx +386 -0
  68. package/registry/blocks/approval-request/block.json +10 -0
  69. package/registry/blocks/approval-request/demo.jsx +127 -0
  70. package/registry/blocks/approval-request/index.js +4 -0
  71. package/registry/blocks/audio-player/README.md +167 -0
  72. package/registry/blocks/audio-player/audio-player.jsx +623 -0
  73. package/registry/blocks/audio-player/block.json +14 -0
  74. package/registry/blocks/audio-player/colors.js +109 -0
  75. package/registry/blocks/audio-player/demo.jsx +209 -0
  76. package/registry/blocks/audio-player/index.js +6 -0
  77. package/registry/blocks/audio-player/time.js +67 -0
  78. package/registry/blocks/audio-player/wavesurfer.js +9 -0
  79. package/registry/blocks/auth-form/README.md +126 -0
  80. package/registry/blocks/auth-form/auth-form.jsx +568 -0
  81. package/registry/blocks/auth-form/block.json +10 -0
  82. package/registry/blocks/auth-form/demo.jsx +131 -0
  83. package/registry/blocks/auth-form/index.js +3 -0
  84. package/registry/blocks/board/README.md +64 -13
  85. package/registry/blocks/board/block.json +2 -2
  86. package/registry/blocks/board/board.jsx +237 -94
  87. package/registry/blocks/board/demo.jsx +103 -24
  88. package/registry/blocks/calendar-view/README.md +180 -0
  89. package/registry/blocks/calendar-view/agenda.jsx +147 -0
  90. package/registry/blocks/calendar-view/block.json +76 -0
  91. package/registry/blocks/calendar-view/calendar-model.js +442 -0
  92. package/registry/blocks/calendar-view/calendar.jsx +627 -0
  93. package/registry/blocks/calendar-view/demo.jsx +195 -0
  94. package/registry/blocks/calendar-view/draft.jsx +49 -0
  95. package/registry/blocks/calendar-view/index.js +39 -0
  96. package/registry/blocks/calendar-view/month.jsx +346 -0
  97. package/registry/blocks/calendar-view/paint.js +69 -0
  98. package/registry/blocks/calendar-view/time-grid.jsx +527 -0
  99. package/registry/blocks/calendar-view/tray.jsx +87 -0
  100. package/registry/blocks/cart/README.md +146 -0
  101. package/registry/blocks/cart/block.json +10 -0
  102. package/registry/blocks/cart/cart.jsx +597 -0
  103. package/registry/blocks/cart/demo.jsx +98 -0
  104. package/registry/blocks/cart/index.js +4 -0
  105. package/registry/blocks/cart/money.js +28 -0
  106. package/registry/blocks/chart/README.md +316 -0
  107. package/registry/blocks/chart/block.json +10 -0
  108. package/registry/blocks/chart/cartesian.jsx +642 -0
  109. package/registry/blocks/chart/core.jsx +262 -0
  110. package/registry/blocks/chart/demo.jsx +357 -0
  111. package/registry/blocks/chart/heatmap.jsx +336 -0
  112. package/registry/blocks/chart/index.js +8 -0
  113. package/registry/blocks/chart/pie.jsx +245 -0
  114. package/registry/blocks/chart/sparkline.jsx +88 -0
  115. package/registry/blocks/chat/README.md +273 -0
  116. package/registry/blocks/chat/attachment.jsx +95 -0
  117. package/registry/blocks/chat/block.json +10 -0
  118. package/registry/blocks/chat/chat.jsx +132 -0
  119. package/registry/blocks/chat/composer.jsx +364 -0
  120. package/registry/blocks/chat/demo.jsx +578 -0
  121. package/registry/blocks/chat/header.jsx +34 -0
  122. package/registry/blocks/chat/index.js +9 -0
  123. package/registry/blocks/chat/message.jsx +450 -0
  124. package/registry/blocks/chat/scroller.jsx +247 -0
  125. package/registry/blocks/chat/text.jsx +104 -0
  126. package/registry/blocks/code-editor/README.md +210 -0
  127. package/registry/blocks/code-editor/block.json +22 -0
  128. package/registry/blocks/code-editor/code-editor.jsx +534 -0
  129. package/registry/blocks/code-editor/codemirror.js +38 -0
  130. package/registry/blocks/code-editor/demo.jsx +205 -0
  131. package/registry/blocks/code-editor/index.js +8 -0
  132. package/registry/blocks/code-editor/json.js +317 -0
  133. package/registry/blocks/code-editor/sql.js +136 -0
  134. package/registry/blocks/code-editor/text.js +101 -0
  135. package/registry/blocks/code-editor/theme.js +186 -0
  136. package/registry/blocks/comment-box/README.md +123 -0
  137. package/registry/blocks/comment-box/block.json +10 -0
  138. package/registry/blocks/comment-box/comment-body.jsx +217 -0
  139. package/registry/blocks/comment-box/comment-box.jsx +198 -0
  140. package/registry/blocks/comment-box/comment-time.jsx +77 -0
  141. package/registry/blocks/comment-box/demo.jsx +141 -0
  142. package/registry/blocks/comment-box/index.js +8 -0
  143. package/registry/blocks/contact-section/README.md +152 -0
  144. package/registry/blocks/contact-section/block.json +11 -0
  145. package/registry/blocks/contact-section/contact-section.jsx +509 -0
  146. package/registry/blocks/contact-section/demo.jsx +93 -0
  147. package/registry/blocks/contact-section/index.js +4 -0
  148. package/registry/blocks/csv-import/README.md +133 -0
  149. package/registry/blocks/csv-import/block.json +13 -0
  150. package/registry/blocks/csv-import/csv-import.jsx +479 -0
  151. package/registry/blocks/csv-import/demo.jsx +106 -0
  152. package/registry/blocks/csv-import/import.js +200 -0
  153. package/registry/blocks/csv-import/index.js +7 -0
  154. package/registry/blocks/data-table/README.md +386 -0
  155. package/registry/blocks/data-table/block.json +10 -0
  156. package/registry/blocks/data-table/data-table.jsx +1164 -0
  157. package/registry/blocks/data-table/demo.jsx +588 -0
  158. package/registry/blocks/data-table/index.js +5 -0
  159. package/registry/blocks/data-table/model.js +169 -0
  160. package/registry/blocks/diagram/README.md +108 -0
  161. package/registry/blocks/diagram/block.json +13 -0
  162. package/registry/blocks/diagram/demo.jsx +113 -0
  163. package/registry/blocks/diagram/diagram.jsx +481 -0
  164. package/registry/blocks/diagram/index.js +7 -0
  165. package/registry/blocks/diagram/mermaid.js +52 -0
  166. package/registry/blocks/diagram/palette.js +313 -0
  167. package/registry/blocks/diagram/source.js +98 -0
  168. package/registry/blocks/doc-editor/README.md +215 -0
  169. package/registry/blocks/doc-editor/block.json +15 -0
  170. package/registry/blocks/doc-editor/demo.jsx +225 -0
  171. package/registry/blocks/doc-editor/doc-editor.jsx +291 -0
  172. package/registry/blocks/doc-editor/doc.js +309 -0
  173. package/registry/blocks/doc-editor/index.js +7 -0
  174. package/registry/blocks/doc-editor/schema.jsx +56 -0
  175. package/registry/blocks/doc-editor/shadcn-classes.js +110 -0
  176. package/registry/blocks/doc-editor/theme.js +82 -0
  177. package/registry/blocks/doc-page/README.md +119 -0
  178. package/registry/blocks/doc-page/autosave.js +104 -0
  179. package/registry/blocks/doc-page/block.json +10 -0
  180. package/registry/blocks/doc-page/demo.jsx +137 -0
  181. package/registry/blocks/doc-page/doc-page.jsx +276 -0
  182. package/registry/blocks/doc-page/index.js +4 -0
  183. package/registry/blocks/email-render/README.md +151 -0
  184. package/registry/blocks/email-render/block.json +14 -0
  185. package/registry/blocks/email-render/demo.jsx +153 -0
  186. package/registry/blocks/email-render/doc-email.jsx +210 -0
  187. package/registry/blocks/email-render/email.jsx +221 -0
  188. package/registry/blocks/email-render/index.js +10 -0
  189. package/registry/blocks/email-render/render.js +50 -0
  190. package/registry/blocks/email-render/theme.js +215 -0
  191. package/registry/blocks/faq/README.md +101 -0
  192. package/registry/blocks/faq/block.json +11 -0
  193. package/registry/blocks/faq/demo.jsx +89 -0
  194. package/registry/blocks/faq/faq.jsx +220 -0
  195. package/registry/blocks/faq/index.js +4 -0
  196. package/registry/blocks/file-upload/README.md +138 -0
  197. package/registry/blocks/file-upload/block.json +10 -0
  198. package/registry/blocks/file-upload/demo.jsx +204 -0
  199. package/registry/blocks/file-upload/file-upload.jsx +635 -0
  200. package/registry/blocks/file-upload/index.js +4 -0
  201. package/registry/blocks/flow-canvas/README.md +420 -0
  202. package/registry/blocks/flow-canvas/block.json +13 -0
  203. package/registry/blocks/flow-canvas/board.js +819 -0
  204. package/registry/blocks/flow-canvas/chrome.jsx +649 -0
  205. package/registry/blocks/flow-canvas/context.jsx +35 -0
  206. package/registry/blocks/flow-canvas/demo.jsx +249 -0
  207. package/registry/blocks/flow-canvas/edges.jsx +107 -0
  208. package/registry/blocks/flow-canvas/flow-canvas.jsx +1329 -0
  209. package/registry/blocks/flow-canvas/index.js +41 -0
  210. package/registry/blocks/flow-canvas/nodes.jsx +392 -0
  211. package/registry/blocks/flow-canvas/theme.js +174 -0
  212. package/registry/blocks/footer/README.md +133 -0
  213. package/registry/blocks/footer/block.json +11 -0
  214. package/registry/blocks/footer/demo.jsx +119 -0
  215. package/registry/blocks/footer/footer.jsx +406 -0
  216. package/registry/blocks/footer/index.js +4 -0
  217. package/registry/blocks/gantt/README.md +220 -0
  218. package/registry/blocks/gantt/block.json +10 -0
  219. package/registry/blocks/gantt/demo.jsx +148 -0
  220. package/registry/blocks/gantt/gantt.jsx +923 -0
  221. package/registry/blocks/gantt/index.js +7 -0
  222. package/registry/blocks/gantt/rows.jsx +468 -0
  223. package/registry/blocks/gantt/schedule.js +278 -0
  224. package/registry/blocks/job-panel/README.md +86 -2
  225. package/registry/blocks/job-panel/block.json +3 -3
  226. package/registry/blocks/job-panel/demo.jsx +230 -3
  227. package/registry/blocks/job-panel/job-panel.jsx +169 -3
  228. package/registry/blocks/json-editor/README.md +133 -0
  229. package/registry/blocks/json-editor/block.json +10 -0
  230. package/registry/blocks/json-editor/demo.jsx +92 -0
  231. package/registry/blocks/json-editor/index.js +7 -0
  232. package/registry/blocks/json-editor/json-editor.jsx +305 -0
  233. package/registry/blocks/json-editor/json-tree.jsx +393 -0
  234. package/registry/blocks/json-editor/tree.js +225 -0
  235. package/registry/blocks/map/README.md +221 -0
  236. package/registry/blocks/map/block.json +14 -0
  237. package/registry/blocks/map/demo.jsx +201 -0
  238. package/registry/blocks/map/geo.js +198 -0
  239. package/registry/blocks/map/index.js +8 -0
  240. package/registry/blocks/map/list.jsx +128 -0
  241. package/registry/blocks/map/map-view.jsx +715 -0
  242. package/registry/blocks/map/maplibre.js +30 -0
  243. package/registry/blocks/map/marks.jsx +146 -0
  244. package/registry/blocks/map/theme.js +142 -0
  245. package/registry/blocks/menubar/README.md +109 -0
  246. package/registry/blocks/menubar/block.json +10 -0
  247. package/registry/blocks/menubar/demo.jsx +128 -0
  248. package/registry/blocks/menubar/index.js +4 -0
  249. package/registry/blocks/menubar/menubar.jsx +199 -0
  250. package/registry/blocks/mind-map/README.md +206 -0
  251. package/registry/blocks/mind-map/block.json +10 -0
  252. package/registry/blocks/mind-map/demo.jsx +103 -0
  253. package/registry/blocks/mind-map/index.js +31 -0
  254. package/registry/blocks/mind-map/mind-map.jsx +1125 -0
  255. package/registry/blocks/mind-map/topic.jsx +143 -0
  256. package/registry/blocks/mind-map/tree.js +645 -0
  257. package/registry/blocks/navigation-menu/README.md +124 -0
  258. package/registry/blocks/navigation-menu/block.json +10 -0
  259. package/registry/blocks/navigation-menu/demo.jsx +84 -0
  260. package/registry/blocks/navigation-menu/index.js +4 -0
  261. package/registry/blocks/navigation-menu/navigation-menu.jsx +448 -0
  262. package/registry/blocks/order-detail/README.md +109 -0
  263. package/registry/blocks/order-detail/block.json +10 -0
  264. package/registry/blocks/order-detail/demo.jsx +88 -0
  265. package/registry/blocks/order-detail/index.js +4 -0
  266. package/registry/blocks/order-detail/order-detail.jsx +354 -0
  267. package/registry/blocks/org-chart/README.md +145 -0
  268. package/registry/blocks/org-chart/block.json +10 -0
  269. package/registry/blocks/org-chart/demo.jsx +94 -0
  270. package/registry/blocks/org-chart/index.js +7 -0
  271. package/registry/blocks/org-chart/org-chart.jsx +631 -0
  272. package/registry/blocks/org-chart/org.js +245 -0
  273. package/registry/blocks/org-chart/person.jsx +96 -0
  274. package/registry/blocks/page-builder/README.md +257 -0
  275. package/registry/blocks/page-builder/block.json +14 -0
  276. package/registry/blocks/page-builder/demo.jsx +145 -0
  277. package/registry/blocks/page-builder/grapes.js +18 -0
  278. package/registry/blocks/page-builder/index.js +11 -0
  279. package/registry/blocks/page-builder/page-builder.jsx +704 -0
  280. package/registry/blocks/page-builder/page-view.jsx +33 -0
  281. package/registry/blocks/page-builder/page.js +118 -0
  282. package/registry/blocks/page-builder/panels.jsx +588 -0
  283. package/registry/blocks/page-builder/sections.js +209 -0
  284. package/registry/blocks/page-builder/theme.js +87 -0
  285. package/registry/blocks/page-builder/thumbs.jsx +189 -0
  286. package/registry/blocks/page-builder/tokens.js +329 -0
  287. package/registry/blocks/payment-methods/README.md +104 -0
  288. package/registry/blocks/payment-methods/block.json +10 -0
  289. package/registry/blocks/payment-methods/demo.jsx +71 -0
  290. package/registry/blocks/payment-methods/index.js +4 -0
  291. package/registry/blocks/payment-methods/payment-methods.jsx +302 -0
  292. package/registry/blocks/pdf-document/README.md +213 -0
  293. package/registry/blocks/pdf-document/block.json +13 -0
  294. package/registry/blocks/pdf-document/demo.jsx +136 -0
  295. package/registry/blocks/pdf-document/index.js +33 -0
  296. package/registry/blocks/pdf-document/money.js +83 -0
  297. package/registry/blocks/pdf-document/pdf-document.jsx +144 -0
  298. package/registry/blocks/pdf-document/primitives.jsx +271 -0
  299. package/registry/blocks/pdf-document/quote.jsx +179 -0
  300. package/registry/blocks/pdf-document/theme.js +193 -0
  301. package/registry/blocks/pdf-viewer/README.md +191 -0
  302. package/registry/blocks/pdf-viewer/block.json +14 -0
  303. package/registry/blocks/pdf-viewer/demo.jsx +252 -0
  304. package/registry/blocks/pdf-viewer/helpers.js +270 -0
  305. package/registry/blocks/pdf-viewer/index.js +8 -0
  306. package/registry/blocks/pdf-viewer/notes.jsx +195 -0
  307. package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1368 -0
  308. package/registry/blocks/pdf-viewer/pdfjs.js +37 -0
  309. package/registry/blocks/pdf-viewer/ssr-shim-end.js +6 -0
  310. package/registry/blocks/pdf-viewer/ssr-shim.js +44 -0
  311. package/registry/blocks/product-detail/README.md +217 -0
  312. package/registry/blocks/product-detail/block.json +10 -0
  313. package/registry/blocks/product-detail/demo.jsx +233 -0
  314. package/registry/blocks/product-detail/index.js +7 -0
  315. package/registry/blocks/product-detail/price.jsx +115 -0
  316. package/registry/blocks/product-detail/product-detail.jsx +348 -0
  317. package/registry/blocks/product-detail/product-media.jsx +102 -0
  318. package/registry/blocks/product-detail/product-options.jsx +144 -0
  319. package/registry/blocks/product-grid/README.md +162 -0
  320. package/registry/blocks/product-grid/block.json +10 -0
  321. package/registry/blocks/product-grid/demo.jsx +255 -0
  322. package/registry/blocks/product-grid/index.js +5 -0
  323. package/registry/blocks/product-grid/product-card.jsx +232 -0
  324. package/registry/blocks/product-grid/product-grid.jsx +235 -0
  325. package/registry/blocks/record-form/README.md +196 -0
  326. package/registry/blocks/record-form/block.json +10 -0
  327. package/registry/blocks/record-form/demo.jsx +525 -0
  328. package/registry/blocks/record-form/index.js +4 -0
  329. package/registry/blocks/record-form/record-fields.jsx +457 -0
  330. package/registry/blocks/record-form/record-form.jsx +415 -0
  331. package/registry/blocks/record-form/rules.js +81 -0
  332. package/registry/blocks/record-notes/README.md +139 -0
  333. package/registry/blocks/record-notes/block.json +10 -0
  334. package/registry/blocks/record-notes/demo.jsx +179 -0
  335. package/registry/blocks/record-notes/index.js +4 -0
  336. package/registry/blocks/record-notes/record-notes.jsx +266 -0
  337. package/registry/blocks/search-dialog/README.md +108 -0
  338. package/registry/blocks/search-dialog/block.json +10 -0
  339. package/registry/blocks/search-dialog/demo.jsx +116 -0
  340. package/registry/blocks/search-dialog/index.js +3 -0
  341. package/registry/blocks/search-dialog/search-dialog.jsx +235 -0
  342. package/registry/blocks/settings-list/README.md +171 -0
  343. package/registry/blocks/settings-list/block.json +10 -0
  344. package/registry/blocks/settings-list/demo.jsx +333 -0
  345. package/registry/blocks/settings-list/index.js +4 -0
  346. package/registry/blocks/settings-list/row-state.jsx +93 -0
  347. package/registry/blocks/settings-list/settings-list.jsx +349 -0
  348. package/registry/blocks/settings-list/settings-matrix.jsx +118 -0
  349. package/registry/blocks/setup-checklist/README.md +142 -0
  350. package/registry/blocks/setup-checklist/block.json +10 -0
  351. package/registry/blocks/setup-checklist/demo.jsx +159 -0
  352. package/registry/blocks/setup-checklist/index.js +4 -0
  353. package/registry/blocks/setup-checklist/setup-checklist.jsx +446 -0
  354. package/registry/blocks/share-dialog/README.md +113 -0
  355. package/registry/blocks/share-dialog/block.json +10 -0
  356. package/registry/blocks/share-dialog/demo.jsx +140 -0
  357. package/registry/blocks/share-dialog/index.js +3 -0
  358. package/registry/blocks/share-dialog/share-dialog.jsx +384 -0
  359. package/registry/blocks/sidebar/README.md +190 -0
  360. package/registry/blocks/sidebar/block.json +43 -0
  361. package/registry/blocks/sidebar/demo.jsx +577 -0
  362. package/registry/blocks/sidebar/index.js +4 -0
  363. package/registry/blocks/sidebar/sidebar-panel.jsx +149 -0
  364. package/registry/blocks/sidebar/sidebar.jsx +864 -0
  365. package/registry/blocks/signature-pad/README.md +137 -0
  366. package/registry/blocks/signature-pad/block.json +13 -0
  367. package/registry/blocks/signature-pad/colors.js +115 -0
  368. package/registry/blocks/signature-pad/demo.jsx +94 -0
  369. package/registry/blocks/signature-pad/index.js +6 -0
  370. package/registry/blocks/signature-pad/pad.js +7 -0
  371. package/registry/blocks/signature-pad/signature-pad.jsx +434 -0
  372. package/registry/blocks/signature-pad/signature.js +68 -0
  373. package/registry/blocks/sketch-canvas/README.md +280 -0
  374. package/registry/blocks/sketch-canvas/block.json +13 -0
  375. package/registry/blocks/sketch-canvas/demo.jsx +159 -0
  376. package/registry/blocks/sketch-canvas/excalidraw.js +40 -0
  377. package/registry/blocks/sketch-canvas/index.js +9 -0
  378. package/registry/blocks/sketch-canvas/palette.js +226 -0
  379. package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1188 -0
  380. package/registry/blocks/sketch-canvas/sketch.js +179 -0
  381. package/registry/blocks/sketch-canvas/ssr-shim-end.js +6 -0
  382. package/registry/blocks/sketch-canvas/ssr-shim.js +45 -0
  383. package/registry/blocks/sketch-canvas/stamps.js +104 -0
  384. package/registry/blocks/sketch-canvas/theme.js +131 -0
  385. package/registry/blocks/spreadsheet/README.md +307 -0
  386. package/registry/blocks/spreadsheet/block.json +13 -0
  387. package/registry/blocks/spreadsheet/cells.jsx +455 -0
  388. package/registry/blocks/spreadsheet/chrome.jsx +325 -0
  389. package/registry/blocks/spreadsheet/demo.jsx +177 -0
  390. package/registry/blocks/spreadsheet/grid.js +387 -0
  391. package/registry/blocks/spreadsheet/index.js +34 -0
  392. package/registry/blocks/spreadsheet/spreadsheet.jsx +1732 -0
  393. package/registry/blocks/spreadsheet/types.js +473 -0
  394. package/registry/blocks/stat-cards/README.md +173 -0
  395. package/registry/blocks/stat-cards/block.json +10 -0
  396. package/registry/blocks/stat-cards/demo.jsx +257 -0
  397. package/registry/blocks/stat-cards/index.js +4 -0
  398. package/registry/blocks/stat-cards/stat-cards.jsx +307 -0
  399. package/registry/blocks/stat-cards/usage.jsx +243 -0
  400. package/registry/blocks/task-list/README.md +176 -0
  401. package/registry/blocks/task-list/block.json +10 -0
  402. package/registry/blocks/task-list/demo.jsx +134 -0
  403. package/registry/blocks/task-list/index.js +5 -0
  404. package/registry/blocks/task-list/task-list.jsx +907 -0
  405. package/registry/blocks/task-model/README.md +455 -0
  406. package/registry/blocks/task-model/block.json +167 -0
  407. package/registry/blocks/task-model/changes.js +89 -0
  408. package/registry/blocks/task-model/dates.js +371 -0
  409. package/registry/blocks/task-model/demo.jsx +215 -0
  410. package/registry/blocks/task-model/dnd.jsx +240 -0
  411. package/registry/blocks/task-model/index.js +97 -0
  412. package/registry/blocks/task-model/model.js +919 -0
  413. package/registry/blocks/task-model/parts.jsx +449 -0
  414. package/registry/blocks/task-model/toolbar.jsx +271 -0
  415. package/registry/blocks/testimonials/README.md +139 -0
  416. package/registry/blocks/testimonials/block.json +11 -0
  417. package/registry/blocks/testimonials/demo.jsx +137 -0
  418. package/registry/blocks/testimonials/index.js +4 -0
  419. package/registry/blocks/testimonials/testimonials.jsx +430 -0
  420. package/registry/blocks/transaction-list/README.md +126 -0
  421. package/registry/blocks/transaction-list/block.json +10 -0
  422. package/registry/blocks/transaction-list/demo.jsx +79 -0
  423. package/registry/blocks/transaction-list/index.js +4 -0
  424. package/registry/blocks/transaction-list/transaction-list.jsx +383 -0
  425. package/registry/blocks/whiteboard/README.md +179 -0
  426. package/registry/blocks/whiteboard/block.json +10 -0
  427. package/registry/blocks/whiteboard/demo.jsx +169 -0
  428. package/registry/blocks/whiteboard/index.js +6 -0
  429. package/registry/blocks/whiteboard/templates.js +150 -0
  430. package/registry/blocks/whiteboard/whiteboard.jsx +721 -0
  431. package/src/install.mjs +18 -0
  432. package/src/shadcn.mjs +171 -0
  433. package/templates/next/files/README.md +145 -11
  434. package/templates/next/files/app/globals.css +55 -4
  435. package/templates/next/files/check/run.mjs +3 -3
  436. package/templates/next/files/components/parts/fixture.jsx +1 -1
  437. package/templates/next/files/components/parts/sample.js +15 -0
  438. package/templates/next/files/components/ui/avatar.jsx +141 -0
  439. package/templates/next/files/components/ui/breadcrumb.jsx +149 -0
  440. package/templates/next/files/components/ui/button-group.jsx +70 -0
  441. package/templates/next/files/components/ui/button.jsx +132 -12
  442. package/templates/next/files/components/ui/calendar.jsx +609 -0
  443. package/templates/next/files/components/ui/carousel.jsx +281 -0
  444. package/templates/next/files/components/ui/collapsible.jsx +208 -0
  445. package/templates/next/files/components/ui/combobox.jsx +608 -0
  446. package/templates/next/files/components/ui/command.jsx +577 -0
  447. package/templates/next/files/components/ui/context-menu.jsx +191 -0
  448. package/templates/next/files/components/ui/date-picker.jsx +360 -0
  449. package/templates/next/files/components/ui/distribution.jsx +117 -0
  450. package/templates/next/files/components/ui/feedback.jsx +143 -0
  451. package/templates/next/files/components/ui/field.jsx +115 -3
  452. package/templates/next/files/components/ui/file-input.jsx +234 -0
  453. package/templates/next/files/components/ui/icon.jsx +130 -8
  454. package/templates/next/files/components/ui/image.jsx +143 -0
  455. package/templates/next/files/components/ui/index.js +47 -2
  456. package/templates/next/files/components/ui/input-group.jsx +213 -0
  457. package/templates/next/files/components/ui/input-otp.jsx +170 -0
  458. package/templates/next/files/components/ui/item.jsx +206 -0
  459. package/templates/next/files/components/ui/menu.jsx +454 -82
  460. package/templates/next/files/components/ui/notice.jsx +126 -14
  461. package/templates/next/files/components/ui/pagination.jsx +156 -0
  462. package/templates/next/files/components/ui/popover.jsx +284 -0
  463. package/templates/next/files/components/ui/radio.jsx +193 -0
  464. package/templates/next/files/components/ui/rating.jsx +131 -0
  465. package/templates/next/files/components/ui/resizable.jsx +338 -0
  466. package/templates/next/files/components/ui/scroll-area.jsx +123 -0
  467. package/templates/next/files/components/ui/separator.jsx +119 -0
  468. package/templates/next/files/components/ui/sheet.jsx +96 -11
  469. package/templates/next/files/components/ui/slider.jsx +174 -0
  470. package/templates/next/files/components/ui/stat.jsx +574 -0
  471. package/templates/next/files/components/ui/steps.jsx +338 -0
  472. package/templates/next/files/components/ui/swatch.jsx +33 -0
  473. package/templates/next/files/components/ui/table.jsx +130 -0
  474. package/templates/next/files/components/ui/toast.jsx +440 -0
  475. package/templates/next/files/components/ui/toggle.jsx +195 -0
  476. package/templates/next/files/components/ui/tooltip.jsx +270 -0
  477. package/templates/next/files/lib/sample.js +4 -12
  478. package/templates/next/template.json +264 -8
@@ -0,0 +1,133 @@
1
+ # CsvImport
2
+
3
+ Bring rows in from a CSV in three steps. **Upload**: drop a file or browse
4
+ (or paste the text, or download a template of your fields to fill in).
5
+ **Match columns**: each column in the file, with a few of its values,
6
+ beside the field it goes to — matched for you by name and common synonyms
7
+ ("E-mail address" → Email, "Organization" → Company), changeable, or
8
+ "Don't import"; required fields that are still unmatched are named.
9
+ **Review**: every row in an editable spreadsheet. A cell that does not fit
10
+ its field ("last spring" in a date, "abc" in an amount, a missing required
11
+ value) keeps its text and gets a red corner; click it and type the right
12
+ value, delete the row, or leave the rows with problems out. Then one
13
+ **Import** — nothing is saved before it.
14
+
15
+ With no `fields`, it makes a new table instead: each column becomes a
16
+ field whose name you can change and whose type is guessed from its values
17
+ (number, currency, date, checkbox, link, single or multiple select, text).
18
+
19
+ The CSV is read by [papaparse](https://www.papaparse.com) (MIT): commas,
20
+ tabs, semicolons or pipes (guessed), quoted cells with commas and
21
+ newlines, a byte-order mark. Typed values come from the spreadsheet
22
+ block's column types, so "$1,250" is 1250 and "11/02/2024" is a day.
23
+
24
+ ```jsx
25
+ import { CsvImport } from "../components/blocks/csv-import"; // a Makersclaw app ("./blocks/csv-import" from components/)
26
+ import { CsvImport } from "@/components/makersclaw/blocks/csv-import"; // a shadcn project
27
+ ```
28
+
29
+ It is a client component. `makersclaw add csv-import` adds the
30
+ spreadsheet block (it requires it) and `papaparse` (pinned, exact): run
31
+ `npm install` after.
32
+
33
+ ## The data
34
+
35
+ `fields` are the spreadsheet's columns — the same shape, the same types
36
+ (its README, "The data"):
37
+
38
+ ```js
39
+ const FIELDS = [
40
+ { key: "name", name: "Name", type: "text", required: true },
41
+ { key: "email", name: "Email", type: "text", required: true },
42
+ { key: "stage", name: "Stage", type: "select", options: [{ value: "lead", label: "Lead", hue: 2 }] },
43
+ { key: "mrr", name: "MRR", type: "currency", precision: 0 },
44
+ { key: "owner", name: "Owner", type: "person" }, // matched by name, first name, email or id in `people`
45
+ { key: "since", name: "Customer since", type: "date" },
46
+ ];
47
+ ```
48
+
49
+ `onImport` receives `{ rows, columns, skipped }`:
50
+
51
+ - `rows` — typed values keyed by field (`{ id, name: "Ada", mrr: 1250,
52
+ since: "2025-03-14", … }`). The `id` is a temporary one ("import-3");
53
+ drop it and let the database make its own.
54
+ - `columns` — the fields the rows use. Into existing fields, a select
55
+ value that was not an option yet ("Trial") arrives as a new option on
56
+ its field (`hue` set): save the options with the rows. As a new table,
57
+ these are the new table's fields.
58
+ - `skipped` — how many rows with problems were left out.
59
+
60
+ ## Props
61
+
62
+ - `onImport({ rows, columns, skipped })` — required; async, answers
63
+ `{ ok: true }` (optionally `imported`: the count you actually saved) or
64
+ `{ ok: false, error }`. A failure keeps everything as it was and shows
65
+ the error; the person can try again.
66
+ - `fields` — the table to import into. Leave it out to make a new table.
67
+ Read-only and computed fields are not offered.
68
+ - `people` — `[{ id, name, email? }]`, for `person` fields.
69
+ - `noun` — what a row is (`"contact"`): "Import 8 contacts". Default
70
+ `"row"`.
71
+ - `title` — the heading (default "Import contacts from a CSV");
72
+ `description` — the line under it.
73
+ - `maxRows` — the most rows one file may hold (`50000`).
74
+ - `example` — `{ key: value }` for the template's example line (with
75
+ `fields`, "Download a template" writes the field names and this line).
76
+ `templateName` — the template's file name (`"contacts-template.csv"`).
77
+ - `initialText` — CSV text to start from (skips the upload step);
78
+ `initialName` — what to call it (`"Pasted text"`).
79
+ - `onCancel` — shows Cancel on the first step.
80
+ - `onDone` — the success screen's main button (`doneLabel`, default
81
+ "View contacts"); without it only "Import another file" shows.
82
+ - `locale` — for amounts and dates; `today` — `"YYYY-MM-DD"` for the date
83
+ picker.
84
+
85
+ The helpers, for a flow of your own: `parseCsvText(text)` → `{ data,
86
+ delimiter, errors }` · `readCsvFile(file)` · `headersOf(data, hasHeader)` ·
87
+ `autoMatch(headers, fields)` → a field key (or null) per column ·
88
+ `guessColumns(headers, body)` → new fields with types and options ·
89
+ `buildRows(body, mapping, fields, { ctx })` → `{ rows, problems,
90
+ creates, fields }` (each row's `__problems` is `{ key: message }`) ·
91
+ `cleanRow(row)` · `templateCsv(fields, example)`.
92
+
93
+ ## Example
94
+
95
+ ```jsx
96
+ "use client";
97
+ import { useRouter } from "next/navigation";
98
+ import { insertRow } from "../lib/actions";
99
+ import { CsvImport } from "./blocks/csv-import";
100
+
101
+ export default function ImportContacts({ fields, team }) {
102
+ const router = useRouter();
103
+ return (
104
+ <CsvImport
105
+ fields={fields}
106
+ people={team}
107
+ noun="contact"
108
+ onImport={async ({ rows }) => {
109
+ let imported = 0;
110
+ for (const { id, ...values } of rows) {
111
+ const r = await insertRow({ source: "public.contacts", values });
112
+ if (!r.ok) return { ok: false, error: `Stopped after ${imported} contacts: ${r.error}` };
113
+ imported++;
114
+ }
115
+ return { ok: true, imported };
116
+ }}
117
+ onDone={() => router.push("/contacts")}
118
+ />
119
+ );
120
+ }
121
+ ```
122
+
123
+ ## What it will not do
124
+
125
+ - Excel files (.xlsx) are not read: save the sheet as CSV first (the
126
+ upload step says so when one is dropped).
127
+ - No merging with rows already in the table (update by email, skip
128
+ duplicates): every imported row is new. Match on your side in
129
+ `onImport` if you need it.
130
+ - One file at a time; nothing is uploaded or kept — the rows go to
131
+ `onImport` and nowhere else.
132
+ - The review can fix cells and delete rows, not add rows or change the
133
+ target fields (as a new table, it can rename and retype them).
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "csv-import",
3
+ "title": "CsvImport",
4
+ "description": "Import a CSV in three calm steps: drop a file (or paste text, or download a template), match each file column to a field with automatic matches and sample values (or, with no fields, name and type a new table's fields from guessed types), then review every row in an editable spreadsheet where cells that do not fit their type are marked red and fixed in place, delete rows or leave the bad ones out, and import through one async onImport that answers { ok }. Quoted commas and newlines, TSV and semicolon files, byte-order marks, 50,000 rows.",
5
+ "useWhen": "People bring records in from a spreadsheet or another tool: contacts, leads, products, a price list, an old CRM export, a first table for a new app. Built on spreadsheet. Not for uploading files to keep (file-upload) or for editing records already in the app (spreadsheet, data-table).",
6
+ "tags": ["csv", "import", "upload", "tsv", "excel", "spreadsheet", "map columns", "field mapping", "validate", "bulk", "papaparse", "migration", "onboarding", "template"],
7
+ "exports": ["CsvImport", "parseCsvText", "readCsvFile", "headersOf", "autoMatch", "guessColumns", "buildRows", "cleanRow", "templateCsv"],
8
+ "files": ["index.js", "csv-import.jsx", "import.js"],
9
+ "requires": ["spreadsheet"],
10
+ "dependencies": {
11
+ "papaparse": "5.7.0"
12
+ }
13
+ }
@@ -0,0 +1,479 @@
1
+ "use client";
2
+
3
+ // CsvImport: a CSV in, typed rows out, in three steps — Upload, Match
4
+ // columns, Review — then one call to onImport. Nothing is saved before
5
+ // that call. The review is a Spreadsheet: a cell that did not fit its
6
+ // field keeps its text and a red corner, and is fixed in place.
7
+ import { useEffect, useMemo, useRef, useState } from "react";
8
+ import { Button, Card, FileDrop, Icon, Input, Notice, Select, Skeleton, Steps, Switch, Textarea, ToggleGroup, cx } from "../../ui";
9
+ import { Spreadsheet, TYPE_ORDER, typeOf } from "../spreadsheet";
10
+ import { autoMatch, buildRows, cleanRow, guessColumns, headersOf, parseCsvText, readCsvFile, templateCsv } from "./import";
11
+
12
+ const STEPS = ["Upload", "Match columns", "Review"];
13
+ const ACCEPT = ".csv,.tsv,.txt,text/csv,text/tab-separated-values,text/plain";
14
+
15
+ const plural = (n, noun) => `${n.toLocaleString()} ${n === 1 ? noun : `${noun}s`}`;
16
+
17
+ // What the review grid marks: the text that did not fit (kept on the row).
18
+ const problemOf = (row, col) => row.__problems?.[col.key] ?? null;
19
+
20
+ function hasProblem(row, cols) {
21
+ if (row.__problems && Object.keys(row.__problems).length) return true;
22
+ return cols.some((c) => c.required && typeOf(c).empty(row[c.key]));
23
+ }
24
+
25
+ export function CsvImport({
26
+ fields,
27
+ onImport,
28
+ people,
29
+ noun = "row",
30
+ title,
31
+ description,
32
+ maxRows = 50000,
33
+ example,
34
+ templateName,
35
+ onCancel,
36
+ onDone,
37
+ doneLabel,
38
+ locale,
39
+ today,
40
+ initialText,
41
+ initialName = "Pasted text",
42
+ className,
43
+ }) {
44
+ const intoTable = Array.isArray(fields) && fields.length > 0;
45
+ const ctx = useMemo(() => ({ people: people ?? [], locale }), [people, locale]);
46
+
47
+ const [step, setStep] = useState("upload");
48
+ const [error, setError] = useState(null);
49
+ const [reading, setReading] = useState(false);
50
+ const [pasting, setPasting] = useState(false);
51
+ const [pasted, setPasted] = useState("");
52
+ const [source, setSource] = useState(null); // { name, data }
53
+ const [hasHeader, setHasHeader] = useState(true);
54
+ const [mapping, setMapping] = useState([]); // file column → field key | null
55
+ const [newCols, setNewCols] = useState([]); // a new table's fields, one per file column (type "" = skip)
56
+ const [rows, setRows] = useState([]);
57
+ const [cols, setCols] = useState([]);
58
+ const [only, setOnly] = useState("all");
59
+ const [skipBad, setSkipBad] = useState(false);
60
+ const [busy, setBusy] = useState(false);
61
+ const [result, setResult] = useState(null);
62
+
63
+ const headers = useMemo(() => (source ? headersOf(source.data, hasHeader) : []), [source, hasHeader]);
64
+ const body = useMemo(() => (source ? source.data.slice(hasHeader ? 1 : 0) : []), [source, hasHeader]);
65
+
66
+ // ── step 1: a file (or pasted text) ──────────────────────────────────
67
+ function take(name, parsed) {
68
+ if (parsed.errors.length && !parsed.data.length) {
69
+ setError("We couldn’t read that file. Save it as CSV (comma separated) and try again.");
70
+ return;
71
+ }
72
+ if (parsed.data.length === 0 || (parsed.data.length === 1 && parsed.data[0].every((c) => !c.trim()))) {
73
+ setError("That file is empty. Check it has a row of column names and at least one row under it.");
74
+ return;
75
+ }
76
+ if (parsed.data.length - 1 > maxRows) {
77
+ setError(`That file has ${parsed.data.length.toLocaleString()} rows; this import takes up to ${maxRows.toLocaleString()}. Split it and import each part.`);
78
+ return;
79
+ }
80
+ const hs = headersOf(parsed.data, true);
81
+ setError(null);
82
+ setSource({ name, data: parsed.data });
83
+ setHasHeader(true);
84
+ setMapping(intoTable ? autoMatch(hs, fields) : hs.map(() => null));
85
+ setNewCols(intoTable ? [] : guessColumns(hs, parsed.data.slice(1)));
86
+ setStep("match");
87
+ }
88
+
89
+ // Text handed in (a paste the host already has) skips the upload step.
90
+ const started = useRef(false);
91
+ useEffect(() => {
92
+ if (!initialText || started.current) return;
93
+ started.current = true;
94
+ take(initialName, parseCsvText(initialText));
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ }, [initialText]);
97
+
98
+ async function onFiles([file]) {
99
+ if (!file) return;
100
+ setReading(true);
101
+ setError(null);
102
+ try {
103
+ take(file.name, await readCsvFile(file));
104
+ } catch {
105
+ setError("We couldn’t open that file. Try saving it again as CSV.");
106
+ } finally {
107
+ setReading(false);
108
+ }
109
+ }
110
+
111
+ function onHeaderToggle(on) {
112
+ setHasHeader(on);
113
+ if (!source) return;
114
+ const hs = headersOf(source.data, on);
115
+ const rest = source.data.slice(on ? 1 : 0);
116
+ if (intoTable) setMapping(on ? autoMatch(hs, fields) : hs.map((_, i) => mapping[i] ?? null));
117
+ else setNewCols(guessColumns(hs, rest));
118
+ }
119
+
120
+ function downloadTemplate() {
121
+ const blob = new Blob([templateCsv(fields, example)], { type: "text/csv" });
122
+ const url = URL.createObjectURL(blob);
123
+ const a = document.createElement("a");
124
+ a.href = url;
125
+ a.download = templateName ?? `${noun}s-template.csv`;
126
+ a.click();
127
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
128
+ }
129
+
130
+ // ── step 2: which column fills which field ───────────────────────────
131
+ const targetFields = intoTable ? fields.filter((f) => !f.readOnly && !f.computed) : [];
132
+ const used = new Set(mapping.filter(Boolean));
133
+ const missing = intoTable ? targetFields.filter((f) => f.required && !used.has(f.key)) : [];
134
+ const importedCount = intoTable ? mapping.filter(Boolean).length : newCols.filter((c) => c.type).length;
135
+ const namesClash = !intoTable && new Set(newCols.filter((c) => c.type).map((c) => c.name.trim().toLowerCase())).size !== newCols.filter((c) => c.type).length;
136
+ const blankName = !intoTable && newCols.some((c) => c.type && !c.name.trim());
137
+
138
+ function setTarget(i, key) {
139
+ setMapping((m) => m.map((k, j) => (j === i ? key || null : key && k === key ? null : k)));
140
+ }
141
+
142
+ function toReview() {
143
+ const targetCols = intoTable ? fields : newCols.filter((c) => c.type).map((c) => ({ ...c, name: c.name.trim() }));
144
+ const map = intoTable ? mapping : newCols.map((c) => (c.type ? c.key : null));
145
+ const built = buildRows(body, map, targetCols, { ctx });
146
+ const shown = intoTable ? built.fields.filter((f) => map.includes(f.key) || f.required) : built.fields;
147
+ setRows(built.rows);
148
+ setCols(shown);
149
+ setOnly(built.problems ? "problems" : "all");
150
+ setSkipBad(false);
151
+ setResult(null);
152
+ setError(null);
153
+ setStep("review");
154
+ }
155
+
156
+ // ── step 3: fix, then import ─────────────────────────────────────────
157
+ const bad = useMemo(() => rows.filter((r) => hasProblem(r, cols)), [rows, cols]);
158
+ const toImport = useMemo(() => (skipBad ? rows.filter((r) => !hasProblem(r, cols)) : rows), [rows, cols, skipBad]);
159
+ const blocked = !skipBad && bad.length > 0;
160
+ const showBad = only === "problems" && bad.length > 0;
161
+ const shownRows = showBad ? bad : rows;
162
+
163
+ async function runImport() {
164
+ setBusy(true);
165
+ setError(null);
166
+ let res;
167
+ try {
168
+ res = (await onImport?.({ rows: toImport.map(cleanRow), columns: cols, skipped: rows.length - toImport.length })) ?? { ok: true };
169
+ } catch (e) {
170
+ res = { ok: false, error: e?.message };
171
+ }
172
+ setBusy(false);
173
+ if (!res.ok) {
174
+ setError(res.error ?? "The import did not go through. Nothing was saved — try again.");
175
+ return;
176
+ }
177
+ setResult({ imported: res.imported ?? toImport.length, skipped: rows.length - toImport.length });
178
+ setStep("done");
179
+ }
180
+
181
+ function restart() {
182
+ setStep("upload");
183
+ setSource(null);
184
+ setRows([]);
185
+ setCols([]);
186
+ setPasted("");
187
+ setPasting(false);
188
+ setError(null);
189
+ setResult(null);
190
+ }
191
+
192
+ const stepIndex = STEPS.indexOf(step === "upload" ? "Upload" : step === "match" ? "Match columns" : "Review");
193
+ const heading = title ?? `Import ${noun}s from a CSV`;
194
+
195
+ // ── drawing ──────────────────────────────────────────────────────────
196
+ if (step === "done" && result) {
197
+ return (
198
+ <Card className={cx("mx-auto w-full max-w-xl", className)}>
199
+ <div className="grid justify-items-center gap-3 py-8 text-center">
200
+ <span className="grid size-12 place-items-center rounded-full bg-success/15 text-success">
201
+ <Icon name="check" size={24} />
202
+ </span>
203
+ <h3 className="text-lg font-semibold">{plural(result.imported, noun)} imported</h3>
204
+ <p className="max-w-sm text-sm text-muted-foreground">
205
+ {result.skipped ? `${plural(result.skipped, "row")} with problems ${result.skipped === 1 ? "was" : "were"} left out.` : `Every row in ${source?.name ?? "the file"} came in.`}
206
+ </p>
207
+ <div className="mt-2 flex flex-wrap justify-center gap-2">
208
+ <Button onClick={restart}>Import another file</Button>
209
+ {onDone ? (
210
+ <Button variant="primary" onClick={onDone}>
211
+ {doneLabel ?? `View ${noun}s`}
212
+ </Button>
213
+ ) : null}
214
+ </div>
215
+ </div>
216
+ </Card>
217
+ );
218
+ }
219
+
220
+ return (
221
+ <div className={cx("grid min-w-0 gap-5", className)}>
222
+ <div className="flex flex-wrap items-start justify-between gap-4">
223
+ <div className="grid gap-1">
224
+ <h2 className="text-lg font-semibold">{heading}</h2>
225
+ <p className="max-w-prose text-sm text-muted-foreground">
226
+ {description ?? "Bring rows in from a spreadsheet or another tool. Nothing is saved until you press Import."}
227
+ </p>
228
+ </div>
229
+ <Steps steps={STEPS} current={stepIndex} size="sm" label="Import progress" hideLabel className="w-full max-w-xs" />
230
+ </div>
231
+
232
+ {error ? (
233
+ <Notice tone="danger" title="That didn’t work" onDismiss={() => setError(null)}>
234
+ {error}
235
+ </Notice>
236
+ ) : null}
237
+
238
+ {step === "upload" ? (
239
+ <div className="grid gap-3">
240
+ {reading ? (
241
+ <div className="grid gap-3 rounded-lg border border-dashed border-border p-8" aria-busy="true">
242
+ <p className="text-sm text-muted-foreground">Reading the file…</p>
243
+ <Skeleton className="h-4 w-3/4" />
244
+ <Skeleton className="h-4 w-1/2" />
245
+ <Skeleton className="h-4 w-2/3" />
246
+ </div>
247
+ ) : pasting ? (
248
+ <div className="grid gap-3">
249
+ <Textarea
250
+ rows={8}
251
+ aria-label="CSV text"
252
+ placeholder={intoTable ? `${fields.map((f) => f.name).join(",")}\n…` : "Name,Email,Amount\nAda Lovelace,ada@example.com,1200"}
253
+ value={pasted}
254
+ onChange={(e) => setPasted(e.target.value)}
255
+ className="font-mono text-xs"
256
+ />
257
+ <div className="flex flex-wrap items-center justify-between gap-2">
258
+ <Button variant="quiet" onClick={() => setPasting(false)}>
259
+ Upload a file instead
260
+ </Button>
261
+ <Button variant="primary" disabled={!pasted.trim()} onClick={() => take("Pasted text", parseCsvText(pasted))}>
262
+ Continue
263
+ </Button>
264
+ </div>
265
+ </div>
266
+ ) : (
267
+ <FileDrop
268
+ accept={ACCEPT}
269
+ onFiles={onFiles}
270
+ onReject={() => setError("That is not a CSV file. Export the sheet as CSV (File → Download → CSV) and drop that.")}
271
+ label="Drop a CSV file here, or browse"
272
+ hint={`CSV or TSV, up to ${maxRows.toLocaleString()} rows. The first row should name the columns.`}
273
+ />
274
+ )}
275
+ {!reading && !pasting ? (
276
+ <div className="flex flex-wrap items-center justify-between gap-2 text-sm">
277
+ <Button variant="link" size="sm" tone="muted" onClick={() => setPasting(true)}>
278
+ Paste the text instead
279
+ </Button>
280
+ <div className="flex items-center gap-3">
281
+ {intoTable ? (
282
+ <Button variant="link" size="sm" tone="muted" icon="download" onClick={downloadTemplate}>
283
+ Download a template
284
+ </Button>
285
+ ) : null}
286
+ {onCancel ? (
287
+ <Button variant="quiet" onClick={onCancel}>
288
+ Cancel
289
+ </Button>
290
+ ) : null}
291
+ </div>
292
+ </div>
293
+ ) : null}
294
+ </div>
295
+ ) : null}
296
+
297
+ {step === "match" && source ? (
298
+ <div className="grid gap-4">
299
+ <div className="flex flex-wrap items-center justify-between gap-3">
300
+ <p className="flex min-w-0 items-center gap-2 text-sm">
301
+ <Icon name="file" className="shrink-0 text-muted-foreground" />
302
+ <span className="truncate font-medium">{source.name}</span>
303
+ <span className="shrink-0 text-muted-foreground">· {plural(body.length, "row")}</span>
304
+ </p>
305
+ <Switch id="csv-has-header" label="First row names the columns" checked={hasHeader} onChange={(e) => onHeaderToggle(e.target.checked)} />
306
+ </div>
307
+ {missing.length ? (
308
+ <Notice tone="warning" title={missing.length === 1 ? `${missing[0].name} is required` : "Some fields are required"}>
309
+ Pick the column that holds {missing.map((f) => f.name).join(", ")}.
310
+ </Notice>
311
+ ) : null}
312
+ <div className="overflow-hidden rounded-lg border border-border">
313
+ <div className="hidden grid-cols-[minmax(0,1fr)_1.5rem_minmax(0,18rem)] gap-3 border-b border-border bg-muted px-4 py-2 text-xs font-medium text-muted-foreground sm:grid">
314
+ <span>In your file</span>
315
+ <span />
316
+ <span>{intoTable ? "Goes to" : "Becomes the field"}</span>
317
+ </div>
318
+ <ul className="divide-y divide-border">
319
+ {headers.map((h, i) => {
320
+ const samples = body
321
+ .slice(0, 40)
322
+ .map((r) => (r[i] ?? "").trim())
323
+ .filter(Boolean)
324
+ .slice(0, 3);
325
+ const target = intoTable ? mapping[i] : newCols[i]?.type ? newCols[i].key : null;
326
+ return (
327
+ <li key={i} className="grid gap-2 px-4 py-3 sm:grid-cols-[minmax(0,1fr)_1.5rem_minmax(0,18rem)] sm:items-center sm:gap-3">
328
+ <div className="min-w-0">
329
+ <p className="truncate text-sm font-medium">{h}</p>
330
+ <p className="truncate text-xs text-muted-foreground">{samples.length ? samples.join(" · ") : "Empty in the first rows"}</p>
331
+ </div>
332
+ <Icon name="arrow-right" className={cx("hidden sm:block", target ? "text-primary" : "text-muted-foreground")} />
333
+ {intoTable ? (
334
+ <Select
335
+ aria-label={`Field for ${h}`}
336
+ value={mapping[i] ?? ""}
337
+ onChange={(e) => setTarget(i, e.target.value)}
338
+ options={[
339
+ { value: "", label: "Don’t import" },
340
+ ...targetFields.map((f) => ({
341
+ value: f.key,
342
+ label: `${f.name}${f.required ? " (required)" : ""}${used.has(f.key) && mapping[i] !== f.key ? " — in use" : ""}`,
343
+ })),
344
+ ]}
345
+ />
346
+ ) : (
347
+ <div className="grid grid-cols-[minmax(0,1fr)_minmax(0,9rem)] gap-2">
348
+ <Input
349
+ aria-label={`Name for ${h}`}
350
+ value={newCols[i]?.name ?? ""}
351
+ disabled={!newCols[i]?.type}
352
+ invalid={Boolean(newCols[i]?.type) && !newCols[i]?.name.trim()}
353
+ onChange={(e) => setNewCols((cs) => cs.map((c, j) => (j === i ? { ...c, name: e.target.value } : c)))}
354
+ />
355
+ <Select
356
+ aria-label={`Type for ${h}`}
357
+ value={newCols[i]?.type ?? ""}
358
+ onChange={(e) =>
359
+ setNewCols((cs) =>
360
+ cs.map((c, j) => {
361
+ if (j !== i) return c;
362
+ const type = e.target.value;
363
+ if (!type) return { ...c, type: "" };
364
+ const guessed = guessColumns([c.name], body.map((r) => [r[i] ?? ""]))[0];
365
+ return { ...c, type, options: type === "select" || type === "multi-select" ? guessed.options ?? c.options ?? [] : c.options };
366
+ }),
367
+ )
368
+ }
369
+ options={[{ value: "", label: "Don’t import" }, ...TYPE_ORDER.filter((t) => t !== "person").map((t) => ({ value: t, label: typeOf({ type: t }).label }))]}
370
+ />
371
+ </div>
372
+ )}
373
+ </li>
374
+ );
375
+ })}
376
+ </ul>
377
+ </div>
378
+ {namesClash ? <p className="text-sm text-destructive">Two fields have the same name. Rename one.</p> : null}
379
+ <div className="flex flex-wrap items-center justify-between gap-2">
380
+ <Button variant="quiet" icon="arrow-left" onClick={restart}>
381
+ Choose another file
382
+ </Button>
383
+ <div className="flex items-center gap-3">
384
+ <span className="text-sm text-muted-foreground max-sm:hidden">
385
+ {importedCount} of {headers.length} columns
386
+ </span>
387
+ <Button variant="primary" disabled={!importedCount || missing.length > 0 || namesClash || blankName || !body.length} onClick={toReview}>
388
+ {body.length ? `Review ${plural(body.length, "row")}` : "No rows to review"}
389
+ </Button>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ ) : null}
394
+
395
+ {step === "review" ? (
396
+ <div className="grid gap-4">
397
+ <div className="flex flex-wrap items-center justify-between gap-3">
398
+ <p className="text-sm">
399
+ <span className="font-medium tabular-nums">{plural(rows.length - bad.length, noun)} ready</span>
400
+ {bad.length ? <span className="text-muted-foreground"> · {plural(bad.length, "row")} to fix</span> : null}
401
+ </p>
402
+ {bad.length ? (
403
+ <ToggleGroup
404
+ size="sm"
405
+ aria-label="Show"
406
+ value={showBad ? "problems" : "all"}
407
+ onChange={setOnly}
408
+ items={[
409
+ { value: "problems", label: `Needs fixing (${bad.length.toLocaleString()})` },
410
+ { value: "all", label: "All rows" },
411
+ ]}
412
+ />
413
+ ) : null}
414
+ </div>
415
+ {bad.length ? (
416
+ <Notice tone="warning">
417
+ Some cells don’t fit their field — a red corner marks each one. Click it and type the right value, delete the row, or leave those rows out.
418
+ </Notice>
419
+ ) : null}
420
+ <Spreadsheet
421
+ label="Rows to import"
422
+ noun={noun}
423
+ toolbar={false}
424
+ height={Math.min(440, 44 + Math.max(4, shownRows.length + 1) * 40)}
425
+ rows={shownRows}
426
+ columns={cols}
427
+ people={people}
428
+ locale={locale}
429
+ today={today}
430
+ validate={problemOf}
431
+ defaultView={{ frozen: 1 }}
432
+ onChange={(changes) => {
433
+ setRows((rs) => {
434
+ const by = new Map();
435
+ for (const c of changes) by.set(c.rowId, [...(by.get(c.rowId) ?? []), c]);
436
+ return rs.map((r) => {
437
+ const list = by.get(r.id);
438
+ if (!list) return r;
439
+ const next = { ...r, __problems: { ...(r.__problems ?? {}) } };
440
+ for (const c of list) {
441
+ next[c.key] = c.value;
442
+ delete next.__problems[c.key];
443
+ }
444
+ if (!Object.keys(next.__problems).length) delete next.__problems;
445
+ return next;
446
+ });
447
+ });
448
+ return { ok: true };
449
+ }}
450
+ onDeleteRows={(ids) => {
451
+ const gone = new Set(ids);
452
+ setRows((rs) => rs.filter((r) => !gone.has(r.id)));
453
+ return { ok: true };
454
+ }}
455
+ lockFields={intoTable}
456
+ onColumnsChange={(next) => {
457
+ setCols(next);
458
+ return { ok: true };
459
+ }}
460
+ empty={<p className="text-sm text-muted-foreground">No rows left to import. Go back and choose another file.</p>}
461
+ />
462
+ <div className="flex flex-wrap items-center justify-between gap-3">
463
+ <Button variant="quiet" icon="arrow-left" onClick={() => setStep("match")}>
464
+ Back to matching
465
+ </Button>
466
+ <div className="flex flex-wrap items-center gap-3">
467
+ {bad.length ? <Switch id="csv-skip-bad" label={`Leave out ${plural(bad.length, "row")} to fix`} checked={skipBad} onChange={(e) => setSkipBad(e.target.checked)} /> : null}
468
+ <Button variant="primary" loading={busy} disabled={blocked || toImport.length === 0} onClick={runImport}>
469
+ {toImport.length ? `Import ${plural(toImport.length, noun)}` : "Nothing to import"}
470
+ </Button>
471
+ </div>
472
+ </div>
473
+ {blocked ? <p className="text-right text-xs text-muted-foreground">Fix the rows marked in red, or leave them out, to import.</p> : null}
474
+ </div>
475
+ ) : null}
476
+ </div>
477
+ );
478
+ }
479
+