makersclaw 0.2.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/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/install.mjs +18 -0
- package/src/shadcn.mjs +8 -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/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,721 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Whiteboard — a full-page freeform board on sketch-canvas: a top bar with
|
|
4
|
+
// the board's title (click to rename), the save state, a Board / Text view
|
|
5
|
+
// switch, Export PNG and Share; under it the canvas with sketch-canvas's
|
|
6
|
+
// rail, context toolbar, zoom and undo. An empty board offers starter
|
|
7
|
+
// templates (blank, grid, 2×2 matrix). The Text view is the board's words
|
|
8
|
+
// as a list — the accessible alternative to the drawing, and what an agent
|
|
9
|
+
// reads.
|
|
10
|
+
//
|
|
11
|
+
// With `onCreateTask`, a selected note or text gets a "Create task" button
|
|
12
|
+
// in the top bar; the element keeps a mark (customData.task, drawn as a
|
|
13
|
+
// small badge over it) once the task exists.
|
|
14
|
+
//
|
|
15
|
+
// Saves are the page's: `onSave(sketch)` and `onRename(title)` are async
|
|
16
|
+
// and answer { ok }; the top bar shows "Saving…", "Saved", or the error
|
|
17
|
+
// with a Retry. The canvas stays mounted under the Text view, so switching
|
|
18
|
+
// views never loses an edit or the undo history.
|
|
19
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
20
|
+
import { Button, Field, Icon, IconButton, Input, Kbd, Popover, ToggleGroup, Tooltip, cx, toast } from "../../ui";
|
|
21
|
+
import { SketchCanvas, isSketchEmpty, sketchItems, sketchToText, useSketchCanvas } from "../sketch-canvas";
|
|
22
|
+
import { WHITEBOARD_TEMPLATES, fileNameFor, templateSketch } from "./templates";
|
|
23
|
+
|
|
24
|
+
const IDLE = { state: "idle" };
|
|
25
|
+
|
|
26
|
+
async function settle(fn, ...args) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fn(...args);
|
|
29
|
+
return res?.ok ? { state: "saved" } : { state: "error", error: res?.error };
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { state: "error", error: e?.message };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function Whiteboard({
|
|
36
|
+
title,
|
|
37
|
+
defaultTitle = "Untitled board",
|
|
38
|
+
onRename,
|
|
39
|
+
value,
|
|
40
|
+
defaultValue,
|
|
41
|
+
onChange,
|
|
42
|
+
onSave,
|
|
43
|
+
changeDelay,
|
|
44
|
+
templates = WHITEBOARD_TEMPLATES,
|
|
45
|
+
onShare,
|
|
46
|
+
shareLabel = "Share",
|
|
47
|
+
onExport,
|
|
48
|
+
onBack,
|
|
49
|
+
backLabel = "Back",
|
|
50
|
+
meta,
|
|
51
|
+
readOnly = false,
|
|
52
|
+
defaultView = "board",
|
|
53
|
+
canvasRef,
|
|
54
|
+
onCreateTask,
|
|
55
|
+
onOpenTask,
|
|
56
|
+
taskLabel = "Create task",
|
|
57
|
+
className,
|
|
58
|
+
}) {
|
|
59
|
+
const own = useSketchCanvas();
|
|
60
|
+
const ref = canvasRef ?? own;
|
|
61
|
+
|
|
62
|
+
// ---- title --------------------------------------------------------------
|
|
63
|
+
const [ownTitle, setOwnTitle] = useState(defaultTitle);
|
|
64
|
+
const [pendingTitle, setPendingTitle] = useState(null); // typed, not yet confirmed by onRename
|
|
65
|
+
const shownTitle = pendingTitle ?? (title !== undefined ? title : ownTitle);
|
|
66
|
+
const [titleSave, setTitleSave] = useState(IDLE);
|
|
67
|
+
const titleSeq = useRef(0);
|
|
68
|
+
|
|
69
|
+
const rename = useCallback(
|
|
70
|
+
async (next) => {
|
|
71
|
+
if (title === undefined) setOwnTitle(next);
|
|
72
|
+
if (!onRename) return;
|
|
73
|
+
const seq = ++titleSeq.current;
|
|
74
|
+
setPendingTitle(next);
|
|
75
|
+
setTitleSave({ state: "saving" });
|
|
76
|
+
const res = await settle(onRename, next);
|
|
77
|
+
if (seq !== titleSeq.current) return;
|
|
78
|
+
setTitleSave(res);
|
|
79
|
+
if (res.state === "saved") setPendingTitle(null);
|
|
80
|
+
},
|
|
81
|
+
[title, onRename],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// ---- the sketch ---------------------------------------------------------
|
|
85
|
+
const [sketch, setSketch] = useState(() => value ?? defaultValue ?? null);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (value !== undefined) setSketch(value);
|
|
88
|
+
}, [value]);
|
|
89
|
+
const empty = isSketchEmpty(sketch);
|
|
90
|
+
const [blank, setBlank] = useState(false); // "Blank" chosen: the plain hint, not the templates
|
|
91
|
+
const [sketchSave, setSketchSave] = useState(IDLE);
|
|
92
|
+
const sketchSeq = useRef(0);
|
|
93
|
+
const lastSketch = useRef(null);
|
|
94
|
+
const cbs = useRef({});
|
|
95
|
+
cbs.current = { onChange, onSave };
|
|
96
|
+
|
|
97
|
+
const saveSketch = useCallback(async (s) => {
|
|
98
|
+
const fn = cbs.current.onSave;
|
|
99
|
+
if (!fn) return;
|
|
100
|
+
const seq = ++sketchSeq.current;
|
|
101
|
+
setSketchSave({ state: "saving" });
|
|
102
|
+
const res = await settle(fn, s);
|
|
103
|
+
if (seq !== sketchSeq.current) return;
|
|
104
|
+
setSketchSave(res);
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
const handleChange = useCallback(
|
|
108
|
+
(s) => {
|
|
109
|
+
lastSketch.current = s;
|
|
110
|
+
setSketch(s);
|
|
111
|
+
if (isSketchEmpty(s)) setBlank(false);
|
|
112
|
+
cbs.current.onChange?.(s);
|
|
113
|
+
saveSketch(s);
|
|
114
|
+
},
|
|
115
|
+
[saveSketch],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const applyTemplate = (t) => {
|
|
119
|
+
const h = ref.current;
|
|
120
|
+
if (!h) return;
|
|
121
|
+
const s = templateSketch(t);
|
|
122
|
+
if (!s) {
|
|
123
|
+
setBlank(true);
|
|
124
|
+
h.focus();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
h.setScene(s);
|
|
128
|
+
h.setTool("select");
|
|
129
|
+
requestAnimationFrame(() => h.zoomToFit({ animate: false }));
|
|
130
|
+
h.focus();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ---- export -------------------------------------------------------------
|
|
134
|
+
const [exporting, setExporting] = useState(false);
|
|
135
|
+
const [exportError, setExportError] = useState(null);
|
|
136
|
+
const exportPng = async () => {
|
|
137
|
+
const h = ref.current;
|
|
138
|
+
if (!h) return;
|
|
139
|
+
setExporting(true);
|
|
140
|
+
setExportError(null);
|
|
141
|
+
try {
|
|
142
|
+
const blob = await h.exportImage();
|
|
143
|
+
if (onExport) {
|
|
144
|
+
const res = await settle(onExport, blob, { title: shownTitle, fileName: fileNameFor(shownTitle) });
|
|
145
|
+
if (res.state === "error") setExportError(res.error || "Couldn't export");
|
|
146
|
+
} else {
|
|
147
|
+
const url = URL.createObjectURL(blob);
|
|
148
|
+
const a = document.createElement("a");
|
|
149
|
+
a.href = url;
|
|
150
|
+
a.download = fileNameFor(shownTitle);
|
|
151
|
+
document.body.appendChild(a);
|
|
152
|
+
a.click();
|
|
153
|
+
a.remove();
|
|
154
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
setExportError(e?.message || "Couldn't export");
|
|
158
|
+
} finally {
|
|
159
|
+
setExporting(false);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ---- tasks from what is on the board -----------------------------------
|
|
164
|
+
// Excalidraw tells us every change (selection, scroll, zoom); from it we
|
|
165
|
+
// read what could become a task (selected notes and texts that are not
|
|
166
|
+
// one yet) and where the badges of those that are sit on screen.
|
|
167
|
+
const [handle, setHandle] = useState(null);
|
|
168
|
+
const [picked, setPicked] = useState([]); // [{ key, ids, text }]
|
|
169
|
+
const [marks, setMarks] = useState([]); // [{ key, x, y, title, id }]
|
|
170
|
+
const [pending, setPending] = useState(() => new Map()); // key → title
|
|
171
|
+
const pendingRef = useRef(pending);
|
|
172
|
+
pendingRef.current = pending;
|
|
173
|
+
const keys = useRef({ picked: "", marks: "" });
|
|
174
|
+
const scene = useRef(null);
|
|
175
|
+
const canTask = Boolean(onCreateTask) && !readOnly;
|
|
176
|
+
|
|
177
|
+
const readScene = useCallback((elements, appState) => {
|
|
178
|
+
scene.current = { elements, appState };
|
|
179
|
+
const live = (elements ?? []).filter((el) => !el.isDeleted);
|
|
180
|
+
const byId = new Map(live.map((el) => [el.id, el]));
|
|
181
|
+
const sel = appState?.selectedElementIds ?? {};
|
|
182
|
+
const found = new Map();
|
|
183
|
+
for (const el of live) {
|
|
184
|
+
if (!sel[el.id]) continue;
|
|
185
|
+
const found1 = taskSource(el, byId);
|
|
186
|
+
if (!found1 || found.has(found1.key) || isTask(found1, byId) || pendingRef.current.has(found1.key)) continue;
|
|
187
|
+
found.set(found1.key, found1);
|
|
188
|
+
}
|
|
189
|
+
const nextPicked = [...found.values()].sort((a, b) => a.y - b.y || a.x - b.x);
|
|
190
|
+
const pk = nextPicked.map((p) => `${p.key}:${p.text}`).join("|");
|
|
191
|
+
if (pk !== keys.current.picked) {
|
|
192
|
+
keys.current.picked = pk;
|
|
193
|
+
setPicked(nextPicked);
|
|
194
|
+
}
|
|
195
|
+
// Badges: at the top-right corner of each converted note, in screen px.
|
|
196
|
+
const z = appState?.zoom?.value ?? 1;
|
|
197
|
+
const sx = appState?.scrollX ?? 0;
|
|
198
|
+
const sy = appState?.scrollY ?? 0;
|
|
199
|
+
const w = appState?.width ?? Infinity;
|
|
200
|
+
const h = appState?.height ?? Infinity;
|
|
201
|
+
const nextMarks = [];
|
|
202
|
+
for (const el of live) {
|
|
203
|
+
if (el.type === "text" && el.containerId && byId.has(el.containerId)) continue;
|
|
204
|
+
const task = el.customData?.task;
|
|
205
|
+
const title = task?.title ?? pendingRef.current.get(el.id);
|
|
206
|
+
if (!task && !pendingRef.current.has(el.id)) continue;
|
|
207
|
+
const x = Math.round((el.x + el.width + sx) * z);
|
|
208
|
+
const y = Math.round((el.y + sy) * z);
|
|
209
|
+
if (x < -40 || y < -20 || x > w + 40 || y > h + 20) continue;
|
|
210
|
+
nextMarks.push({ key: el.id, x, y, title, id: task?.id ?? null, pending: !task });
|
|
211
|
+
}
|
|
212
|
+
const mk = JSON.stringify(nextMarks);
|
|
213
|
+
if (mk !== keys.current.marks) {
|
|
214
|
+
keys.current.marks = mk;
|
|
215
|
+
setMarks(nextMarks);
|
|
216
|
+
}
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
const api = handle?.api;
|
|
221
|
+
if (!api?.onChange) return;
|
|
222
|
+
readScene(api.getSceneElements(), api.getAppState());
|
|
223
|
+
return api.onChange((els, st) => readScene(els, st));
|
|
224
|
+
}, [handle, readScene]);
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
// A pending mark came or went: redraw the badges.
|
|
227
|
+
keys.current.marks = "";
|
|
228
|
+
if (scene.current) readScene(scene.current.elements, scene.current.appState);
|
|
229
|
+
}, [pending, readScene]);
|
|
230
|
+
|
|
231
|
+
/** Write (or clear) the task mark on the elements a task came from. */
|
|
232
|
+
const markElements = (ids, task) => {
|
|
233
|
+
const api = (handle ?? ref.current)?.api;
|
|
234
|
+
if (!api) return;
|
|
235
|
+
const set = new Set(ids);
|
|
236
|
+
const els = api.getSceneElementsIncludingDeleted().map((el) => {
|
|
237
|
+
if (!set.has(el.id)) return el;
|
|
238
|
+
const { task: _old, ...rest } = el.customData ?? {};
|
|
239
|
+
return {
|
|
240
|
+
...el,
|
|
241
|
+
customData: task ? { ...rest, task } : rest,
|
|
242
|
+
version: (el.version ?? 1) + 1,
|
|
243
|
+
versionNonce: Math.floor(Math.random() * 2 ** 31),
|
|
244
|
+
updated: Date.now(),
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
api.updateScene({ elements: els });
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const [taskOpen, setTaskOpen] = useState(false);
|
|
251
|
+
const [taskDraft, setTaskDraft] = useState("");
|
|
252
|
+
const createTasks = async (items, override) => {
|
|
253
|
+
setTaskOpen(false);
|
|
254
|
+
const list = items.map((it) => ({ ...it, title: clipTitle(items.length === 1 && override?.trim() ? override : it.text) }));
|
|
255
|
+
setPending((m) => {
|
|
256
|
+
const next = new Map(m);
|
|
257
|
+
for (const it of list) next.set(it.key, it.title);
|
|
258
|
+
return next;
|
|
259
|
+
});
|
|
260
|
+
const results = await Promise.all(
|
|
261
|
+
list.map(async (it) => {
|
|
262
|
+
let res;
|
|
263
|
+
try {
|
|
264
|
+
res = await onCreateTask({ title: it.title, sourceIds: it.ids });
|
|
265
|
+
} catch (e) {
|
|
266
|
+
res = { ok: false, error: e?.message };
|
|
267
|
+
}
|
|
268
|
+
const ok = Boolean(res) && res.ok !== false;
|
|
269
|
+
if (ok) markElements(it.ids, { id: res?.id ?? null, title: it.title });
|
|
270
|
+
return { it, ok, id: res?.id ?? null, error: res?.error };
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
setPending((m) => {
|
|
274
|
+
const next = new Map(m);
|
|
275
|
+
for (const it of list) next.delete(it.key);
|
|
276
|
+
return next;
|
|
277
|
+
});
|
|
278
|
+
const done = results.filter((r) => r.ok);
|
|
279
|
+
const failed = results.filter((r) => !r.ok);
|
|
280
|
+
if (done.length) {
|
|
281
|
+
const one = done.length === 1 ? done[0] : null;
|
|
282
|
+
toast.success(one ? `Task created: ${one.it.title}` : `${done.length} tasks created`, {
|
|
283
|
+
action: one && onOpenTask ? { label: "Open", onClick: () => onOpenTask({ id: one.id, title: one.it.title, sourceIds: one.it.ids }) } : undefined,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (failed.length) {
|
|
287
|
+
toast.error(failed.length === 1 ? `Couldn't create “${failed[0].it.title}”` : `Couldn't create ${failed.length} tasks`, {
|
|
288
|
+
description: failed[0].error || "Nothing was created. Try again.",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// ---- view ---------------------------------------------------------------
|
|
294
|
+
const [view, setView] = useState(defaultView === "text" ? "text" : "board");
|
|
295
|
+
const textView = view === "text";
|
|
296
|
+
|
|
297
|
+
const retry = () => {
|
|
298
|
+
if (sketchSave.state === "error") saveSketch(lastSketch.current ?? ref.current?.getScene() ?? sketch);
|
|
299
|
+
if (titleSave.state === "error") rename(shownTitle);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const sized = /(^|\s)(h-|min-h-|size-|flex-1|grow)/.test(className ?? "");
|
|
303
|
+
const hasTemplates = Array.isArray(templates) && templates.length > 0;
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<section
|
|
307
|
+
aria-label={shownTitle}
|
|
308
|
+
className={cx("flex flex-col overflow-hidden rounded-lg border border-border bg-background text-foreground", !sized && "h-[40rem]", className)}
|
|
309
|
+
>
|
|
310
|
+
<header className="flex min-h-14 shrink-0 items-center gap-1 border-b border-border bg-card px-2 py-1.5 text-card-foreground sm:gap-2 sm:px-3">
|
|
311
|
+
{onBack ? <IconButton icon="arrow-left" label={backLabel} size="sm" onClick={onBack} /> : null}
|
|
312
|
+
<span aria-hidden="true" className="hidden size-8 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground sm:grid">
|
|
313
|
+
<Icon name="pencil" size={16} />
|
|
314
|
+
</span>
|
|
315
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
316
|
+
<TitleField value={shownTitle} readOnly={readOnly} onCommit={rename} />
|
|
317
|
+
{meta ? <span className="hidden min-w-0 truncate text-xs text-muted-foreground lg:inline">{meta}</span> : null}
|
|
318
|
+
</div>
|
|
319
|
+
<SaveStatus states={[sketchSave, titleSave]} onRetry={retry} />
|
|
320
|
+
<ToggleGroup
|
|
321
|
+
size="sm"
|
|
322
|
+
aria-label="View"
|
|
323
|
+
value={view}
|
|
324
|
+
onChange={setView}
|
|
325
|
+
items={[
|
|
326
|
+
{ value: "board", label: "Board view", icon: "pencil", iconOnly: true },
|
|
327
|
+
{ value: "text", label: "Text view", icon: "list", iconOnly: true },
|
|
328
|
+
]}
|
|
329
|
+
/>
|
|
330
|
+
{canTask && picked.length ? (
|
|
331
|
+
<Popover
|
|
332
|
+
open={taskOpen}
|
|
333
|
+
onOpenChange={(o) => {
|
|
334
|
+
if (o) setTaskDraft(picked[0]?.text ?? "");
|
|
335
|
+
setTaskOpen(o);
|
|
336
|
+
}}
|
|
337
|
+
label={picked.length === 1 ? taskLabel : `Create ${picked.length} tasks`}
|
|
338
|
+
align="end"
|
|
339
|
+
width="md"
|
|
340
|
+
trigger={(p) => (
|
|
341
|
+
<Button {...p} size="sm" icon="check-circle" aria-label={picked.length === 1 ? taskLabel : `Create ${picked.length} tasks`}>
|
|
342
|
+
<span className="max-sm:hidden">{picked.length === 1 ? taskLabel : `Create ${picked.length} tasks`}</span>
|
|
343
|
+
</Button>
|
|
344
|
+
)}
|
|
345
|
+
>
|
|
346
|
+
{picked.length === 1 ? (
|
|
347
|
+
<form
|
|
348
|
+
className="grid gap-3"
|
|
349
|
+
onSubmit={(e) => {
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
if (taskDraft.trim()) createTasks(picked, taskDraft);
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
<Field id="wb-task-title" label="Task">
|
|
355
|
+
<Input id="wb-task-title" value={taskDraft} maxLength={200} onChange={(e) => setTaskDraft(e.target.value)} onFocus={(e) => e.target.select()} />
|
|
356
|
+
</Field>
|
|
357
|
+
<p className="text-xs text-muted-foreground">The note stays on the board, with a mark that it's now a task.</p>
|
|
358
|
+
<div className="flex justify-end gap-2">
|
|
359
|
+
<Button size="sm" variant="quiet" onClick={() => setTaskOpen(false)}>
|
|
360
|
+
Cancel
|
|
361
|
+
</Button>
|
|
362
|
+
<Button size="sm" variant="primary" type="submit" disabled={!taskDraft.trim()}>
|
|
363
|
+
{taskLabel}
|
|
364
|
+
</Button>
|
|
365
|
+
</div>
|
|
366
|
+
</form>
|
|
367
|
+
) : (
|
|
368
|
+
<div className="grid gap-3">
|
|
369
|
+
<p className="text-sm font-medium">One task for each note</p>
|
|
370
|
+
<ul className="grid max-h-48 gap-1 overflow-y-auto">
|
|
371
|
+
{picked.map((it) => (
|
|
372
|
+
<li key={it.key} className="flex items-start gap-2 text-sm">
|
|
373
|
+
<Icon name="check-circle" size={14} className="mt-0.5 shrink-0 text-muted-foreground" />
|
|
374
|
+
<span className="min-w-0 break-words">{clipTitle(it.text)}</span>
|
|
375
|
+
</li>
|
|
376
|
+
))}
|
|
377
|
+
</ul>
|
|
378
|
+
<div className="flex justify-end gap-2">
|
|
379
|
+
<Button size="sm" variant="quiet" onClick={() => setTaskOpen(false)}>
|
|
380
|
+
Cancel
|
|
381
|
+
</Button>
|
|
382
|
+
<Button size="sm" variant="primary" onClick={() => createTasks(picked)}>
|
|
383
|
+
Create {picked.length} tasks
|
|
384
|
+
</Button>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
)}
|
|
388
|
+
</Popover>
|
|
389
|
+
) : null}
|
|
390
|
+
<Tooltip label={exportError ? `Export failed: ${exportError}` : "Export as PNG"}>
|
|
391
|
+
<Button size="sm" icon={exportError ? "warning" : "download"} loading={exporting} disabled={empty} onClick={exportPng} aria-label={exportError ? `Export PNG — failed: ${exportError}. Try again` : "Export PNG"}>
|
|
392
|
+
<span className="max-sm:hidden">Export</span>
|
|
393
|
+
</Button>
|
|
394
|
+
</Tooltip>
|
|
395
|
+
{onShare ? (
|
|
396
|
+
<Button size="sm" variant="primary" icon="share" onClick={onShare} aria-label={shareLabel}>
|
|
397
|
+
<span className="max-sm:hidden">{shareLabel}</span>
|
|
398
|
+
</Button>
|
|
399
|
+
) : null}
|
|
400
|
+
</header>
|
|
401
|
+
|
|
402
|
+
<div className="relative min-h-0 flex-1">
|
|
403
|
+
<div className="absolute inset-0" inert={textView || undefined} aria-hidden={textView || undefined}>
|
|
404
|
+
<SketchCanvas
|
|
405
|
+
label={shownTitle}
|
|
406
|
+
value={value}
|
|
407
|
+
defaultValue={defaultValue}
|
|
408
|
+
onChange={handleChange}
|
|
409
|
+
changeDelay={changeDelay}
|
|
410
|
+
readOnly={readOnly}
|
|
411
|
+
canvasRef={ref}
|
|
412
|
+
onReady={setHandle}
|
|
413
|
+
empty={hasTemplates && !blank ? <Starter templates={templates} onPick={applyTemplate} /> : undefined}
|
|
414
|
+
className="h-full rounded-none border-0"
|
|
415
|
+
/>
|
|
416
|
+
{marks.length ? <TaskMarks marks={marks} onOpen={onOpenTask} /> : null}
|
|
417
|
+
</div>
|
|
418
|
+
{textView ? <TextView sketch={sketch} onBoard={() => setView("board")} readOnly={readOnly} /> : null}
|
|
419
|
+
</div>
|
|
420
|
+
</section>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** The title: a button that becomes a field; Enter or leaving saves, Esc
|
|
425
|
+
* puts it back. */
|
|
426
|
+
function TitleField({ value, readOnly, onCommit }) {
|
|
427
|
+
const [editing, setEditing] = useState(false);
|
|
428
|
+
const [draft, setDraft] = useState(value);
|
|
429
|
+
const done = useRef(false);
|
|
430
|
+
if (readOnly) return <h2 className="min-w-0 truncate px-1 text-sm font-semibold">{value}</h2>;
|
|
431
|
+
if (!editing) {
|
|
432
|
+
return (
|
|
433
|
+
<Tooltip label="Rename">
|
|
434
|
+
<Button
|
|
435
|
+
variant="quiet"
|
|
436
|
+
size="sm"
|
|
437
|
+
align="start"
|
|
438
|
+
className="min-w-0 max-w-full"
|
|
439
|
+
aria-label={`${value}. Rename board`}
|
|
440
|
+
onClick={() => {
|
|
441
|
+
setDraft(value);
|
|
442
|
+
done.current = false;
|
|
443
|
+
setEditing(true);
|
|
444
|
+
}}
|
|
445
|
+
>
|
|
446
|
+
<span className="truncate font-semibold">{value}</span>
|
|
447
|
+
</Button>
|
|
448
|
+
</Tooltip>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const finish = (save) => {
|
|
452
|
+
if (done.current) return;
|
|
453
|
+
done.current = true;
|
|
454
|
+
setEditing(false);
|
|
455
|
+
const next = draft.trim();
|
|
456
|
+
if (save && next && next !== value) onCommit(next);
|
|
457
|
+
};
|
|
458
|
+
return (
|
|
459
|
+
<Input
|
|
460
|
+
autoFocus
|
|
461
|
+
aria-label="Board title"
|
|
462
|
+
value={draft}
|
|
463
|
+
maxLength={120}
|
|
464
|
+
className="min-w-0 max-w-sm font-semibold"
|
|
465
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
466
|
+
onFocus={(e) => e.target.select()}
|
|
467
|
+
onBlur={() => finish(true)}
|
|
468
|
+
onKeyDown={(e) => {
|
|
469
|
+
if (e.key === "Enter") {
|
|
470
|
+
e.preventDefault();
|
|
471
|
+
finish(true);
|
|
472
|
+
} else if (e.key === "Escape") {
|
|
473
|
+
e.preventDefault();
|
|
474
|
+
finish(false);
|
|
475
|
+
}
|
|
476
|
+
}}
|
|
477
|
+
/>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** One line for every save the board makes (the drawing, the title): the
|
|
482
|
+
* worst of them. */
|
|
483
|
+
function SaveStatus({ states, onRetry }) {
|
|
484
|
+
const failed = states.find((s) => s.state === "error");
|
|
485
|
+
if (failed) {
|
|
486
|
+
return (
|
|
487
|
+
<div role="alert" className="flex min-w-0 items-center gap-1 text-xs">
|
|
488
|
+
<Icon name="warning" size={14} className="shrink-0 text-destructive" />
|
|
489
|
+
<span className="truncate max-md:sr-only">{failed.error || "Couldn't save"}</span>
|
|
490
|
+
<Button variant="quiet" size="sm" onClick={onRetry}>
|
|
491
|
+
Retry
|
|
492
|
+
</Button>
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
const saving = states.some((s) => s.state === "saving");
|
|
497
|
+
const saved = states.some((s) => s.state === "saved");
|
|
498
|
+
if (!saving && !saved) return null;
|
|
499
|
+
return (
|
|
500
|
+
<p role="status" className="flex shrink-0 items-center gap-1 px-1 text-xs text-muted-foreground">
|
|
501
|
+
<Icon name={saving ? "loader" : "check"} size={14} className={cx(saving && "motion-safe:animate-spin")} />
|
|
502
|
+
<span className="max-sm:sr-only">{saving ? "Saving…" : "Saved"}</span>
|
|
503
|
+
</p>
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** The empty board: how to start, and the templates. The layer under it
|
|
508
|
+
* lets clicks through to the canvas; the card takes its own. */
|
|
509
|
+
function Starter({ templates, onPick }) {
|
|
510
|
+
return (
|
|
511
|
+
<div className="pointer-events-auto w-full max-w-md rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-lg">
|
|
512
|
+
<p className="text-base font-semibold">Start your board</p>
|
|
513
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
514
|
+
Pick a starting point, or double-click anywhere to write. <Kbd>P</Kbd> draws, <Kbd>R</Kbd> makes a box.
|
|
515
|
+
</p>
|
|
516
|
+
<ul className="mt-4 grid grid-cols-3 gap-2" aria-label="Templates">
|
|
517
|
+
{templates.map((t) => (
|
|
518
|
+
<li key={t.id} className="min-w-0">
|
|
519
|
+
{/* raw: a template tile holds a drawn preview above its name;
|
|
520
|
+
Button is a one-line label. Keyboard and focus ring as Button. */}
|
|
521
|
+
<button
|
|
522
|
+
type="button"
|
|
523
|
+
onClick={() => onPick(t)}
|
|
524
|
+
title={t.description}
|
|
525
|
+
aria-label={t.description ? `${t.label}: ${t.description}` : t.label}
|
|
526
|
+
className="group flex w-full flex-col gap-2 rounded-lg border border-border bg-background p-2 text-left outline-none transition-colors duration-150 hover:border-primary hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none"
|
|
527
|
+
>
|
|
528
|
+
<Preview template={t} />
|
|
529
|
+
<span className="truncate text-xs font-medium">{t.label}</span>
|
|
530
|
+
</button>
|
|
531
|
+
</li>
|
|
532
|
+
))}
|
|
533
|
+
</ul>
|
|
534
|
+
</div>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** A small drawing of what a template lays down, in tokens. */
|
|
539
|
+
function Preview({ template }) {
|
|
540
|
+
const box = "aspect-[4/3] w-full overflow-hidden rounded-md border border-border bg-card";
|
|
541
|
+
if (template.preview) return <span className={cx(box, "grid place-items-center")}>{template.preview}</span>;
|
|
542
|
+
if (template.id === "grid") {
|
|
543
|
+
return (
|
|
544
|
+
<span aria-hidden="true" className={cx(box, "grid grid-cols-3 grid-rows-2 gap-1 p-1.5")}>
|
|
545
|
+
{Array.from({ length: 6 }, (_, i) => (
|
|
546
|
+
<span key={i} className="rounded-sm border border-dashed border-muted-foreground/50" />
|
|
547
|
+
))}
|
|
548
|
+
</span>
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (template.id === "matrix") {
|
|
552
|
+
return (
|
|
553
|
+
<span aria-hidden="true" className={cx(box, "block p-1.5")}>
|
|
554
|
+
<span className="grid size-full grid-cols-2 grid-rows-2 gap-px border-b-2 border-l-2 border-muted-foreground">
|
|
555
|
+
<span className="bg-success/25" />
|
|
556
|
+
<span className="bg-info/25" />
|
|
557
|
+
<span className="bg-muted" />
|
|
558
|
+
<span className="bg-warning/25" />
|
|
559
|
+
</span>
|
|
560
|
+
</span>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (template.id === "blank") {
|
|
564
|
+
return (
|
|
565
|
+
<span aria-hidden="true" className={cx(box, "grid place-items-center text-muted-foreground")}>
|
|
566
|
+
<Icon name="plus" size={18} />
|
|
567
|
+
</span>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return (
|
|
571
|
+
<span aria-hidden="true" className={cx(box, "grid place-items-center text-muted-foreground")}>
|
|
572
|
+
<Icon name={template.icon ?? "frame"} size={18} />
|
|
573
|
+
</span>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** The board as a list: frames as sections, their words in reading order;
|
|
578
|
+
* what sits outside any frame last. */
|
|
579
|
+
function TextView({ sketch, onBoard, readOnly }) {
|
|
580
|
+
const items = sketchItems(sketch);
|
|
581
|
+
const frames = items.filter((i) => i.kind === "frame");
|
|
582
|
+
const rest = items.filter((i) => i.kind !== "frame");
|
|
583
|
+
const inFrame = (f) => rest.filter((i) => i.frame === f.id);
|
|
584
|
+
const outside = rest.filter((i) => !i.frame || !frames.some((f) => f.id === i.frame));
|
|
585
|
+
const [copied, setCopied] = useState(null);
|
|
586
|
+
const copy = async () => {
|
|
587
|
+
try {
|
|
588
|
+
await navigator.clipboard.writeText(sketchToText(sketch));
|
|
589
|
+
setCopied("Copied");
|
|
590
|
+
} catch {
|
|
591
|
+
setCopied("Couldn't copy");
|
|
592
|
+
}
|
|
593
|
+
setTimeout(() => setCopied(null), 1600);
|
|
594
|
+
};
|
|
595
|
+
const line = (it) => (it.kind === "stamp" ? (it.text ? `${it.stamp}: ${it.text}` : it.stamp) : it.kind === "image" ? "Image" : it.text);
|
|
596
|
+
return (
|
|
597
|
+
<div className="absolute inset-0 z-10 overflow-y-auto bg-background">
|
|
598
|
+
<div className="mx-auto grid max-w-2xl gap-6 px-4 py-6 sm:px-6">
|
|
599
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
600
|
+
<div>
|
|
601
|
+
<h3 className="text-base font-semibold">What the board says</h3>
|
|
602
|
+
<p className="text-sm text-muted-foreground">Every word on the board, top to bottom, grouped by frame.</p>
|
|
603
|
+
</div>
|
|
604
|
+
<div className="flex items-center gap-2">
|
|
605
|
+
{copied ? (
|
|
606
|
+
<span role="status" className="text-xs text-muted-foreground">
|
|
607
|
+
{copied}
|
|
608
|
+
</span>
|
|
609
|
+
) : null}
|
|
610
|
+
<Button size="sm" icon="copy" onClick={copy} disabled={!items.length}>
|
|
611
|
+
Copy text
|
|
612
|
+
</Button>
|
|
613
|
+
</div>
|
|
614
|
+
</div>
|
|
615
|
+
{!items.length ? (
|
|
616
|
+
<div className="grid justify-items-center gap-3 rounded-lg border border-dashed border-border px-4 py-10 text-center">
|
|
617
|
+
<p className="text-sm text-muted-foreground">{readOnly ? "Nothing on this board yet." : "Nothing written on the board yet. Drawings without words don't show here."}</p>
|
|
618
|
+
<Button size="sm" icon="pencil" onClick={onBoard}>
|
|
619
|
+
Back to the board
|
|
620
|
+
</Button>
|
|
621
|
+
</div>
|
|
622
|
+
) : (
|
|
623
|
+
<>
|
|
624
|
+
{frames.map((f) => (
|
|
625
|
+
<TextSection key={f.id} title={f.name} lines={inFrame(f).map(line).filter(Boolean)} />
|
|
626
|
+
))}
|
|
627
|
+
{outside.length ? <TextSection title={frames.length ? "Outside frames" : null} lines={outside.map(line).filter(Boolean)} /> : null}
|
|
628
|
+
</>
|
|
629
|
+
)}
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function TextSection({ title, lines }) {
|
|
636
|
+
return (
|
|
637
|
+
<section className="grid gap-2">
|
|
638
|
+
{title ? <h4 className="text-sm font-semibold">{title}</h4> : null}
|
|
639
|
+
{lines.length ? (
|
|
640
|
+
<ul className="grid gap-1.5">
|
|
641
|
+
{lines.map((l, i) => (
|
|
642
|
+
<li key={i} className="rounded-md border border-border bg-card px-3 py-2 text-sm text-card-foreground">
|
|
643
|
+
{l}
|
|
644
|
+
</li>
|
|
645
|
+
))}
|
|
646
|
+
</ul>
|
|
647
|
+
) : (
|
|
648
|
+
<p className="text-sm text-muted-foreground">Empty.</p>
|
|
649
|
+
)}
|
|
650
|
+
</section>
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ---- tasks ------------------------------------------------------------------
|
|
655
|
+
|
|
656
|
+
const clipTitle = (s) => {
|
|
657
|
+
const t = String(s ?? "").replace(/\s+/g, " ").trim();
|
|
658
|
+
return t.length > 200 ? `${t.slice(0, 199)}…` : t;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
/** What a selected element could become a task from: a note (a shape with
|
|
662
|
+
* words bound in it — selecting its words counts too) or a lone text.
|
|
663
|
+
* { key: the element that carries the mark, ids: every element it came
|
|
664
|
+
* from, text, x, y } or null. */
|
|
665
|
+
function taskSource(el, byId) {
|
|
666
|
+
let box = el;
|
|
667
|
+
let words = null;
|
|
668
|
+
if (el.type === "text") {
|
|
669
|
+
words = el;
|
|
670
|
+
if (el.containerId && byId.has(el.containerId)) box = byId.get(el.containerId);
|
|
671
|
+
} else {
|
|
672
|
+
const b = (el.boundElements ?? []).find((x) => x.type === "text");
|
|
673
|
+
words = b ? byId.get(b.id) : null;
|
|
674
|
+
}
|
|
675
|
+
const text = String(words?.originalText ?? words?.text ?? "").replace(/\s+/g, " ").trim();
|
|
676
|
+
if (!words || !text) return null;
|
|
677
|
+
return { key: box.id, ids: box === words ? [words.id] : [box.id, words.id], text, x: box.x, y: box.y };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function isTask(src, byId) {
|
|
681
|
+
return src.ids.some((id) => byId.get(id)?.customData?.task);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** The small "Task" badges over converted notes, in screen px over the
|
|
685
|
+
* canvas. Clickable (open the task) when the page gives `onOpenTask`;
|
|
686
|
+
* otherwise they let the pointer through to the board. */
|
|
687
|
+
function TaskMarks({ marks, onOpen }) {
|
|
688
|
+
return (
|
|
689
|
+
<div className="pointer-events-none absolute inset-0 z-[5] overflow-hidden">
|
|
690
|
+
{marks.map((m) => {
|
|
691
|
+
const body = (
|
|
692
|
+
<>
|
|
693
|
+
<Icon name={m.pending ? "loader" : "check-circle"} size={12} className={cx("shrink-0", m.pending ? "motion-safe:animate-spin" : "text-success")} />
|
|
694
|
+
<span>{m.pending ? "Creating…" : "Task"}</span>
|
|
695
|
+
</>
|
|
696
|
+
);
|
|
697
|
+
const place = { left: m.x, top: m.y };
|
|
698
|
+
const look = "absolute flex -translate-x-3/4 -translate-y-1/2 items-center gap-1 rounded-full border border-border bg-card px-2 py-0.5 text-[11px] leading-4 font-medium text-card-foreground shadow-sm";
|
|
699
|
+
return onOpen && m.id != null && !m.pending ? (
|
|
700
|
+
// raw: a 20px badge pinned over a drawn note; a kit Button is 36px
|
|
701
|
+
// and would cover the note it marks.
|
|
702
|
+
<button
|
|
703
|
+
key={m.key}
|
|
704
|
+
type="button"
|
|
705
|
+
title={`Open task: ${m.title}`}
|
|
706
|
+
aria-label={`Open task: ${m.title}`}
|
|
707
|
+
onClick={() => onOpen({ id: m.id, title: m.title, sourceIds: [m.key] })}
|
|
708
|
+
className={cx(look, "pointer-events-auto cursor-pointer outline-none transition-colors duration-150 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none")}
|
|
709
|
+
style={place}
|
|
710
|
+
>
|
|
711
|
+
{body}
|
|
712
|
+
</button>
|
|
713
|
+
) : (
|
|
714
|
+
<span key={m.key} title={m.title} className={look} style={place}>
|
|
715
|
+
{body}
|
|
716
|
+
</span>
|
|
717
|
+
);
|
|
718
|
+
})}
|
|
719
|
+
</div>
|
|
720
|
+
);
|
|
721
|
+
}
|