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.
- package/LICENSE +21 -0
- package/README.md +41 -1
- package/THIRD-PARTY-NOTICES.md +180 -0
- package/package.json +15 -8
- package/r/annotate.json +62 -0
- package/r/approval-request.json +52 -0
- package/r/audio-player.json +74 -0
- package/r/auth-form.json +55 -0
- package/r/board.json +55 -0
- package/r/calendar-view.json +105 -0
- package/r/cart.json +62 -0
- package/r/chart.json +83 -0
- package/r/chat.json +95 -0
- package/r/code-editor.json +94 -0
- package/r/comment-box.json +64 -0
- package/r/contact-section.json +53 -0
- package/r/core.json +341 -0
- package/r/csv-import.json +63 -0
- package/r/data-table.json +69 -0
- package/r/diagram.json +74 -0
- package/r/doc-editor.json +82 -0
- package/r/doc-page.json +61 -0
- package/r/email-render.json +73 -0
- package/r/faq.json +50 -0
- package/r/file-upload.json +54 -0
- package/r/flow-canvas.json +93 -0
- package/r/footer.json +52 -0
- package/r/gantt.json +70 -0
- package/r/job-panel.json +56 -0
- package/r/json-editor.json +66 -0
- package/r/map.json +89 -0
- package/r/menubar.json +52 -0
- package/r/mind-map.json +67 -0
- package/r/navigation-menu.json +52 -0
- package/r/order-detail.json +54 -0
- package/r/org-chart.json +66 -0
- package/r/page-builder.json +105 -0
- package/r/payment-methods.json +51 -0
- package/r/pdf-document.json +79 -0
- package/r/pdf-viewer.json +88 -0
- package/r/product-detail.json +76 -0
- package/r/product-grid.json +65 -0
- package/r/record-form.json +67 -0
- package/r/record-notes.json +55 -0
- package/r/registry.json +3612 -0
- package/r/search-dialog.json +51 -0
- package/r/settings-list.json +66 -0
- package/r/setup-checklist.json +51 -0
- package/r/share-dialog.json +52 -0
- package/r/sidebar.json +69 -0
- package/r/signature-pad.json +72 -0
- package/r/sketch-canvas.json +99 -0
- package/r/spreadsheet.json +90 -0
- package/r/stat-cards.json +66 -0
- package/r/task-list.json +63 -0
- package/r/task-model.json +98 -0
- package/r/testimonials.json +52 -0
- package/r/transaction-list.json +53 -0
- package/r/whiteboard.json +66 -0
- package/registry/blocks/annotate/README.md +179 -0
- package/registry/blocks/annotate/annotate-data.js +250 -0
- package/registry/blocks/annotate/annotate.jsx +646 -0
- package/registry/blocks/annotate/block.json +10 -0
- package/registry/blocks/annotate/demo.jsx +135 -0
- package/registry/blocks/annotate/index.js +15 -0
- package/registry/blocks/approval-request/README.md +116 -0
- package/registry/blocks/approval-request/approval-request.jsx +386 -0
- package/registry/blocks/approval-request/block.json +10 -0
- package/registry/blocks/approval-request/demo.jsx +127 -0
- package/registry/blocks/approval-request/index.js +4 -0
- package/registry/blocks/audio-player/README.md +167 -0
- package/registry/blocks/audio-player/audio-player.jsx +623 -0
- package/registry/blocks/audio-player/block.json +14 -0
- package/registry/blocks/audio-player/colors.js +109 -0
- package/registry/blocks/audio-player/demo.jsx +209 -0
- package/registry/blocks/audio-player/index.js +6 -0
- package/registry/blocks/audio-player/time.js +67 -0
- package/registry/blocks/audio-player/wavesurfer.js +9 -0
- package/registry/blocks/auth-form/README.md +126 -0
- package/registry/blocks/auth-form/auth-form.jsx +568 -0
- package/registry/blocks/auth-form/block.json +10 -0
- package/registry/blocks/auth-form/demo.jsx +131 -0
- package/registry/blocks/auth-form/index.js +3 -0
- package/registry/blocks/board/README.md +64 -13
- package/registry/blocks/board/block.json +2 -2
- package/registry/blocks/board/board.jsx +237 -94
- package/registry/blocks/board/demo.jsx +103 -24
- package/registry/blocks/calendar-view/README.md +180 -0
- package/registry/blocks/calendar-view/agenda.jsx +147 -0
- package/registry/blocks/calendar-view/block.json +76 -0
- package/registry/blocks/calendar-view/calendar-model.js +442 -0
- package/registry/blocks/calendar-view/calendar.jsx +627 -0
- package/registry/blocks/calendar-view/demo.jsx +195 -0
- package/registry/blocks/calendar-view/draft.jsx +49 -0
- package/registry/blocks/calendar-view/index.js +39 -0
- package/registry/blocks/calendar-view/month.jsx +346 -0
- package/registry/blocks/calendar-view/paint.js +69 -0
- package/registry/blocks/calendar-view/time-grid.jsx +527 -0
- package/registry/blocks/calendar-view/tray.jsx +87 -0
- package/registry/blocks/cart/README.md +146 -0
- package/registry/blocks/cart/block.json +10 -0
- package/registry/blocks/cart/cart.jsx +597 -0
- package/registry/blocks/cart/demo.jsx +98 -0
- package/registry/blocks/cart/index.js +4 -0
- package/registry/blocks/cart/money.js +28 -0
- package/registry/blocks/chart/README.md +316 -0
- package/registry/blocks/chart/block.json +10 -0
- package/registry/blocks/chart/cartesian.jsx +642 -0
- package/registry/blocks/chart/core.jsx +262 -0
- package/registry/blocks/chart/demo.jsx +357 -0
- package/registry/blocks/chart/heatmap.jsx +336 -0
- package/registry/blocks/chart/index.js +8 -0
- package/registry/blocks/chart/pie.jsx +245 -0
- package/registry/blocks/chart/sparkline.jsx +88 -0
- package/registry/blocks/chat/README.md +273 -0
- package/registry/blocks/chat/attachment.jsx +95 -0
- package/registry/blocks/chat/block.json +10 -0
- package/registry/blocks/chat/chat.jsx +132 -0
- package/registry/blocks/chat/composer.jsx +364 -0
- package/registry/blocks/chat/demo.jsx +578 -0
- package/registry/blocks/chat/header.jsx +34 -0
- package/registry/blocks/chat/index.js +9 -0
- package/registry/blocks/chat/message.jsx +450 -0
- package/registry/blocks/chat/scroller.jsx +247 -0
- package/registry/blocks/chat/text.jsx +104 -0
- package/registry/blocks/code-editor/README.md +210 -0
- package/registry/blocks/code-editor/block.json +22 -0
- package/registry/blocks/code-editor/code-editor.jsx +534 -0
- package/registry/blocks/code-editor/codemirror.js +38 -0
- package/registry/blocks/code-editor/demo.jsx +205 -0
- package/registry/blocks/code-editor/index.js +8 -0
- package/registry/blocks/code-editor/json.js +317 -0
- package/registry/blocks/code-editor/sql.js +136 -0
- package/registry/blocks/code-editor/text.js +101 -0
- package/registry/blocks/code-editor/theme.js +186 -0
- package/registry/blocks/comment-box/README.md +123 -0
- package/registry/blocks/comment-box/block.json +10 -0
- package/registry/blocks/comment-box/comment-body.jsx +217 -0
- package/registry/blocks/comment-box/comment-box.jsx +198 -0
- package/registry/blocks/comment-box/comment-time.jsx +77 -0
- package/registry/blocks/comment-box/demo.jsx +141 -0
- package/registry/blocks/comment-box/index.js +8 -0
- package/registry/blocks/contact-section/README.md +152 -0
- package/registry/blocks/contact-section/block.json +11 -0
- package/registry/blocks/contact-section/contact-section.jsx +509 -0
- package/registry/blocks/contact-section/demo.jsx +93 -0
- package/registry/blocks/contact-section/index.js +4 -0
- package/registry/blocks/csv-import/README.md +133 -0
- package/registry/blocks/csv-import/block.json +13 -0
- package/registry/blocks/csv-import/csv-import.jsx +479 -0
- package/registry/blocks/csv-import/demo.jsx +106 -0
- package/registry/blocks/csv-import/import.js +200 -0
- package/registry/blocks/csv-import/index.js +7 -0
- package/registry/blocks/data-table/README.md +386 -0
- package/registry/blocks/data-table/block.json +10 -0
- package/registry/blocks/data-table/data-table.jsx +1164 -0
- package/registry/blocks/data-table/demo.jsx +588 -0
- package/registry/blocks/data-table/index.js +5 -0
- package/registry/blocks/data-table/model.js +169 -0
- package/registry/blocks/diagram/README.md +108 -0
- package/registry/blocks/diagram/block.json +13 -0
- package/registry/blocks/diagram/demo.jsx +113 -0
- package/registry/blocks/diagram/diagram.jsx +481 -0
- package/registry/blocks/diagram/index.js +7 -0
- package/registry/blocks/diagram/mermaid.js +52 -0
- package/registry/blocks/diagram/palette.js +313 -0
- package/registry/blocks/diagram/source.js +98 -0
- package/registry/blocks/doc-editor/README.md +215 -0
- package/registry/blocks/doc-editor/block.json +15 -0
- package/registry/blocks/doc-editor/demo.jsx +225 -0
- package/registry/blocks/doc-editor/doc-editor.jsx +291 -0
- package/registry/blocks/doc-editor/doc.js +309 -0
- package/registry/blocks/doc-editor/index.js +7 -0
- package/registry/blocks/doc-editor/schema.jsx +56 -0
- package/registry/blocks/doc-editor/shadcn-classes.js +110 -0
- package/registry/blocks/doc-editor/theme.js +82 -0
- package/registry/blocks/doc-page/README.md +119 -0
- package/registry/blocks/doc-page/autosave.js +104 -0
- package/registry/blocks/doc-page/block.json +10 -0
- package/registry/blocks/doc-page/demo.jsx +137 -0
- package/registry/blocks/doc-page/doc-page.jsx +276 -0
- package/registry/blocks/doc-page/index.js +4 -0
- package/registry/blocks/email-render/README.md +151 -0
- package/registry/blocks/email-render/block.json +14 -0
- package/registry/blocks/email-render/demo.jsx +153 -0
- package/registry/blocks/email-render/doc-email.jsx +210 -0
- package/registry/blocks/email-render/email.jsx +221 -0
- package/registry/blocks/email-render/index.js +10 -0
- package/registry/blocks/email-render/render.js +50 -0
- package/registry/blocks/email-render/theme.js +215 -0
- package/registry/blocks/faq/README.md +101 -0
- package/registry/blocks/faq/block.json +11 -0
- package/registry/blocks/faq/demo.jsx +89 -0
- package/registry/blocks/faq/faq.jsx +220 -0
- package/registry/blocks/faq/index.js +4 -0
- package/registry/blocks/file-upload/README.md +138 -0
- package/registry/blocks/file-upload/block.json +10 -0
- package/registry/blocks/file-upload/demo.jsx +204 -0
- package/registry/blocks/file-upload/file-upload.jsx +635 -0
- package/registry/blocks/file-upload/index.js +4 -0
- package/registry/blocks/flow-canvas/README.md +420 -0
- package/registry/blocks/flow-canvas/block.json +13 -0
- package/registry/blocks/flow-canvas/board.js +819 -0
- package/registry/blocks/flow-canvas/chrome.jsx +649 -0
- package/registry/blocks/flow-canvas/context.jsx +35 -0
- package/registry/blocks/flow-canvas/demo.jsx +249 -0
- package/registry/blocks/flow-canvas/edges.jsx +107 -0
- package/registry/blocks/flow-canvas/flow-canvas.jsx +1329 -0
- package/registry/blocks/flow-canvas/index.js +41 -0
- package/registry/blocks/flow-canvas/nodes.jsx +392 -0
- package/registry/blocks/flow-canvas/theme.js +174 -0
- package/registry/blocks/footer/README.md +133 -0
- package/registry/blocks/footer/block.json +11 -0
- package/registry/blocks/footer/demo.jsx +119 -0
- package/registry/blocks/footer/footer.jsx +406 -0
- package/registry/blocks/footer/index.js +4 -0
- package/registry/blocks/gantt/README.md +220 -0
- package/registry/blocks/gantt/block.json +10 -0
- package/registry/blocks/gantt/demo.jsx +148 -0
- package/registry/blocks/gantt/gantt.jsx +923 -0
- package/registry/blocks/gantt/index.js +7 -0
- package/registry/blocks/gantt/rows.jsx +468 -0
- package/registry/blocks/gantt/schedule.js +278 -0
- package/registry/blocks/job-panel/README.md +86 -2
- package/registry/blocks/job-panel/block.json +3 -3
- package/registry/blocks/job-panel/demo.jsx +230 -3
- package/registry/blocks/job-panel/job-panel.jsx +169 -3
- package/registry/blocks/json-editor/README.md +133 -0
- package/registry/blocks/json-editor/block.json +10 -0
- package/registry/blocks/json-editor/demo.jsx +92 -0
- package/registry/blocks/json-editor/index.js +7 -0
- package/registry/blocks/json-editor/json-editor.jsx +305 -0
- package/registry/blocks/json-editor/json-tree.jsx +393 -0
- package/registry/blocks/json-editor/tree.js +225 -0
- package/registry/blocks/map/README.md +221 -0
- package/registry/blocks/map/block.json +14 -0
- package/registry/blocks/map/demo.jsx +201 -0
- package/registry/blocks/map/geo.js +198 -0
- package/registry/blocks/map/index.js +8 -0
- package/registry/blocks/map/list.jsx +128 -0
- package/registry/blocks/map/map-view.jsx +715 -0
- package/registry/blocks/map/maplibre.js +30 -0
- package/registry/blocks/map/marks.jsx +146 -0
- package/registry/blocks/map/theme.js +142 -0
- package/registry/blocks/menubar/README.md +109 -0
- package/registry/blocks/menubar/block.json +10 -0
- package/registry/blocks/menubar/demo.jsx +128 -0
- package/registry/blocks/menubar/index.js +4 -0
- package/registry/blocks/menubar/menubar.jsx +199 -0
- package/registry/blocks/mind-map/README.md +206 -0
- package/registry/blocks/mind-map/block.json +10 -0
- package/registry/blocks/mind-map/demo.jsx +103 -0
- package/registry/blocks/mind-map/index.js +31 -0
- package/registry/blocks/mind-map/mind-map.jsx +1125 -0
- package/registry/blocks/mind-map/topic.jsx +143 -0
- package/registry/blocks/mind-map/tree.js +645 -0
- package/registry/blocks/navigation-menu/README.md +124 -0
- package/registry/blocks/navigation-menu/block.json +10 -0
- package/registry/blocks/navigation-menu/demo.jsx +84 -0
- package/registry/blocks/navigation-menu/index.js +4 -0
- package/registry/blocks/navigation-menu/navigation-menu.jsx +448 -0
- package/registry/blocks/order-detail/README.md +109 -0
- package/registry/blocks/order-detail/block.json +10 -0
- package/registry/blocks/order-detail/demo.jsx +88 -0
- package/registry/blocks/order-detail/index.js +4 -0
- package/registry/blocks/order-detail/order-detail.jsx +354 -0
- package/registry/blocks/org-chart/README.md +145 -0
- package/registry/blocks/org-chart/block.json +10 -0
- package/registry/blocks/org-chart/demo.jsx +94 -0
- package/registry/blocks/org-chart/index.js +7 -0
- package/registry/blocks/org-chart/org-chart.jsx +631 -0
- package/registry/blocks/org-chart/org.js +245 -0
- package/registry/blocks/org-chart/person.jsx +96 -0
- package/registry/blocks/page-builder/README.md +257 -0
- package/registry/blocks/page-builder/block.json +14 -0
- package/registry/blocks/page-builder/demo.jsx +145 -0
- package/registry/blocks/page-builder/grapes.js +18 -0
- package/registry/blocks/page-builder/index.js +11 -0
- package/registry/blocks/page-builder/page-builder.jsx +704 -0
- package/registry/blocks/page-builder/page-view.jsx +33 -0
- package/registry/blocks/page-builder/page.js +118 -0
- package/registry/blocks/page-builder/panels.jsx +588 -0
- package/registry/blocks/page-builder/sections.js +209 -0
- package/registry/blocks/page-builder/theme.js +87 -0
- package/registry/blocks/page-builder/thumbs.jsx +189 -0
- package/registry/blocks/page-builder/tokens.js +329 -0
- package/registry/blocks/payment-methods/README.md +104 -0
- package/registry/blocks/payment-methods/block.json +10 -0
- package/registry/blocks/payment-methods/demo.jsx +71 -0
- package/registry/blocks/payment-methods/index.js +4 -0
- package/registry/blocks/payment-methods/payment-methods.jsx +302 -0
- package/registry/blocks/pdf-document/README.md +213 -0
- package/registry/blocks/pdf-document/block.json +13 -0
- package/registry/blocks/pdf-document/demo.jsx +136 -0
- package/registry/blocks/pdf-document/index.js +33 -0
- package/registry/blocks/pdf-document/money.js +83 -0
- package/registry/blocks/pdf-document/pdf-document.jsx +144 -0
- package/registry/blocks/pdf-document/primitives.jsx +271 -0
- package/registry/blocks/pdf-document/quote.jsx +179 -0
- package/registry/blocks/pdf-document/theme.js +193 -0
- package/registry/blocks/pdf-viewer/README.md +191 -0
- package/registry/blocks/pdf-viewer/block.json +14 -0
- package/registry/blocks/pdf-viewer/demo.jsx +252 -0
- package/registry/blocks/pdf-viewer/helpers.js +270 -0
- package/registry/blocks/pdf-viewer/index.js +8 -0
- package/registry/blocks/pdf-viewer/notes.jsx +195 -0
- package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1368 -0
- package/registry/blocks/pdf-viewer/pdfjs.js +37 -0
- package/registry/blocks/pdf-viewer/ssr-shim-end.js +6 -0
- package/registry/blocks/pdf-viewer/ssr-shim.js +44 -0
- package/registry/blocks/product-detail/README.md +217 -0
- package/registry/blocks/product-detail/block.json +10 -0
- package/registry/blocks/product-detail/demo.jsx +233 -0
- package/registry/blocks/product-detail/index.js +7 -0
- package/registry/blocks/product-detail/price.jsx +115 -0
- package/registry/blocks/product-detail/product-detail.jsx +348 -0
- package/registry/blocks/product-detail/product-media.jsx +102 -0
- package/registry/blocks/product-detail/product-options.jsx +144 -0
- package/registry/blocks/product-grid/README.md +162 -0
- package/registry/blocks/product-grid/block.json +10 -0
- package/registry/blocks/product-grid/demo.jsx +255 -0
- package/registry/blocks/product-grid/index.js +5 -0
- package/registry/blocks/product-grid/product-card.jsx +232 -0
- package/registry/blocks/product-grid/product-grid.jsx +235 -0
- package/registry/blocks/record-form/README.md +196 -0
- package/registry/blocks/record-form/block.json +10 -0
- package/registry/blocks/record-form/demo.jsx +525 -0
- package/registry/blocks/record-form/index.js +4 -0
- package/registry/blocks/record-form/record-fields.jsx +457 -0
- package/registry/blocks/record-form/record-form.jsx +415 -0
- package/registry/blocks/record-form/rules.js +81 -0
- package/registry/blocks/record-notes/README.md +139 -0
- package/registry/blocks/record-notes/block.json +10 -0
- package/registry/blocks/record-notes/demo.jsx +179 -0
- package/registry/blocks/record-notes/index.js +4 -0
- package/registry/blocks/record-notes/record-notes.jsx +266 -0
- package/registry/blocks/search-dialog/README.md +108 -0
- package/registry/blocks/search-dialog/block.json +10 -0
- package/registry/blocks/search-dialog/demo.jsx +116 -0
- package/registry/blocks/search-dialog/index.js +3 -0
- package/registry/blocks/search-dialog/search-dialog.jsx +235 -0
- package/registry/blocks/settings-list/README.md +171 -0
- package/registry/blocks/settings-list/block.json +10 -0
- package/registry/blocks/settings-list/demo.jsx +333 -0
- package/registry/blocks/settings-list/index.js +4 -0
- package/registry/blocks/settings-list/row-state.jsx +93 -0
- package/registry/blocks/settings-list/settings-list.jsx +349 -0
- package/registry/blocks/settings-list/settings-matrix.jsx +118 -0
- package/registry/blocks/setup-checklist/README.md +142 -0
- package/registry/blocks/setup-checklist/block.json +10 -0
- package/registry/blocks/setup-checklist/demo.jsx +159 -0
- package/registry/blocks/setup-checklist/index.js +4 -0
- package/registry/blocks/setup-checklist/setup-checklist.jsx +446 -0
- package/registry/blocks/share-dialog/README.md +113 -0
- package/registry/blocks/share-dialog/block.json +10 -0
- package/registry/blocks/share-dialog/demo.jsx +140 -0
- package/registry/blocks/share-dialog/index.js +3 -0
- package/registry/blocks/share-dialog/share-dialog.jsx +384 -0
- package/registry/blocks/sidebar/README.md +190 -0
- package/registry/blocks/sidebar/block.json +43 -0
- package/registry/blocks/sidebar/demo.jsx +577 -0
- package/registry/blocks/sidebar/index.js +4 -0
- package/registry/blocks/sidebar/sidebar-panel.jsx +149 -0
- package/registry/blocks/sidebar/sidebar.jsx +864 -0
- package/registry/blocks/signature-pad/README.md +137 -0
- package/registry/blocks/signature-pad/block.json +13 -0
- package/registry/blocks/signature-pad/colors.js +115 -0
- package/registry/blocks/signature-pad/demo.jsx +94 -0
- package/registry/blocks/signature-pad/index.js +6 -0
- package/registry/blocks/signature-pad/pad.js +7 -0
- package/registry/blocks/signature-pad/signature-pad.jsx +434 -0
- package/registry/blocks/signature-pad/signature.js +68 -0
- package/registry/blocks/sketch-canvas/README.md +280 -0
- package/registry/blocks/sketch-canvas/block.json +13 -0
- package/registry/blocks/sketch-canvas/demo.jsx +159 -0
- package/registry/blocks/sketch-canvas/excalidraw.js +40 -0
- package/registry/blocks/sketch-canvas/index.js +9 -0
- package/registry/blocks/sketch-canvas/palette.js +226 -0
- package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1188 -0
- package/registry/blocks/sketch-canvas/sketch.js +179 -0
- package/registry/blocks/sketch-canvas/ssr-shim-end.js +6 -0
- package/registry/blocks/sketch-canvas/ssr-shim.js +45 -0
- package/registry/blocks/sketch-canvas/stamps.js +104 -0
- package/registry/blocks/sketch-canvas/theme.js +131 -0
- package/registry/blocks/spreadsheet/README.md +307 -0
- package/registry/blocks/spreadsheet/block.json +13 -0
- package/registry/blocks/spreadsheet/cells.jsx +455 -0
- package/registry/blocks/spreadsheet/chrome.jsx +325 -0
- package/registry/blocks/spreadsheet/demo.jsx +177 -0
- package/registry/blocks/spreadsheet/grid.js +387 -0
- package/registry/blocks/spreadsheet/index.js +34 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +1732 -0
- package/registry/blocks/spreadsheet/types.js +473 -0
- package/registry/blocks/stat-cards/README.md +173 -0
- package/registry/blocks/stat-cards/block.json +10 -0
- package/registry/blocks/stat-cards/demo.jsx +257 -0
- package/registry/blocks/stat-cards/index.js +4 -0
- package/registry/blocks/stat-cards/stat-cards.jsx +307 -0
- package/registry/blocks/stat-cards/usage.jsx +243 -0
- package/registry/blocks/task-list/README.md +176 -0
- package/registry/blocks/task-list/block.json +10 -0
- package/registry/blocks/task-list/demo.jsx +134 -0
- package/registry/blocks/task-list/index.js +5 -0
- package/registry/blocks/task-list/task-list.jsx +907 -0
- package/registry/blocks/task-model/README.md +455 -0
- package/registry/blocks/task-model/block.json +167 -0
- package/registry/blocks/task-model/changes.js +89 -0
- package/registry/blocks/task-model/dates.js +371 -0
- package/registry/blocks/task-model/demo.jsx +215 -0
- package/registry/blocks/task-model/dnd.jsx +240 -0
- package/registry/blocks/task-model/index.js +97 -0
- package/registry/blocks/task-model/model.js +919 -0
- package/registry/blocks/task-model/parts.jsx +449 -0
- package/registry/blocks/task-model/toolbar.jsx +271 -0
- package/registry/blocks/testimonials/README.md +139 -0
- package/registry/blocks/testimonials/block.json +11 -0
- package/registry/blocks/testimonials/demo.jsx +137 -0
- package/registry/blocks/testimonials/index.js +4 -0
- package/registry/blocks/testimonials/testimonials.jsx +430 -0
- package/registry/blocks/transaction-list/README.md +126 -0
- package/registry/blocks/transaction-list/block.json +10 -0
- package/registry/blocks/transaction-list/demo.jsx +79 -0
- package/registry/blocks/transaction-list/index.js +4 -0
- package/registry/blocks/transaction-list/transaction-list.jsx +383 -0
- package/registry/blocks/whiteboard/README.md +179 -0
- package/registry/blocks/whiteboard/block.json +10 -0
- package/registry/blocks/whiteboard/demo.jsx +169 -0
- package/registry/blocks/whiteboard/index.js +6 -0
- package/registry/blocks/whiteboard/templates.js +150 -0
- package/registry/blocks/whiteboard/whiteboard.jsx +721 -0
- package/src/install.mjs +18 -0
- package/src/shadcn.mjs +171 -0
- package/templates/next/files/README.md +145 -11
- package/templates/next/files/app/globals.css +55 -4
- package/templates/next/files/check/run.mjs +3 -3
- package/templates/next/files/components/parts/fixture.jsx +1 -1
- package/templates/next/files/components/parts/sample.js +15 -0
- package/templates/next/files/components/ui/avatar.jsx +141 -0
- package/templates/next/files/components/ui/breadcrumb.jsx +149 -0
- package/templates/next/files/components/ui/button-group.jsx +70 -0
- package/templates/next/files/components/ui/button.jsx +132 -12
- package/templates/next/files/components/ui/calendar.jsx +609 -0
- package/templates/next/files/components/ui/carousel.jsx +281 -0
- package/templates/next/files/components/ui/collapsible.jsx +208 -0
- package/templates/next/files/components/ui/combobox.jsx +608 -0
- package/templates/next/files/components/ui/command.jsx +577 -0
- package/templates/next/files/components/ui/context-menu.jsx +191 -0
- package/templates/next/files/components/ui/date-picker.jsx +360 -0
- package/templates/next/files/components/ui/distribution.jsx +117 -0
- package/templates/next/files/components/ui/feedback.jsx +143 -0
- package/templates/next/files/components/ui/field.jsx +115 -3
- package/templates/next/files/components/ui/file-input.jsx +234 -0
- package/templates/next/files/components/ui/icon.jsx +130 -8
- package/templates/next/files/components/ui/image.jsx +143 -0
- package/templates/next/files/components/ui/index.js +47 -2
- package/templates/next/files/components/ui/input-group.jsx +213 -0
- package/templates/next/files/components/ui/input-otp.jsx +170 -0
- package/templates/next/files/components/ui/item.jsx +206 -0
- package/templates/next/files/components/ui/menu.jsx +454 -82
- package/templates/next/files/components/ui/notice.jsx +126 -14
- package/templates/next/files/components/ui/pagination.jsx +156 -0
- package/templates/next/files/components/ui/popover.jsx +284 -0
- package/templates/next/files/components/ui/radio.jsx +193 -0
- package/templates/next/files/components/ui/rating.jsx +131 -0
- package/templates/next/files/components/ui/resizable.jsx +338 -0
- package/templates/next/files/components/ui/scroll-area.jsx +123 -0
- package/templates/next/files/components/ui/separator.jsx +119 -0
- package/templates/next/files/components/ui/sheet.jsx +96 -11
- package/templates/next/files/components/ui/slider.jsx +174 -0
- package/templates/next/files/components/ui/stat.jsx +574 -0
- package/templates/next/files/components/ui/steps.jsx +338 -0
- package/templates/next/files/components/ui/swatch.jsx +33 -0
- package/templates/next/files/components/ui/table.jsx +130 -0
- package/templates/next/files/components/ui/toast.jsx +440 -0
- package/templates/next/files/components/ui/toggle.jsx +195 -0
- package/templates/next/files/components/ui/tooltip.jsx +270 -0
- package/templates/next/files/lib/sample.js +4 -12
- 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
|
+
|