makersclaw 0.2.0 → 0.3.1
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/THIRD-PARTY-NOTICES.md +180 -0
- package/package.json +3 -2
- 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 +10 -6
- 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 +205 -8
- 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 +12 -7
- 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 +3411 -29
- 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 +55 -5
- package/registry/blocks/board/block.json +2 -2
- package/registry/blocks/board/board.jsx +230 -86
- 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 +80 -0
- 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/cli.mjs +19 -6
- package/src/install.mjs +18 -0
- package/src/registry.mjs +38 -4
- package/src/shadcn.mjs +14 -1
- 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/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/template.json +264 -8
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Badge, Button, Image, Kicker, Rating, Toggle, cx } from "../../ui";
|
|
5
|
+
import { Price, cartLine } from "../product-detail";
|
|
6
|
+
|
|
7
|
+
// One product as a card in the grid, and one promotion as a picture tile.
|
|
8
|
+
// The whole card opens the product (its name is the real link or button,
|
|
9
|
+
// stretched over the card); the heart and the add button sit above that
|
|
10
|
+
// target and click on their own.
|
|
11
|
+
|
|
12
|
+
async function attempt(fn, ...args) {
|
|
13
|
+
try {
|
|
14
|
+
const r = await fn(...args);
|
|
15
|
+
return r && typeof r === "object" ? r : { ok: Boolean(r) };
|
|
16
|
+
} catch (e) {
|
|
17
|
+
return { ok: false, error: e?.message };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const STRETCH = "after:absolute after:inset-0";
|
|
22
|
+
|
|
23
|
+
function Title({ href, onOpen, children, className }) {
|
|
24
|
+
if (href) {
|
|
25
|
+
return (
|
|
26
|
+
<Button variant="link" size="inherit" tone="muted" href={href} data-stretch className={cx(STRETCH, className)}>
|
|
27
|
+
{children}
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (onOpen) {
|
|
32
|
+
return (
|
|
33
|
+
<Button variant="link" size="inherit" tone="muted" onClick={onOpen} data-stretch className={cx("text-left", STRETCH, className)}>
|
|
34
|
+
{children}
|
|
35
|
+
</Button>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return <span className={className}>{children}</span>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A product card: picture, badge, heart, brand, name, rating, price and a
|
|
43
|
+
* quick add. A product with options cannot be added blind: its button
|
|
44
|
+
* says "Choose options" and opens it.
|
|
45
|
+
*/
|
|
46
|
+
export function ProductCard({ product, onOpen, onAddToCart, onFavorite, favorite = false, currency = "USD", locale, addLabel = "Add to cart", className }) {
|
|
47
|
+
const [fav, setFav] = useState(favorite);
|
|
48
|
+
const [status, setStatus] = useState({ state: "idle" });
|
|
49
|
+
useEffect(() => setFav(favorite), [favorite]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (status.state !== "added") return;
|
|
52
|
+
const t = setTimeout(() => setStatus({ state: "idle" }), 2500);
|
|
53
|
+
return () => clearTimeout(t);
|
|
54
|
+
}, [status.state]);
|
|
55
|
+
|
|
56
|
+
const open = onOpen ? () => onOpen(product) : undefined;
|
|
57
|
+
const cur = product.currency ?? currency;
|
|
58
|
+
const needsChoice = (product.options ?? []).some((o) => (o.values ?? []).length > 1);
|
|
59
|
+
const soldOut = product.available === false;
|
|
60
|
+
const picture = product.image ?? (typeof product.images?.[0] === "object" ? product.images[0]?.src : product.images?.[0]);
|
|
61
|
+
|
|
62
|
+
async function add() {
|
|
63
|
+
setStatus({ state: "busy" });
|
|
64
|
+
const r = await attempt(onAddToCart, cartLine(product, {}, 1, cur));
|
|
65
|
+
setStatus(r.ok ? { state: "added" } : { state: "failed", error: r.error || "Not added. Try again." });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function toggle(next) {
|
|
69
|
+
setFav(next);
|
|
70
|
+
const r = await attempt(onFavorite, product, next);
|
|
71
|
+
if (!r.ok) {
|
|
72
|
+
setFav(!next);
|
|
73
|
+
setStatus({ state: "failed", error: r.error || (next ? "Not saved." : "Not removed.") });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<article
|
|
79
|
+
className={cx(
|
|
80
|
+
"relative flex h-full min-w-0 flex-col overflow-hidden rounded-lg border border-border bg-card text-card-foreground",
|
|
81
|
+
"has-[a[data-stretch]:focus-visible,button[data-stretch]:focus-visible]:ring-2 has-[a[data-stretch]:focus-visible,button[data-stretch]:focus-visible]:ring-ring",
|
|
82
|
+
(product.href || onOpen) && "transition-shadow hover:shadow-md",
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<div className="relative">
|
|
87
|
+
<Image src={picture} alt="" ratio="square" rounded="none" />
|
|
88
|
+
{product.badge ? (
|
|
89
|
+
<Badge variant="soft" className="absolute start-2 top-2">
|
|
90
|
+
{product.badge}
|
|
91
|
+
</Badge>
|
|
92
|
+
) : null}
|
|
93
|
+
{onFavorite ? (
|
|
94
|
+
<Toggle
|
|
95
|
+
icon="heart"
|
|
96
|
+
label={fav ? `Remove ${product.name} from favourites` : `Save ${product.name} to favourites`}
|
|
97
|
+
pressed={fav}
|
|
98
|
+
onPressedChange={toggle}
|
|
99
|
+
className="absolute end-2 top-2 z-10"
|
|
100
|
+
/>
|
|
101
|
+
) : null}
|
|
102
|
+
</div>
|
|
103
|
+
<div className="flex flex-1 flex-col gap-1.5 border-t border-border p-3 sm:p-4">
|
|
104
|
+
{product.brand ? <Kicker className="truncate">{product.brand}</Kicker> : null}
|
|
105
|
+
<h3 className="line-clamp-2 text-sm font-medium leading-snug">
|
|
106
|
+
<Title href={product.href} onOpen={open}>
|
|
107
|
+
{product.name}
|
|
108
|
+
</Title>
|
|
109
|
+
</h3>
|
|
110
|
+
{typeof product.rating === "number" ? (
|
|
111
|
+
<p className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
112
|
+
<Rating value={product.rating} size="sm" />
|
|
113
|
+
{typeof product.reviews === "number" ? <span data-nums>({product.reviews.toLocaleString(locale)})</span> : null}
|
|
114
|
+
</p>
|
|
115
|
+
) : null}
|
|
116
|
+
<Price price={product.price} compareAt={product.compareAt} currency={cur} locale={locale} size="md" className="mt-auto pt-1" />
|
|
117
|
+
{onAddToCart ? (
|
|
118
|
+
<div className="relative z-10 mt-2 grid gap-1.5">
|
|
119
|
+
{soldOut ? (
|
|
120
|
+
<Button disabled className="w-full">
|
|
121
|
+
Sold out
|
|
122
|
+
</Button>
|
|
123
|
+
) : needsChoice ? (
|
|
124
|
+
<Button className="w-full" onClick={open} disabled={!open} href={open ? undefined : product.href}>
|
|
125
|
+
Choose options
|
|
126
|
+
</Button>
|
|
127
|
+
) : (
|
|
128
|
+
<Button variant="primary" icon="shopping-cart" className="w-full" loading={status.state === "busy"} onClick={add}>
|
|
129
|
+
{status.state === "added" ? "Added" : addLabel}
|
|
130
|
+
</Button>
|
|
131
|
+
)}
|
|
132
|
+
<p aria-live="polite" className="text-xs empty:hidden">
|
|
133
|
+
{status.state === "failed" ? <span className="text-destructive">{status.error}</span> : null}
|
|
134
|
+
{status.state === "added" ? <span className="sr-only">{product.name} added to your cart.</span> : null}
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
) : null}
|
|
138
|
+
</div>
|
|
139
|
+
</article>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A promotion: a tall picture with a kicker, a title and an action on a
|
|
145
|
+
* panel over its foot. The panel is the theme's background, so the words
|
|
146
|
+
* read on any picture in any theme.
|
|
147
|
+
*/
|
|
148
|
+
export function PromoTile({ item, large = false, onOpen, className }) {
|
|
149
|
+
const action = item.action ?? (item.href || onOpen ? { label: item.actionLabel ?? "Shop now", href: item.href } : null);
|
|
150
|
+
const open = action?.onClick ?? (onOpen ? () => onOpen(item) : undefined);
|
|
151
|
+
return (
|
|
152
|
+
<article
|
|
153
|
+
className={cx(
|
|
154
|
+
"relative flex min-w-0 overflow-hidden rounded-lg border border-border bg-muted",
|
|
155
|
+
"has-[a[data-stretch]:focus-visible,button[data-stretch]:focus-visible]:ring-2 has-[a[data-stretch]:focus-visible,button[data-stretch]:focus-visible]:ring-ring",
|
|
156
|
+
large ? "min-h-80 lg:min-h-full" : "min-h-80",
|
|
157
|
+
className,
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
<FeatureMedia item={item} />
|
|
161
|
+
<div className={cx("relative mt-auto w-full p-3", large && "sm:p-5")}>
|
|
162
|
+
<div className="grid gap-1 rounded-md bg-background/90 p-4 text-foreground shadow-sm backdrop-blur-sm">
|
|
163
|
+
{item.kicker ? <Kicker>{item.kicker}</Kicker> : null}
|
|
164
|
+
<h3 className={cx("font-semibold leading-tight", large ? "text-xl sm:text-2xl" : "text-lg")}>{item.title ?? item.name}</h3>
|
|
165
|
+
{item.body ? <p className="text-sm text-muted-foreground">{item.body}</p> : null}
|
|
166
|
+
{action ? (
|
|
167
|
+
<p className="mt-1 text-sm">
|
|
168
|
+
{action.href ? (
|
|
169
|
+
<Button variant="link" href={action.href} data-stretch className={STRETCH}>
|
|
170
|
+
{action.label}
|
|
171
|
+
</Button>
|
|
172
|
+
) : open ? (
|
|
173
|
+
<Button variant="link" onClick={open} data-stretch className={STRETCH}>
|
|
174
|
+
{action.label}
|
|
175
|
+
</Button>
|
|
176
|
+
) : null}
|
|
177
|
+
</p>
|
|
178
|
+
) : null}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</article>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The tile's picture, or a muted looping video for a feature. The video
|
|
186
|
+
// never plays for a reader who asks for reduced motion, and has a pause.
|
|
187
|
+
function FeatureMedia({ item }) {
|
|
188
|
+
const [paused, setPaused] = useState(false);
|
|
189
|
+
const [calm, setCalm] = useState(true);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!item.video || typeof matchMedia !== "function") return;
|
|
192
|
+
const mq = matchMedia("(prefers-reduced-motion: reduce)");
|
|
193
|
+
setCalm(mq.matches);
|
|
194
|
+
const on = () => setCalm(mq.matches);
|
|
195
|
+
mq.addEventListener("change", on);
|
|
196
|
+
return () => mq.removeEventListener("change", on);
|
|
197
|
+
}, [item.video]);
|
|
198
|
+
|
|
199
|
+
if (item.video) {
|
|
200
|
+
const still = calm || paused;
|
|
201
|
+
return (
|
|
202
|
+
<>
|
|
203
|
+
{/* A picture of motion, not content: muted, no controls of its own. */}
|
|
204
|
+
<video
|
|
205
|
+
key={still ? "still" : "moving"}
|
|
206
|
+
src={item.video}
|
|
207
|
+
poster={item.poster ?? item.image}
|
|
208
|
+
muted
|
|
209
|
+
loop
|
|
210
|
+
playsInline
|
|
211
|
+
autoPlay={!still}
|
|
212
|
+
aria-hidden="true"
|
|
213
|
+
className="absolute inset-0 size-full object-cover"
|
|
214
|
+
/>
|
|
215
|
+
{!calm ? (
|
|
216
|
+
<Toggle
|
|
217
|
+
icon={paused ? "play" : "pause"}
|
|
218
|
+
label={paused ? "Play the video" : "Pause the video"}
|
|
219
|
+
pressed={paused}
|
|
220
|
+
onPressedChange={setPaused}
|
|
221
|
+
className="absolute start-3 top-3 z-10"
|
|
222
|
+
/>
|
|
223
|
+
) : null}
|
|
224
|
+
</>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return (
|
|
228
|
+
<div className="absolute inset-0">
|
|
229
|
+
<Image src={item.image} alt={item.alt ?? ""} ratio="square" rounded="none" className="size-full" />
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { isValidElement, useId, useMemo, useState } from "react";
|
|
4
|
+
import { Button, Empty, Notice, Select, Skeleton, cx } from "../../ui";
|
|
5
|
+
import { ProductCard, PromoTile } from "./product-card";
|
|
6
|
+
|
|
7
|
+
// Products (or promotions) as cards in a grid, with facet filters, a sort
|
|
8
|
+
// and a count over it, and an optional lead tile that takes a double
|
|
9
|
+
// cell. Filtering and sorting happen here over `products`, or — with
|
|
10
|
+
// `manual` — in your loader, the block only reporting what was picked.
|
|
11
|
+
|
|
12
|
+
const SORTS = {
|
|
13
|
+
featured: { label: "Featured", compare: null },
|
|
14
|
+
"price-asc": { label: "Price: low to high", compare: (a, b) => (a.price ?? 0) - (b.price ?? 0) },
|
|
15
|
+
"price-desc": { label: "Price: high to low", compare: (a, b) => (b.price ?? 0) - (a.price ?? 0) },
|
|
16
|
+
rating: { label: "Top rated", compare: (a, b) => (b.rating ?? 0) - (a.rating ?? 0) },
|
|
17
|
+
newest: {
|
|
18
|
+
label: "Newest",
|
|
19
|
+
compare: (a, b) => String(b.createdAt ?? "").localeCompare(String(a.createdAt ?? "")),
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_SORTS = ["featured", "price-asc", "price-desc", "rating"];
|
|
23
|
+
|
|
24
|
+
function sortsOf(sort) {
|
|
25
|
+
if (!sort) return [];
|
|
26
|
+
const list = Array.isArray(sort) ? sort : DEFAULT_SORTS;
|
|
27
|
+
return list
|
|
28
|
+
.map((s) => (typeof s === "string" ? { value: s, ...SORTS[s] } : { ...SORTS[s.value], ...s }))
|
|
29
|
+
.filter((s) => s.value && s.label);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const optionsOf = (filter) =>
|
|
33
|
+
(filter.options ?? []).filter((o) => o != null).map((o) => (typeof o === "object" ? o : { value: String(o), label: String(o) }));
|
|
34
|
+
|
|
35
|
+
/** Does `product` carry `value` under `key`? Looked for in the product's
|
|
36
|
+
* own field (a string or a list), in `facets[key]`, and in the values of
|
|
37
|
+
* its option with that key — so a size filter matches the sizes a
|
|
38
|
+
* product is sold in. */
|
|
39
|
+
export function productMatches(product, key, value) {
|
|
40
|
+
const found = [];
|
|
41
|
+
const push = (v) => (Array.isArray(v) ? found.push(...v) : v != null && found.push(v));
|
|
42
|
+
push(product?.[key]);
|
|
43
|
+
push(product?.facets?.[key]);
|
|
44
|
+
const option = product?.options?.find((o) => o.key === key);
|
|
45
|
+
for (const v of option?.values ?? []) if (!(typeof v === "object" && v?.disabled)) push(typeof v === "object" ? v.value : v);
|
|
46
|
+
return found.some((v) => String(typeof v === "object" ? v.value : v) === String(value));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const COLS = {
|
|
50
|
+
product: {
|
|
51
|
+
2: "grid-cols-2",
|
|
52
|
+
3: "grid-cols-2 lg:grid-cols-3",
|
|
53
|
+
4: "grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
54
|
+
},
|
|
55
|
+
promo: {
|
|
56
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
57
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
58
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function LoadingCards({ count, promo, cols }) {
|
|
63
|
+
return (
|
|
64
|
+
<div aria-busy="true" aria-label="Loading products" className={cx("grid gap-3 sm:gap-4", cols)}>
|
|
65
|
+
{Array.from({ length: count }, (_, i) =>
|
|
66
|
+
promo ? (
|
|
67
|
+
<Skeleton key={i} shape="rect" className="h-80 w-full" />
|
|
68
|
+
) : (
|
|
69
|
+
<div key={i} className="grid gap-2 overflow-hidden rounded-lg border border-border bg-card pb-4">
|
|
70
|
+
<Skeleton shape="rect" className="aspect-square w-full" />
|
|
71
|
+
<Skeleton className="mx-4 w-1/3" />
|
|
72
|
+
<Skeleton className="mx-4 w-3/4" />
|
|
73
|
+
<Skeleton className="mx-4 w-1/4" />
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function ProductGrid({
|
|
82
|
+
products,
|
|
83
|
+
card = "product",
|
|
84
|
+
columns,
|
|
85
|
+
filters,
|
|
86
|
+
sort,
|
|
87
|
+
defaultSort,
|
|
88
|
+
manual = false,
|
|
89
|
+
total,
|
|
90
|
+
onQueryChange,
|
|
91
|
+
feature,
|
|
92
|
+
onOpen,
|
|
93
|
+
onAddToCart,
|
|
94
|
+
onFavorite,
|
|
95
|
+
favorites,
|
|
96
|
+
currency = "USD",
|
|
97
|
+
locale,
|
|
98
|
+
addLabel,
|
|
99
|
+
countLabel = (n) => `${n.toLocaleString(locale)} ${n === 1 ? "product" : "products"}`,
|
|
100
|
+
loading = false,
|
|
101
|
+
error,
|
|
102
|
+
onRetry,
|
|
103
|
+
emptyTitle = "No products yet",
|
|
104
|
+
emptyBody,
|
|
105
|
+
label = "Products",
|
|
106
|
+
className,
|
|
107
|
+
}) {
|
|
108
|
+
const id = useId();
|
|
109
|
+
const promo = card === "promo";
|
|
110
|
+
const sorts = sortsOf(sort);
|
|
111
|
+
const facetList = (filters ?? []).filter(Boolean);
|
|
112
|
+
const [picked, setPicked] = useState(() => Object.fromEntries(facetList.map((f) => [f.key, f.value ?? ""])));
|
|
113
|
+
const [order, setOrder] = useState(defaultSort ?? sorts[0]?.value ?? "featured");
|
|
114
|
+
const favSet = new Set(favorites ?? []);
|
|
115
|
+
const active = Object.values(picked).some(Boolean);
|
|
116
|
+
|
|
117
|
+
const shown = useMemo(() => {
|
|
118
|
+
let list = (products ?? []).filter(Boolean);
|
|
119
|
+
if (manual) return list;
|
|
120
|
+
for (const [key, value] of Object.entries(picked)) if (value) list = list.filter((p) => productMatches(p, key, value));
|
|
121
|
+
const cmp = sorts.find((s) => s.value === order)?.compare;
|
|
122
|
+
return cmp ? [...list].sort(cmp) : list;
|
|
123
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
124
|
+
}, [products, picked, order, manual, sort]);
|
|
125
|
+
|
|
126
|
+
function change(nextPicked, nextOrder) {
|
|
127
|
+
setPicked(nextPicked);
|
|
128
|
+
setOrder(nextOrder);
|
|
129
|
+
onQueryChange?.({ filters: Object.fromEntries(Object.entries(nextPicked).filter(([, v]) => v)), sort: nextOrder });
|
|
130
|
+
}
|
|
131
|
+
const clear = () => change(Object.fromEntries(facetList.map((f) => [f.key, ""])), order);
|
|
132
|
+
|
|
133
|
+
const n = columns ?? (promo ? 3 : 4);
|
|
134
|
+
const cols = (COLS[promo ? "promo" : "product"][n] ?? COLS.product[4]);
|
|
135
|
+
const count = manual && typeof total === "number" ? total : shown.length;
|
|
136
|
+
|
|
137
|
+
const toolbar =
|
|
138
|
+
facetList.length || sorts.length ? (
|
|
139
|
+
<div className="flex flex-wrap items-end justify-between gap-3">
|
|
140
|
+
{facetList.length ? (
|
|
141
|
+
<div role="group" aria-label="Filters" className="grid w-full grid-cols-2 gap-2 sm:flex sm:w-auto sm:flex-wrap">
|
|
142
|
+
{facetList.map((f) => (
|
|
143
|
+
<div key={f.key} className="min-w-0 sm:w-40">
|
|
144
|
+
<Select aria-label={f.label} value={picked[f.key] ?? ""} onChange={(e) => change({ ...picked, [f.key]: e.target.value }, order)}>
|
|
145
|
+
<option value="">{f.all ?? `All ${String(f.label).toLowerCase()}`}</option>
|
|
146
|
+
{optionsOf(f).map((o) => (
|
|
147
|
+
<option key={o.value} value={o.value}>
|
|
148
|
+
{o.label}
|
|
149
|
+
</option>
|
|
150
|
+
))}
|
|
151
|
+
</Select>
|
|
152
|
+
</div>
|
|
153
|
+
))}
|
|
154
|
+
{active ? (
|
|
155
|
+
<Button variant="quiet" icon="x" onClick={clear}>
|
|
156
|
+
Clear
|
|
157
|
+
</Button>
|
|
158
|
+
) : null}
|
|
159
|
+
</div>
|
|
160
|
+
) : null}
|
|
161
|
+
<div className="flex w-full items-center justify-between gap-3 sm:ml-auto sm:w-auto">
|
|
162
|
+
<p id={`${id}-count`} data-nums aria-live="polite" className="text-sm text-muted-foreground whitespace-nowrap">
|
|
163
|
+
{loading ? " " : countLabel(count)}
|
|
164
|
+
</p>
|
|
165
|
+
{sorts.length ? (
|
|
166
|
+
<div className="w-44 shrink-0">
|
|
167
|
+
<Select
|
|
168
|
+
aria-label="Sort by"
|
|
169
|
+
value={order}
|
|
170
|
+
onChange={(e) => change(picked, e.target.value)}
|
|
171
|
+
options={sorts.map((s) => ({ value: s.value, label: s.label }))}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
) : null}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
) : null;
|
|
178
|
+
|
|
179
|
+
let body;
|
|
180
|
+
if (loading) body = <LoadingCards count={promo ? n : n * 2} promo={promo} cols={cols} />;
|
|
181
|
+
else if (error) {
|
|
182
|
+
body = (
|
|
183
|
+
<Notice tone="danger" title="The products did not load" action={onRetry ? <Button size="sm" onClick={onRetry}>Try again</Button> : null}>
|
|
184
|
+
{typeof error === "string" ? error : null}
|
|
185
|
+
</Notice>
|
|
186
|
+
);
|
|
187
|
+
} else if (shown.length === 0 && !feature) {
|
|
188
|
+
body = active ? (
|
|
189
|
+
<Empty
|
|
190
|
+
icon="filter"
|
|
191
|
+
title="Nothing matches these filters"
|
|
192
|
+
body="Take one off, or clear them all."
|
|
193
|
+
action={<Button onClick={clear}>Clear filters</Button>}
|
|
194
|
+
/>
|
|
195
|
+
) : (
|
|
196
|
+
<Empty icon="package" title={emptyTitle} body={emptyBody} />
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
const lead = feature ? (
|
|
200
|
+
<li key="feature" className={cx("min-w-0", promo ? "sm:col-span-2 sm:row-span-2" : "col-span-2 lg:row-span-2")}>
|
|
201
|
+
{isValidElement(feature) ? feature : <PromoTile item={feature} large onOpen={feature.onOpen} className="h-full" />}
|
|
202
|
+
</li>
|
|
203
|
+
) : null;
|
|
204
|
+
body = (
|
|
205
|
+
<ul aria-describedby={toolbar ? `${id}-count` : undefined} className={cx("grid gap-3 sm:gap-4", cols)}>
|
|
206
|
+
{lead}
|
|
207
|
+
{shown.map((p, i) => (
|
|
208
|
+
<li key={p.id ?? i} className="min-w-0">
|
|
209
|
+
{promo ? (
|
|
210
|
+
<PromoTile item={p} onOpen={onOpen} className="h-full" />
|
|
211
|
+
) : (
|
|
212
|
+
<ProductCard
|
|
213
|
+
product={p}
|
|
214
|
+
onOpen={onOpen}
|
|
215
|
+
onAddToCart={onAddToCart}
|
|
216
|
+
onFavorite={onFavorite}
|
|
217
|
+
favorite={favSet.has(p.id)}
|
|
218
|
+
currency={currency}
|
|
219
|
+
locale={locale}
|
|
220
|
+
addLabel={addLabel}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
223
|
+
</li>
|
|
224
|
+
))}
|
|
225
|
+
</ul>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<section aria-label={label} className={cx("grid min-w-0 gap-4", className)}>
|
|
231
|
+
{toolbar}
|
|
232
|
+
{body}
|
|
233
|
+
</section>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# RecordForm
|
|
2
|
+
|
|
3
|
+
One record's fields, described as data and drawn with the kit: a label, a
|
|
4
|
+
control, help under it, an error in its place. The fields are grouped into
|
|
5
|
+
titled sections. The form holds the draft, checks each field when you
|
|
6
|
+
leave it (and again as you fix it), and saves the whole record through
|
|
7
|
+
`onSave`. A save that fails keeps every edit on screen and says why — over
|
|
8
|
+
the form, and under each field the server refused.
|
|
9
|
+
|
|
10
|
+
```jsx
|
|
11
|
+
import { RecordForm } from "../components/blocks/record-form"; // a Makersclaw app ("./blocks/record-form" from components/)
|
|
12
|
+
import { RecordForm } from "@/components/makersclaw/blocks/record-form"; // a shadcn project
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It is a client component: render it from a `"use client"` component, and
|
|
16
|
+
load the record in the server surface (README §4).
|
|
17
|
+
|
|
18
|
+
## The data
|
|
19
|
+
|
|
20
|
+
`values` is the record: `{ name: "…", price: 189, tags: ["gift"] }`. Each
|
|
21
|
+
field reads and writes `values[field.name]`.
|
|
22
|
+
|
|
23
|
+
A field:
|
|
24
|
+
|
|
25
|
+
- `name` — the key in `values` (required, unique in the form).
|
|
26
|
+
- `label` — what it is called.
|
|
27
|
+
- `type` — how it is entered (default `"text"`):
|
|
28
|
+
- `text` · `email` · `url` · `tel` · `number` — an Input. `icon`,
|
|
29
|
+
`prefix` ("$") or `suffix` ("kg") make it an InputGroup. A number is
|
|
30
|
+
sent as a number (empty → `null`).
|
|
31
|
+
- `password` — a PasswordInput with show/hide.
|
|
32
|
+
- `textarea` — `rows` (4).
|
|
33
|
+
- `select` — a native Select over `options`; `placeholder` is the empty
|
|
34
|
+
first choice.
|
|
35
|
+
- `combobox` — a searchable Combobox over `options` (with `description`
|
|
36
|
+
per option); `multiple: true` holds an array.
|
|
37
|
+
- `tags` — a Combobox that holds an array and takes new words.
|
|
38
|
+
- `radio` — a RadioGroup; `orientation: "horizontal"` for a short row.
|
|
39
|
+
- `choice-cards` — the RadioGroup's card look: each option
|
|
40
|
+
`{ value, label, description?, price?, per?, badge? }` — a plan, a
|
|
41
|
+
package, a mode.
|
|
42
|
+
- `checkbox` — one box that must be ticked when `required` ("I agree…");
|
|
43
|
+
`description` under its label. Holds `true`/`false`.
|
|
44
|
+
- `checkboxes` — several boxes over `options`; holds an array of values.
|
|
45
|
+
- `switch` — an on/off with `description`; holds `true`/`false`.
|
|
46
|
+
- `date` — a DatePicker; the value is `"YYYY-MM-DD"`. `min`, `max`,
|
|
47
|
+
`captionLayout: "dropdown"` for a birth date.
|
|
48
|
+
- `time` — a TimeInput; `"HH:MM"`.
|
|
49
|
+
- `rating` — Rating stars as an input; `max` (5). Holds a whole number.
|
|
50
|
+
- `photo` — an Avatar with Upload and Remove. `nameFrom(values)` gives
|
|
51
|
+
the initials shown with no picture. Holds a URL string, or
|
|
52
|
+
`{ src, file, name }` after a pick: upload `file` in `onSave`.
|
|
53
|
+
- `images` — a row of thumbnails, each removable, and Add while under
|
|
54
|
+
`maxItems`. Holds an array of URL strings or `{ src, file, name }`.
|
|
55
|
+
- `static` — the value as text, not editable ("Verified"); `format(value,
|
|
56
|
+
values)` to word it.
|
|
57
|
+
- `required` — it must hold something (a ticked box, a chosen option).
|
|
58
|
+
- `options` — for the choosing types: `[{ value, label, description?,
|
|
59
|
+
disabled? }]` or plain strings.
|
|
60
|
+
- `help` — the muted line under it (hidden while an error shows).
|
|
61
|
+
- `placeholder`, `autoComplete`, `min`, `max`, `step`, `disabled`,
|
|
62
|
+
`readOnly` — as the control's own.
|
|
63
|
+
- `width` — `"full"` spans both columns; `"half"` keeps a wide type in one.
|
|
64
|
+
Text areas, choices, boxes, switches, a photo and images are full by
|
|
65
|
+
default.
|
|
66
|
+
- `when(values)` — shown only while it returns true; a hidden field is not
|
|
67
|
+
checked and not sent.
|
|
68
|
+
- `defaultValue` — used when `values` has none.
|
|
69
|
+
|
|
70
|
+
Validation, first broken rule wins, one message per field:
|
|
71
|
+
|
|
72
|
+
- `required` (`requiredMessage` to word it);
|
|
73
|
+
- the type's own shape: an email, a full `https://` address, a number
|
|
74
|
+
within `min`/`max`;
|
|
75
|
+
- `minLength`, `maxLength`;
|
|
76
|
+
- `pattern` — a regular expression the whole text must match, with
|
|
77
|
+
`patternMessage`;
|
|
78
|
+
- `matches` — the name of another field this one must equal (a confirm),
|
|
79
|
+
with `matchMessage`;
|
|
80
|
+
- `maxItems` — for arrays (tags, images, checkboxes);
|
|
81
|
+
- `validate(value, values)` — return a message, or nothing when fine.
|
|
82
|
+
|
|
83
|
+
## Props
|
|
84
|
+
|
|
85
|
+
- `fields` — the fields, one untitled section. Or:
|
|
86
|
+
- `sections` — `[{ title?, description?, columns?, fields }]`: titled
|
|
87
|
+
groups in order.
|
|
88
|
+
- `values` — the record as it stands. Pass a different record and the form
|
|
89
|
+
starts over from it (and forgets unsaved edits).
|
|
90
|
+
- `onSave(values, { changed })` — save the record. `values` is every
|
|
91
|
+
shown field, text trimmed, numbers as numbers; `changed` names the keys
|
|
92
|
+
that differ from `values`. Return `{ ok: true }`, or `{ ok: false,
|
|
93
|
+
error?, errors? }` where `errors` is `{ fieldName: "message" }` — each
|
|
94
|
+
shows under its field and the first one is focused. A throw counts as
|
|
95
|
+
failed. In a Makersclaw app it is one line over `updateRow`:
|
|
96
|
+
`onSave={(v) => updateRow({ source: "public.products", id: p.id, patch: v })}`.
|
|
97
|
+
- `container` — `"card"` (default: a Card with `title` and `description`)
|
|
98
|
+
· `"page"` (no box: a page heading, then the sections) · `"dialog"` (a
|
|
99
|
+
centred Sheet; on a phone a bottom sheet). A dialog needs `open` and
|
|
100
|
+
`onClose`; it closes on a good save, and opens on the record as it
|
|
101
|
+
stands, never on last time's edits.
|
|
102
|
+
- `layout` — `"stacked"` (default: each section's title over its fields)
|
|
103
|
+
or `"split"` (the title and description on the left, the fields on the
|
|
104
|
+
right, a rule between sections, from 768px up). A dialog is always
|
|
105
|
+
stacked.
|
|
106
|
+
- `columns` — `2` (default: two columns from 640px) or `1`. A dialog is
|
|
107
|
+
one column.
|
|
108
|
+
- `title`, `description` — the card's, the page's, or the dialog's.
|
|
109
|
+
- `onCancel()` — shows Cancel; it drops the edits first. Without it, a
|
|
110
|
+
Discard button shows while there are edits.
|
|
111
|
+
- `onDelete()` — shows a danger button at the start of the footer (not in
|
|
112
|
+
a dialog) that asks first. Return `{ ok }` like `onSave`.
|
|
113
|
+
- `confirmDelete` — the ask: `{ title?, body?, confirmLabel?,
|
|
114
|
+
consequences?, requireText?, acknowledge? }` (the kit Confirm's).
|
|
115
|
+
- `deleteLabel` — `"Delete"`.
|
|
116
|
+
- `onChange(values)` — every edit, for a live preview beside the form.
|
|
117
|
+
- `submitLabel` (`"Save changes"`) · `cancelLabel` (`"Cancel"`) ·
|
|
118
|
+
`savedLabel` (`"Saved"`, shown for three seconds after a good save).
|
|
119
|
+
- `requireChanges` — `true` (default): Save waits for an edit. Pass
|
|
120
|
+
`false` for a form that creates a record.
|
|
121
|
+
- `loading` — skeleton fields while the record loads.
|
|
122
|
+
- `error` — the record could not be loaded: a Notice in place of the
|
|
123
|
+
fields; `onRetry` adds "Try again".
|
|
124
|
+
- `emptyLabel` — shown when there are no fields.
|
|
125
|
+
- `className` — layout around it (width, margin). Never colour.
|
|
126
|
+
|
|
127
|
+
## Example
|
|
128
|
+
|
|
129
|
+
```jsx
|
|
130
|
+
// components/ProductEditor.jsx — client
|
|
131
|
+
"use client";
|
|
132
|
+
import { updateRow, deleteRow } from "../lib/actions";
|
|
133
|
+
import { RecordForm } from "./blocks/record-form";
|
|
134
|
+
|
|
135
|
+
const FIELDS = [
|
|
136
|
+
{ name: "name", label: "Product name", required: true },
|
|
137
|
+
{ name: "price", label: "Price", type: "number", prefix: "$", min: 0, required: true },
|
|
138
|
+
{ name: "category", label: "Category", type: "select", options: ["Outerwear", "Tops", "Footwear"] },
|
|
139
|
+
{ name: "launch", label: "Launch date", type: "date" },
|
|
140
|
+
{ name: "details", label: "Details", type: "textarea" },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
export default function ProductEditor({ product }) {
|
|
144
|
+
return (
|
|
145
|
+
<RecordForm
|
|
146
|
+
title="Edit product"
|
|
147
|
+
fields={FIELDS}
|
|
148
|
+
values={product}
|
|
149
|
+
onSave={(values) => updateRow({ source: "public.products", id: product.id, patch: values })}
|
|
150
|
+
onDelete={() => deleteRow({ source: "public.products", id: product.id })}
|
|
151
|
+
deleteLabel="Remove product"
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
In a dialog, from a list:
|
|
158
|
+
|
|
159
|
+
```jsx
|
|
160
|
+
const [editing, setEditing] = useState(null);
|
|
161
|
+
|
|
162
|
+
<RecordForm
|
|
163
|
+
container="dialog"
|
|
164
|
+
open={Boolean(editing)}
|
|
165
|
+
onClose={() => setEditing(null)}
|
|
166
|
+
title="Edit member"
|
|
167
|
+
values={editing ?? {}}
|
|
168
|
+
sections={[
|
|
169
|
+
{ title: "Profile", fields: [{ name: "photo", label: "Photo", type: "photo" }, { name: "name", label: "Name", required: true }] },
|
|
170
|
+
{ title: "Access", fields: [{ name: "role", label: "Role", type: "radio", options: ["Admin", "Member", "Viewer"] }] },
|
|
171
|
+
]}
|
|
172
|
+
onSave={async (values) => {
|
|
173
|
+
const res = await updateRow({ source: "public.members", id: editing.id, patch: values });
|
|
174
|
+
return res.ok ? res : { ok: false, errors: res.error?.includes("email") ? { email: "Already in use." } : undefined, error: res.error };
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## What it will not do
|
|
180
|
+
|
|
181
|
+
- Upload files: a picked photo or image arrives in `onSave` as
|
|
182
|
+
`{ src, file }` — upload `file`, store the URL.
|
|
183
|
+
- Save field by field: it saves the record with one button. Settings that
|
|
184
|
+
each save when flipped are `settings-list`.
|
|
185
|
+
- Sign in, sign up or change a password with rules beside it: that is
|
|
186
|
+
`auth-form`.
|
|
187
|
+
- Lay out a field by hand: a field it cannot draw is your own form in
|
|
188
|
+
`components/`, built from the kit's `Field` and controls.
|
|
189
|
+
|
|
190
|
+
A form it cannot be is your own component in `components/`, built from the
|
|
191
|
+
kit — not an edit to this block. Its files are fixed: `npm run check`
|
|
192
|
+
refuses a change, and `makersclaw add record-form` restores them.
|
|
193
|
+
|
|
194
|
+
## Credits
|
|
195
|
+
|
|
196
|
+
Shapes from Creative Tim UI (https://github.com/creativetimofficial/ui, MIT) and blocks.so (https://github.com/ephraimduncan/blocks, MIT); this is a rewrite on the Makersclaw kit.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "record-form",
|
|
3
|
+
"title": "RecordForm",
|
|
4
|
+
"description": "Edit or create one record from a list of fields given as data (type, label, required, options, help, validation), grouped into titled sections — text, email, number, password, select, combobox, tags, radio, choice cards with prices, checkboxes, switch, date, time, rating, a photo and a row of images. It checks as you type, saves through an async onSave that answers { ok } or per-field errors shown under each field, and sits in a card, on a page (sections split title-left) or in a dialog.",
|
|
5
|
+
"useWhen": "A screen edits one record's fields: a profile, a product, a team member, account details, a sign-up or application form, a settings page of fields with one Save. Not for a list of records (data-table), for toggles that each save on their own (settings-list), or for sign-in (auth-form).",
|
|
6
|
+
"tags": ["form", "record", "edit", "create", "fields", "sections", "validation", "save", "profile", "product", "dialog", "settings", "crud", "choice", "plan"],
|
|
7
|
+
"exports": ["RecordForm"],
|
|
8
|
+
"files": ["index.js", "record-form.jsx", "record-fields.jsx", "rules.js"],
|
|
9
|
+
"requires": []
|
|
10
|
+
}
|