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,473 @@
|
|
|
1
|
+
// The column-type registry: what each kind of column holds, how it reads
|
|
2
|
+
// and writes text, sorts and filters. Plain functions — no React, so they
|
|
3
|
+
// run on the server, in a test, in csv-import. How a type is DRAWN and
|
|
4
|
+
// EDITED lives in cells.jsx, keyed by `editor` below.
|
|
5
|
+
//
|
|
6
|
+
// A type is:
|
|
7
|
+
// label what a person calls it ("Currency")
|
|
8
|
+
// icon a kit glyph for the header
|
|
9
|
+
// align start · end (numbers line up on the right)
|
|
10
|
+
// width the default column width, px
|
|
11
|
+
// editor how it is edited in a cell: text · number · date · select ·
|
|
12
|
+
// person · toggle (flips in place) · rating · url · none
|
|
13
|
+
// empty(v) true when the cell holds nothing
|
|
14
|
+
// parse(text, col, ctx) → { value } | { error } — typed or pasted text in.
|
|
15
|
+
// A select may add `create: [{ value, label }]` (options that
|
|
16
|
+
// do not exist yet)
|
|
17
|
+
// toText(v, col, ctx) the text a copy puts on the clipboard and an
|
|
18
|
+
// edit starts from ("1250.5", "2026-09-23", "Won, Lost")
|
|
19
|
+
// format(v, col, ctx) the text a cell shows ("$1,250.50", "Sep 23, 2026")
|
|
20
|
+
// compare(a, b, col, ctx) the sort order; empties always sort last
|
|
21
|
+
// filters the conditions the Filter panel offers: [{ id, label, arg,
|
|
22
|
+
// test(v, arg, col, ctx) }] — `arg` names the value control:
|
|
23
|
+
// text · number · date · option · options · person · none
|
|
24
|
+
//
|
|
25
|
+
// `ctx` is { people, locale, currency }: what a type reads beyond its own
|
|
26
|
+
// column (the person list, the reader's locale).
|
|
27
|
+
|
|
28
|
+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
|
29
|
+
|
|
30
|
+
const isBlank = (v) => v == null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
31
|
+
const str = (v) => (v == null ? "" : String(v));
|
|
32
|
+
const lc = (v) => str(v).toLowerCase();
|
|
33
|
+
|
|
34
|
+
// ── numbers ─────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/** "$1,250.50", "(300)", "1 250,5", "-12" → a number, or null for blank,
|
|
37
|
+
* or NaN when it is not one. */
|
|
38
|
+
export function parseNumberText(text) {
|
|
39
|
+
let s = str(text).trim();
|
|
40
|
+
if (!s) return null;
|
|
41
|
+
let neg = false;
|
|
42
|
+
if (/^\(.*\)$/.test(s)) {
|
|
43
|
+
neg = true;
|
|
44
|
+
s = s.slice(1, -1);
|
|
45
|
+
}
|
|
46
|
+
s = s.replace(/[\s ]/g, "").replace(/^[^\d.,+-]+|[^\d.,]+$/g, "");
|
|
47
|
+
if (s.startsWith("-")) {
|
|
48
|
+
neg = !neg;
|
|
49
|
+
s = s.slice(1);
|
|
50
|
+
} else if (s.startsWith("+")) s = s.slice(1);
|
|
51
|
+
// A comma is a decimal point only when it is the one separator and two
|
|
52
|
+
// or fewer digits follow it ("12,5"); else it groups thousands.
|
|
53
|
+
const commas = (s.match(/,/g) || []).length;
|
|
54
|
+
const dots = (s.match(/\./g) || []).length;
|
|
55
|
+
if (commas === 1 && dots === 0 && /,\d{1,2}$/.test(s)) s = s.replace(",", ".");
|
|
56
|
+
else if (dots > 1 && commas <= 1) s = s.replace(/\./g, "").replace(",", ".");
|
|
57
|
+
else s = s.replace(/,/g, "");
|
|
58
|
+
if (!/^\d*\.?\d+$|^\d+\.$/.test(s)) return NaN;
|
|
59
|
+
const n = Number(s);
|
|
60
|
+
return neg ? -n : n;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function numberFormat(col, ctx, currency) {
|
|
64
|
+
const digits = col?.precision ?? (currency ? 2 : undefined);
|
|
65
|
+
const opts = currency
|
|
66
|
+
? { style: "currency", currency: col?.currency || ctx?.currency || "USD", minimumFractionDigits: digits, maximumFractionDigits: digits }
|
|
67
|
+
: { maximumFractionDigits: digits ?? 2, minimumFractionDigits: col?.precision ?? 0 };
|
|
68
|
+
return cachedFormat("n", ctx?.locale, opts, () => new Intl.NumberFormat(ctx?.locale, opts), () => new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Building an Intl formatter costs far more than using one, and a sort or
|
|
72
|
+
// a search formats every cell: one per locale and options, kept.
|
|
73
|
+
const FORMATS = new Map();
|
|
74
|
+
function cachedFormat(kind, locale, opts, make, fallback) {
|
|
75
|
+
const key = `${kind}|${locale ?? ""}|${JSON.stringify(opts)}`;
|
|
76
|
+
let f = FORMATS.get(key);
|
|
77
|
+
if (!f) {
|
|
78
|
+
try {
|
|
79
|
+
f = make();
|
|
80
|
+
} catch {
|
|
81
|
+
f = fallback();
|
|
82
|
+
}
|
|
83
|
+
FORMATS.set(key, f);
|
|
84
|
+
}
|
|
85
|
+
return f;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const numberCompare = (a, b) => a - b;
|
|
89
|
+
|
|
90
|
+
const NUMBER_FILTERS = [
|
|
91
|
+
{ id: "eq", label: "=", arg: "number", test: (v, a) => v === a },
|
|
92
|
+
{ id: "ne", label: "≠", arg: "number", test: (v, a) => v !== a },
|
|
93
|
+
{ id: "gt", label: ">", arg: "number", test: (v, a) => v > a },
|
|
94
|
+
{ id: "lt", label: "<", arg: "number", test: (v, a) => v < a },
|
|
95
|
+
{ id: "gte", label: "≥", arg: "number", test: (v, a) => v >= a },
|
|
96
|
+
{ id: "lte", label: "≤", arg: "number", test: (v, a) => v <= a },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// ── dates ───────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
102
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
103
|
+
|
|
104
|
+
function validDay(y, m, d) {
|
|
105
|
+
if (!(y >= 1000 && y <= 9999 && m >= 1 && m <= 12 && d >= 1)) return null;
|
|
106
|
+
const last = new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
107
|
+
return d <= last ? `${y}-${pad(m)}-${pad(d)}` : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Text → "YYYY-MM-DD", null for blank, or undefined when it is no day.
|
|
111
|
+
* ISO first, then 9/23/2026 (or 23/9/2026 with order "dmy", or when the
|
|
112
|
+
* first number cannot be a month), then "Sep 23, 2026" / "23 Sep 2026". */
|
|
113
|
+
export function parseDateText(text, order = "mdy") {
|
|
114
|
+
const s = str(text).trim();
|
|
115
|
+
if (!s) return null;
|
|
116
|
+
let m = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s].*)?$/.exec(s);
|
|
117
|
+
if (m) return validDay(+m[1], +m[2], +m[3]) ?? undefined;
|
|
118
|
+
m = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2}|\d{4})$/.exec(s);
|
|
119
|
+
if (m) {
|
|
120
|
+
let [a, b] = [+m[1], +m[2]];
|
|
121
|
+
const y = m[3].length === 2 ? 2000 + +m[3] : +m[3];
|
|
122
|
+
if (order === "dmy" || a > 12) [a, b] = [b, a];
|
|
123
|
+
return validDay(y, a, b) ?? undefined;
|
|
124
|
+
}
|
|
125
|
+
m = /^([a-z]{3,})\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s+(\d{4})$/i.exec(s);
|
|
126
|
+
if (m) {
|
|
127
|
+
const mo = MONTHS.indexOf(m[1].slice(0, 3).toLowerCase());
|
|
128
|
+
return mo < 0 ? undefined : validDay(+m[3], mo + 1, +m[2]) ?? undefined;
|
|
129
|
+
}
|
|
130
|
+
m = /^(\d{1,2})(?:st|nd|rd|th)?\s+([a-z]{3,})\.?,?\s+(\d{4})$/i.exec(s);
|
|
131
|
+
if (m) {
|
|
132
|
+
const mo = MONTHS.indexOf(m[2].slice(0, 3).toLowerCase());
|
|
133
|
+
return mo < 0 ? undefined : validDay(+m[3], mo + 1, +m[1]) ?? undefined;
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatDay(v, ctx) {
|
|
139
|
+
if (!v) return "";
|
|
140
|
+
const [y, m, d] = String(v).split("-").map(Number);
|
|
141
|
+
if (!y || !m || !d) return str(v);
|
|
142
|
+
const opts = { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" };
|
|
143
|
+
const fmt = cachedFormat("d", ctx?.locale, opts, () => new Intl.DateTimeFormat(ctx?.locale, opts), () => new Intl.DateTimeFormat(undefined, opts));
|
|
144
|
+
return fmt.format(new Date(Date.UTC(y, m - 1, d)));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── options (select, multi-select) ──────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
const optionsOf = (col) => col?.options ?? [];
|
|
150
|
+
export const optionOf = (col, value) => optionsOf(col).find((o) => o.value === value);
|
|
151
|
+
const labelOf = (col, value) => optionOf(col, value)?.label ?? str(value);
|
|
152
|
+
|
|
153
|
+
/** A new option's value from its label: "Closed won" → "closed-won". */
|
|
154
|
+
export function optionValue(label, taken = []) {
|
|
155
|
+
const base = lc(label).trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "option";
|
|
156
|
+
let v = base;
|
|
157
|
+
for (let i = 2; taken.includes(v); i++) v = `${base}-${i}`;
|
|
158
|
+
return v;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Hues 1–5 cycle through new options, so a created one is never grey.
|
|
162
|
+
function nextHue(col, extra = 0) {
|
|
163
|
+
return ((optionsOf(col).length + extra) % 5) + 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function matchOption(col, text) {
|
|
167
|
+
const t = lc(text).trim();
|
|
168
|
+
return optionsOf(col).find((o) => lc(o.label) === t || lc(o.value) === t);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseOptions(text, col, multiple) {
|
|
172
|
+
const s = str(text).trim();
|
|
173
|
+
if (!s) return { value: multiple ? [] : null };
|
|
174
|
+
const parts = multiple ? s.split(/\s*[,;\n]\s*/).filter(Boolean) : [s];
|
|
175
|
+
const value = [];
|
|
176
|
+
const create = [];
|
|
177
|
+
for (const p of parts) {
|
|
178
|
+
const hit = matchOption(col, p) ?? create.find((c) => lc(c.label) === lc(p));
|
|
179
|
+
if (hit) {
|
|
180
|
+
if (!value.includes(hit.value)) value.push(hit.value);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (col?.allowNew === false) return { error: `“${p}” is not one of the options` };
|
|
184
|
+
const o = { value: optionValue(p, [...optionsOf(col).map((x) => x.value), ...create.map((x) => x.value)]), label: p, hue: nextHue(col, create.length) };
|
|
185
|
+
create.push(o);
|
|
186
|
+
value.push(o.value);
|
|
187
|
+
}
|
|
188
|
+
return create.length ? { value: multiple ? value : value[0], create } : { value: multiple ? value : value[0] };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const optionRank = (col, v) => {
|
|
192
|
+
const i = optionsOf(col).findIndex((o) => o.value === v);
|
|
193
|
+
return i < 0 ? Infinity : i;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// ── people ──────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
const personOf = (ctx, id) => (ctx?.people ?? []).find((p) => p.id === id);
|
|
199
|
+
const personName = (ctx, id) => personOf(ctx, id)?.name ?? str(id);
|
|
200
|
+
const asList = (v) => (Array.isArray(v) ? v : isBlank(v) ? [] : [v]);
|
|
201
|
+
|
|
202
|
+
function parsePeople(text, col, ctx) {
|
|
203
|
+
const s = str(text).trim();
|
|
204
|
+
if (!s) return { value: col?.multiple ? [] : null };
|
|
205
|
+
const parts = col?.multiple ? s.split(/\s*[,;\n]\s*/).filter(Boolean) : [s];
|
|
206
|
+
const ids = [];
|
|
207
|
+
for (const p of parts) {
|
|
208
|
+
const t = lc(p);
|
|
209
|
+
const hit = (ctx?.people ?? []).find((x) => lc(x.name) === t || lc(x.email) === t || lc(x.id) === t || lc(x.name).split(" ")[0] === t);
|
|
210
|
+
if (!hit) return { error: `No one called “${p}”` };
|
|
211
|
+
if (!ids.includes(hit.id)) ids.push(hit.id);
|
|
212
|
+
}
|
|
213
|
+
return { value: col?.multiple ? ids : ids[0] };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── the registry ────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
const TEXT_FILTERS = [
|
|
219
|
+
{ id: "contains", label: "contains", arg: "text", test: (v, a) => lc(v).includes(lc(a)) },
|
|
220
|
+
{ id: "not-contains", label: "does not contain", arg: "text", test: (v, a) => !lc(v).includes(lc(a)) },
|
|
221
|
+
{ id: "is", label: "is", arg: "text", test: (v, a) => lc(v).trim() === lc(a).trim() },
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
export const COLUMN_TYPES = {
|
|
225
|
+
text: {
|
|
226
|
+
label: "Text",
|
|
227
|
+
icon: "type",
|
|
228
|
+
align: "start",
|
|
229
|
+
width: 220,
|
|
230
|
+
editor: "text",
|
|
231
|
+
parse: (t) => ({ value: str(t) }),
|
|
232
|
+
toText: (v) => str(v),
|
|
233
|
+
format: (v) => str(v),
|
|
234
|
+
compare: (a, b) => collator.compare(str(a), str(b)),
|
|
235
|
+
filters: TEXT_FILTERS,
|
|
236
|
+
},
|
|
237
|
+
number: {
|
|
238
|
+
label: "Number",
|
|
239
|
+
icon: "hash",
|
|
240
|
+
align: "end",
|
|
241
|
+
width: 120,
|
|
242
|
+
editor: "number",
|
|
243
|
+
parse: (t) => {
|
|
244
|
+
const n = parseNumberText(t);
|
|
245
|
+
return Number.isNaN(n) ? { error: "That is not a number" } : { value: n };
|
|
246
|
+
},
|
|
247
|
+
toText: (v) => (typeof v === "number" ? String(v) : str(v)),
|
|
248
|
+
format: (v, col, ctx) => (typeof v === "number" ? numberFormat(col, ctx, false).format(v) : str(v)),
|
|
249
|
+
compare: numberCompare,
|
|
250
|
+
filters: NUMBER_FILTERS,
|
|
251
|
+
},
|
|
252
|
+
currency: {
|
|
253
|
+
label: "Currency",
|
|
254
|
+
icon: "dollar",
|
|
255
|
+
align: "end",
|
|
256
|
+
width: 130,
|
|
257
|
+
editor: "number",
|
|
258
|
+
parse: (t) => {
|
|
259
|
+
const n = parseNumberText(t);
|
|
260
|
+
return Number.isNaN(n) ? { error: "That is not an amount" } : { value: n };
|
|
261
|
+
},
|
|
262
|
+
toText: (v) => (typeof v === "number" ? String(v) : str(v)),
|
|
263
|
+
format: (v, col, ctx) => (typeof v === "number" ? numberFormat(col, ctx, true).format(v) : str(v)),
|
|
264
|
+
compare: numberCompare,
|
|
265
|
+
filters: NUMBER_FILTERS,
|
|
266
|
+
},
|
|
267
|
+
date: {
|
|
268
|
+
label: "Date",
|
|
269
|
+
icon: "calendar",
|
|
270
|
+
align: "start",
|
|
271
|
+
width: 140,
|
|
272
|
+
editor: "date",
|
|
273
|
+
parse: (t, col) => {
|
|
274
|
+
const d = parseDateText(t, col?.dateOrder);
|
|
275
|
+
return d === undefined ? { error: "That is not a date — try 2026-09-23" } : { value: d };
|
|
276
|
+
},
|
|
277
|
+
toText: (v) => str(v),
|
|
278
|
+
format: (v, col, ctx) => formatDay(v, ctx),
|
|
279
|
+
compare: (a, b) => (a < b ? -1 : a > b ? 1 : 0),
|
|
280
|
+
filters: [
|
|
281
|
+
{ id: "is", label: "is", arg: "date", test: (v, a) => v === a },
|
|
282
|
+
{ id: "before", label: "is before", arg: "date", test: (v, a) => v < a },
|
|
283
|
+
{ id: "after", label: "is after", arg: "date", test: (v, a) => v > a },
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
select: {
|
|
287
|
+
label: "Single select",
|
|
288
|
+
icon: "chevron-down",
|
|
289
|
+
align: "start",
|
|
290
|
+
width: 150,
|
|
291
|
+
editor: "select",
|
|
292
|
+
parse: (t, col) => parseOptions(t, col, false),
|
|
293
|
+
toText: (v, col) => (isBlank(v) ? "" : labelOf(col, v)),
|
|
294
|
+
format: (v, col) => (isBlank(v) ? "" : labelOf(col, v)),
|
|
295
|
+
compare: (a, b, col) => optionRank(col, a) - optionRank(col, b) || collator.compare(str(a), str(b)),
|
|
296
|
+
filters: [
|
|
297
|
+
{ id: "any", label: "is any of", arg: "options", test: (v, a) => asList(a).includes(v) },
|
|
298
|
+
{ id: "none", label: "is none of", arg: "options", test: (v, a) => !asList(a).includes(v) },
|
|
299
|
+
],
|
|
300
|
+
},
|
|
301
|
+
"multi-select": {
|
|
302
|
+
label: "Multiple select",
|
|
303
|
+
icon: "tag",
|
|
304
|
+
align: "start",
|
|
305
|
+
width: 200,
|
|
306
|
+
editor: "select",
|
|
307
|
+
multiple: true,
|
|
308
|
+
parse: (t, col) => parseOptions(t, col, true),
|
|
309
|
+
toText: (v, col) => asList(v).map((x) => labelOf(col, x)).join(", "),
|
|
310
|
+
format: (v, col) => asList(v).map((x) => labelOf(col, x)).join(", "),
|
|
311
|
+
compare: (a, b, col) => optionRank(col, asList(a)[0]) - optionRank(col, asList(b)[0]) || asList(a).length - asList(b).length,
|
|
312
|
+
filters: [
|
|
313
|
+
{ id: "any", label: "has any of", arg: "options", test: (v, a) => asList(v).some((x) => asList(a).includes(x)) },
|
|
314
|
+
{ id: "all", label: "has all of", arg: "options", test: (v, a) => asList(a).every((x) => asList(v).includes(x)) },
|
|
315
|
+
{ id: "none", label: "has none of", arg: "options", test: (v, a) => !asList(v).some((x) => asList(a).includes(x)) },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
person: {
|
|
319
|
+
label: "Person",
|
|
320
|
+
icon: "user",
|
|
321
|
+
align: "start",
|
|
322
|
+
width: 170,
|
|
323
|
+
editor: "person",
|
|
324
|
+
parse: parsePeople,
|
|
325
|
+
toText: (v, col, ctx) => asList(v).map((id) => personName(ctx, id)).join(", "),
|
|
326
|
+
format: (v, col, ctx) => asList(v).map((id) => personName(ctx, id)).join(", "),
|
|
327
|
+
compare: (a, b, col, ctx) => collator.compare(personName(ctx, asList(a)[0]), personName(ctx, asList(b)[0])),
|
|
328
|
+
filters: [
|
|
329
|
+
{ id: "any", label: "is any of", arg: "person", test: (v, a) => asList(v).some((x) => asList(a).includes(x)) },
|
|
330
|
+
{ id: "none", label: "is none of", arg: "person", test: (v, a) => !asList(v).some((x) => asList(a).includes(x)) },
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
checkbox: {
|
|
334
|
+
label: "Checkbox",
|
|
335
|
+
icon: "square-check",
|
|
336
|
+
align: "center",
|
|
337
|
+
width: 96,
|
|
338
|
+
editor: "toggle",
|
|
339
|
+
// A checkbox is never "empty": unchecked is a value.
|
|
340
|
+
empty: () => false,
|
|
341
|
+
parse: (t) => {
|
|
342
|
+
const s = lc(t).trim();
|
|
343
|
+
if (["true", "yes", "y", "1", "x", "✓", "✔", "checked", "on", "done"].includes(s)) return { value: true };
|
|
344
|
+
if (["false", "no", "n", "0", "", "unchecked", "off"].includes(s)) return { value: false };
|
|
345
|
+
return { error: "Use yes or no" };
|
|
346
|
+
},
|
|
347
|
+
toText: (v) => (v ? "true" : "false"),
|
|
348
|
+
format: (v) => (v ? "Yes" : "No"),
|
|
349
|
+
compare: (a, b) => Number(Boolean(b)) - Number(Boolean(a)),
|
|
350
|
+
filters: [
|
|
351
|
+
{ id: "checked", label: "is checked", arg: "none", test: (v) => Boolean(v) },
|
|
352
|
+
{ id: "unchecked", label: "is not checked", arg: "none", test: (v) => !v },
|
|
353
|
+
],
|
|
354
|
+
},
|
|
355
|
+
url: {
|
|
356
|
+
label: "Link",
|
|
357
|
+
icon: "link",
|
|
358
|
+
align: "start",
|
|
359
|
+
width: 200,
|
|
360
|
+
editor: "url",
|
|
361
|
+
parse: (t) => {
|
|
362
|
+
const s = str(t).trim();
|
|
363
|
+
if (!s) return { value: "" };
|
|
364
|
+
if (/\s/.test(s) || !/[.:]/.test(s)) return { error: "That does not look like a link" };
|
|
365
|
+
return { value: s };
|
|
366
|
+
},
|
|
367
|
+
toText: (v) => str(v),
|
|
368
|
+
format: (v) => str(v).replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, ""),
|
|
369
|
+
compare: (a, b) => collator.compare(str(a), str(b)),
|
|
370
|
+
filters: TEXT_FILTERS,
|
|
371
|
+
},
|
|
372
|
+
rating: {
|
|
373
|
+
label: "Rating",
|
|
374
|
+
icon: "star",
|
|
375
|
+
align: "start",
|
|
376
|
+
width: 130,
|
|
377
|
+
editor: "rating",
|
|
378
|
+
empty: (v) => !v,
|
|
379
|
+
parse: (t, col) => {
|
|
380
|
+
const s = str(t).trim();
|
|
381
|
+
if (!s) return { value: 0 };
|
|
382
|
+
const stars = (s.match(/[★⭐*]/g) || []).length;
|
|
383
|
+
const n = stars && stars === s.replace(/\s/g, "").length ? stars : Number(s);
|
|
384
|
+
const max = col?.max ?? 5;
|
|
385
|
+
if (!Number.isInteger(n) || n < 0 || n > max) return { error: `Use a whole number from 0 to ${max}` };
|
|
386
|
+
return { value: n };
|
|
387
|
+
},
|
|
388
|
+
toText: (v) => (v ? String(v) : ""),
|
|
389
|
+
format: (v, col) => (v ? `${v} of ${col?.max ?? 5}` : ""),
|
|
390
|
+
compare: (a, b) => (a || 0) - (b || 0),
|
|
391
|
+
filters: [
|
|
392
|
+
{ id: "gte", label: "is at least", arg: "number", test: (v, a) => (v || 0) >= a },
|
|
393
|
+
{ id: "lte", label: "is at most", arg: "number", test: (v, a) => (v || 0) <= a },
|
|
394
|
+
{ id: "eq", label: "is", arg: "number", test: (v, a) => (v || 0) === a },
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Every type also filters on emptiness.
|
|
400
|
+
const EMPTY_FILTERS = [
|
|
401
|
+
{ id: "empty", label: "is empty", arg: "none", test: (v, a, col, ctx, type) => type.empty(v) },
|
|
402
|
+
{ id: "not-empty", label: "is not empty", arg: "none", test: (v, a, col, ctx, type) => !type.empty(v) },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
/** The type a column has: `custom` (the Spreadsheet's `types` prop)
|
|
406
|
+
* first, then the built-ins; an unknown name reads as text. Missing
|
|
407
|
+
* functions fall back to text's, so a custom type may be small. */
|
|
408
|
+
export function typeOf(col, custom) {
|
|
409
|
+
const name = col?.type ?? "text";
|
|
410
|
+
const key = custom ?? NONE;
|
|
411
|
+
let byName = CACHE.get(key);
|
|
412
|
+
if (!byName) CACHE.set(key, (byName = new Map()));
|
|
413
|
+
let t = byName.get(name);
|
|
414
|
+
if (t) return t;
|
|
415
|
+
const own = custom?.[name] ? { ...(COLUMN_TYPES[name] ?? {}), ...custom[name] } : COLUMN_TYPES[name] ?? COLUMN_TYPES.text;
|
|
416
|
+
t = { ...COLUMN_TYPES.text, empty: isBlank, ...own, name };
|
|
417
|
+
t.allFilters = [...(own.filters ?? COLUMN_TYPES.text.filters), ...(name === "checkbox" ? [] : EMPTY_FILTERS)];
|
|
418
|
+
byName.set(name, t);
|
|
419
|
+
return t;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// One resolved type per (custom registry, name): typeOf runs per cell.
|
|
423
|
+
const NONE = {};
|
|
424
|
+
const CACHE = new WeakMap();
|
|
425
|
+
|
|
426
|
+
export { isBlank };
|
|
427
|
+
|
|
428
|
+
/** Parse `text` for `col`; a blank cell is always fine (an empty value). */
|
|
429
|
+
export function parseCell(text, col, ctx, custom) {
|
|
430
|
+
const t = typeOf(col, custom);
|
|
431
|
+
try {
|
|
432
|
+
return t.parse(text, col, ctx);
|
|
433
|
+
} catch {
|
|
434
|
+
return { error: "That value does not fit here" };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** A value moved to a column of another type: through its text. What
|
|
439
|
+
* does not parse becomes empty. */
|
|
440
|
+
export function convertValue(value, from, to, ctx, custom) {
|
|
441
|
+
const text = typeOf(from, custom).toText(value, from, ctx);
|
|
442
|
+
const r = parseCell(text, to, ctx, custom);
|
|
443
|
+
return "error" in r ? typeOf(to, custom).parse("", to, ctx).value ?? null : r.value;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Guess a column type from sample text values (csv-import uses it):
|
|
447
|
+
* checkbox, currency, number, date, url, select (few distinct values),
|
|
448
|
+
* multi-select (comma lists of few distinct parts), else text. Most
|
|
449
|
+
* values (four in five) must fit, so a few bad cells in an import are
|
|
450
|
+
* flagged to fix instead of turning the whole column into text. */
|
|
451
|
+
export function guessType(values) {
|
|
452
|
+
const vals = values.map((v) => str(v).trim()).filter(Boolean);
|
|
453
|
+
if (vals.length === 0) return "text";
|
|
454
|
+
const need = Math.max(1, Math.ceil(vals.length * 0.8));
|
|
455
|
+
const most = (fn) => vals.filter(fn).length >= need;
|
|
456
|
+
const isNum = (v) => {
|
|
457
|
+
const n = parseNumberText(v);
|
|
458
|
+
return n !== null && !Number.isNaN(n);
|
|
459
|
+
};
|
|
460
|
+
if (vals.every((v) => /^(true|false|yes|no|y|n|x|✓)$/i.test(v))) return "checkbox";
|
|
461
|
+
if (most(isNum)) return vals.some((v) => /[$€£¥₹]|^[A-Z]{3}\s?[\d.,]/.test(v)) ? "currency" : "number";
|
|
462
|
+
if (most((v) => /\d/.test(v) && parseDateText(v) !== undefined)) return "date";
|
|
463
|
+
if (most((v) => /^(https?:\/\/|www\.)\S+$/i.test(v))) return "url";
|
|
464
|
+
const distinct = new Set(vals.map((v) => v.toLowerCase()));
|
|
465
|
+
if (vals.some((v) => v.includes(",")) && vals.every((v) => v.length <= 80)) {
|
|
466
|
+
const all = vals.flatMap((v) => v.split(/\s*,\s*/)).map((p) => p.toLowerCase());
|
|
467
|
+
const parts = new Set(all);
|
|
468
|
+
// Parts that repeat across cells ("SaaS, B2B" … "B2B"): tags, not prose.
|
|
469
|
+
if (parts.size <= 12 && parts.size < all.length) return "multi-select";
|
|
470
|
+
}
|
|
471
|
+
if (vals.length >= 4 && distinct.size <= Math.min(12, Math.ceil(vals.length / 2)) && vals.every((v) => v.length <= 40)) return "select";
|
|
472
|
+
return "text";
|
|
473
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Stat cards
|
|
2
|
+
|
|
3
|
+
The few numbers at the top of a dashboard, each with its name and what
|
|
4
|
+
changed. Four shapes over one list of items: a bordered `strip`; `cards`,
|
|
5
|
+
one per number, with a change in the corner or after the value, a "from"
|
|
6
|
+
value, a status dot and a goal meter, a sparkline, a "View more" footer;
|
|
7
|
+
`usage`, one tile per amount against its limit, as a bar or a ring, a
|
|
8
|
+
split by kind with what is free, a warning line near the cap, an action
|
|
9
|
+
along the foot and a limit the founder can change; and a plain `list` of
|
|
10
|
+
values with their change. Loading, empty and error states are built in.
|
|
11
|
+
|
|
12
|
+
```jsx
|
|
13
|
+
import { StatCards } from "../components/blocks/stat-cards"; // a Makersclaw app ("./blocks/stat-cards" from components/)
|
|
14
|
+
import { StatCards } from "@/components/makersclaw/blocks/stat-cards"; // a shadcn project
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It is a client component: render it from a `"use client"` component, and
|
|
18
|
+
load the numbers in the server surface (README §4). A sparkline is drawn
|
|
19
|
+
by the free `chart` block, so `makersclaw add stat-cards` lays that one
|
|
20
|
+
too.
|
|
21
|
+
|
|
22
|
+
## The data
|
|
23
|
+
|
|
24
|
+
One item per number, already formatted where it is shown as text:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
{
|
|
28
|
+
id: "mrr", // key; default the label
|
|
29
|
+
label: "Monthly recurring revenue",
|
|
30
|
+
value: "$48.6K", // a string you formatted, or a number (gets thousands separators)
|
|
31
|
+
unit: "ms", // after the value, smaller and muted
|
|
32
|
+
sublabel: "ORBT", // after the label, muted: a ticker, a region code
|
|
33
|
+
delta: 5.2, // a percent; or Delta's props: { value, suffix, display, label, invert }
|
|
34
|
+
invert: true, // lower is better (churn, cost): down turns green
|
|
35
|
+
previous: "$46.2K", // shown after the value as "from $46.2K"
|
|
36
|
+
caption: "Paid plans only", // a muted line under the value
|
|
37
|
+
status: "warning", // a dot and a word at the top right: success · warning · danger · info,
|
|
38
|
+
// or { tone, label: "Needs attention" }
|
|
39
|
+
goal: { value: 3, max: 5, label: "Goals achieved" }, // a thin meter in the status tone
|
|
40
|
+
trend: [12, 14, 13, 17], // a sparkline under the value (numbers, oldest first)
|
|
41
|
+
trendLabel: "MRR, last 16 weeks", // what a screen reader hears for it
|
|
42
|
+
trendTone: "chart-2", // default: green when the change is good, red when bad
|
|
43
|
+
href: "/revenue", // a "View more →" footer link; or onSelect(item) for a button
|
|
44
|
+
linkLabel: "View region",
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In `usage`, an item is an amount against a limit:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
{
|
|
52
|
+
id: "egress",
|
|
53
|
+
label: "Egress",
|
|
54
|
+
used: 188, // a number, in the limit's unit
|
|
55
|
+
limit: 200, // the cap; null means none ("/ Unlimited"); left out, no limit is shown
|
|
56
|
+
unit: "GB",
|
|
57
|
+
value: "188 GB", // how `used` reads; default the number and the unit
|
|
58
|
+
limitText: "$150 budget", // how the limit reads; default the number and the unit
|
|
59
|
+
suffix: "this month", // after the value when there is no limit
|
|
60
|
+
format: (n) => `$${n}`, // every number shown, default thousands separators
|
|
61
|
+
parts: [{ label: "Writes", value: 7410000 }, { label: "Reads", value: 2410000 }],
|
|
62
|
+
// a split bar with a legend in place of the meter; with a
|
|
63
|
+
// limit, the rest shows as "Free" (restLabel)
|
|
64
|
+
note: "Past 200 GB is billed per GB.", // a line under the bar: warning near the cap
|
|
65
|
+
noteTone: "muted", // override: muted · success · warning · danger · info
|
|
66
|
+
delta: 9.5, // a badge beside the label
|
|
67
|
+
thresholds: [{ at: 75, tone: "warning" }], // default: warning at warnAt
|
|
68
|
+
action: { label: "Upgrade", icon: "zap", onSelect: (item) => … }, // a button along the foot
|
|
69
|
+
editable: false, // no "Change limit" button on this tile
|
|
70
|
+
limitLabel: "Change budget", limitPrefix: "$", // this tile's limit button and field
|
|
71
|
+
meter: "ring", // this tile's shape, over the block's `meter`
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Over the limit the bar stays full and turns red; a reader hears "over by".
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
- `items` — the numbers, as above. Required.
|
|
80
|
+
- `variant` — `"strip"` (default) · `"cards"` · `"usage"` · `"list"`.
|
|
81
|
+
- `columns` — how many across from `lg` up, 1 to 4; default the number of
|
|
82
|
+
items, at most 4. Cards and usage tiles are one column on a phone and
|
|
83
|
+
two from `sm`; a strip is two on a phone.
|
|
84
|
+
- `title` · `description` · `actions` — a header over the numbers
|
|
85
|
+
("Plan usage", "You are on the Team plan. Compare plans", a button).
|
|
86
|
+
- `label` — the group's name for a screen reader when there is no title.
|
|
87
|
+
|
|
88
|
+
Stats (`strip`, `cards`, `list`)
|
|
89
|
+
|
|
90
|
+
- `deltaPlacement` — `"inline"` (after the value) or `"corner"` (at the
|
|
91
|
+
label's right). Default corner in a strip, inline elsewhere. A tile with
|
|
92
|
+
a `status` keeps its change inline.
|
|
93
|
+
- `deltaVariant` — `"text"` or `"badge"` (a tinted pill). Default badge
|
|
94
|
+
in a list, text elsewhere.
|
|
95
|
+
- `size` — the value's size: `"sm"` · `"md"` (default in a strip) · `"lg"`
|
|
96
|
+
(default in cards).
|
|
97
|
+
- `linkLabel` — the footer link's words, default `"View more"`.
|
|
98
|
+
|
|
99
|
+
Usage
|
|
100
|
+
|
|
101
|
+
- `meter` — `"bar"` (default) or `"ring"`, for every tile without its own
|
|
102
|
+
`meter`. A tile with `parts` is always a split bar.
|
|
103
|
+
- `format(n)` — every number shown, unless a tile has its own.
|
|
104
|
+
- `warnAt` — the percent of the limit where the bar and the note turn
|
|
105
|
+
warning, default 90.
|
|
106
|
+
- `onLimitChange(item, limit)` — adds a "Change limit" button to each tile
|
|
107
|
+
(not those with `editable: false`). It opens a dialog with the limit in
|
|
108
|
+
a number field; Save calls this with the new number. Return (or resolve
|
|
109
|
+
to) `{ ok: false, error }`, or throw, and the dialog stays open and says
|
|
110
|
+
why; anything else closes it. The block does not change the number on
|
|
111
|
+
screen: pass the new `limit` back in `items`.
|
|
112
|
+
- `limitLabel` — the button's and dialog's words, default `"Change limit"`.
|
|
113
|
+
- `limitHint` — the dialog's description, default the current limit and
|
|
114
|
+
what is used.
|
|
115
|
+
|
|
116
|
+
States
|
|
117
|
+
|
|
118
|
+
- `loading` — skeleton tiles in the shape of the variant; `loadingCount`
|
|
119
|
+
of them (default 4).
|
|
120
|
+
- `error` — a danger notice above the numbers (they still show, if any);
|
|
121
|
+
`onRetry` adds a Retry button to it.
|
|
122
|
+
- `emptyTitle` · `emptyBody` · `emptyAction` — when `items` is empty.
|
|
123
|
+
- `className` — layout around it (width, margin). Never colour.
|
|
124
|
+
|
|
125
|
+
## Example
|
|
126
|
+
|
|
127
|
+
```jsx
|
|
128
|
+
// components/Overview.jsx — client: the numbers the server read
|
|
129
|
+
"use client";
|
|
130
|
+
import { StatCards } from "./blocks/stat-cards";
|
|
131
|
+
import { updateRow } from "../lib/actions";
|
|
132
|
+
|
|
133
|
+
export default function Overview({ kpis, usage }) {
|
|
134
|
+
return (
|
|
135
|
+
<div className="grid gap-6">
|
|
136
|
+
<StatCards
|
|
137
|
+
items={[
|
|
138
|
+
{ id: "mrr", label: "MRR", value: kpis.mrr, delta: kpis.mrrChange, trend: kpis.mrrWeeks, href: "/revenue" },
|
|
139
|
+
{ id: "churn", label: "Logo churn", value: kpis.churn, delta: kpis.churnChange, invert: true },
|
|
140
|
+
{ id: "trials", label: "Active trials", value: kpis.trials, previous: kpis.trialsLastWeek, delta: kpis.trialsChange },
|
|
141
|
+
]}
|
|
142
|
+
variant="cards"
|
|
143
|
+
/>
|
|
144
|
+
<StatCards
|
|
145
|
+
variant="usage"
|
|
146
|
+
title="Plan usage"
|
|
147
|
+
items={usage.map((u) => ({ id: u.id, label: u.name, used: u.used, limit: u.limit, unit: u.unit }))}
|
|
148
|
+
onLimitChange={(item, limit) => updateRow({ source: "public.quotas", id: item.id, patch: { limit } })}
|
|
149
|
+
limitLabel="Change budget"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## What it will not do
|
|
157
|
+
|
|
158
|
+
- Fetch or compute: numbers come in formatted, changes already worked out.
|
|
159
|
+
- Draw a chart worth reading on its own: that is the `chart` block.
|
|
160
|
+
- Score one subject against thresholds with an overall status, or list
|
|
161
|
+
many meters under a shared summary: those are other blocks.
|
|
162
|
+
- Colour a number for decoration: tones are for status (a change's
|
|
163
|
+
direction, a tile near its cap, a status dot).
|
|
164
|
+
|
|
165
|
+
A tile it cannot be is your own component in `components/`, built from
|
|
166
|
+
the kit's `Stat`, `Meter` and `Distribution` — not an edit to this block.
|
|
167
|
+
Its files are fixed: `npm run check` refuses a change, and `makersclaw add
|
|
168
|
+
stat-cards` restores them.
|
|
169
|
+
|
|
170
|
+
## Credits
|
|
171
|
+
|
|
172
|
+
Ported from blocks.so's stats (https://github.com/ephraimduncan/blocks,
|
|
173
|
+
MIT): a rewrite on the Makersclaw kit, not their code.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stat-cards",
|
|
3
|
+
"title": "Stat cards",
|
|
4
|
+
"description": "A row of key numbers, each with its name and its change: one bordered strip, or a card each with a change in the corner or after the value, a \"from\" value, a status dot and a goal meter, a sparkline, a \"View more\" footer; or a usage tile per amount against its limit (a bar or a ring, a split by kind with what is free, a warning line near the cap, an action and a limit the founder can change); or a plain list of values with their change. Loading, empty and error states.",
|
|
5
|
+
"useWhen": "The few numbers at the top of a dashboard or a report: revenue, users, conversion, spend against budget, seats and storage against a plan. Not for a trend worth reading on its own (a chart), many rows of records (a table), or one subject scored against thresholds (a scorecard).",
|
|
6
|
+
"tags": ["stats", "kpi", "metrics", "numbers", "dashboard", "overview", "delta", "change", "trend", "sparkline", "usage", "limit", "quota", "plan", "budget", "goal", "status", "ring", "meter"],
|
|
7
|
+
"exports": ["StatCards"],
|
|
8
|
+
"files": ["index.js", "stat-cards.jsx", "usage.jsx"],
|
|
9
|
+
"requires": ["chart"]
|
|
10
|
+
}
|