makersclaw 0.8.0 → 0.8.2
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/README.md +9 -3
- package/package.json +1 -1
- package/r/activity-log.json +52 -0
- package/r/agent-changes.json +51 -0
- package/r/agent-chat.json +53 -0
- package/r/agent-column.json +85 -0
- package/r/agent-dock.json +57 -0
- package/r/agent-kit.json +49 -0
- package/r/agent-marks.json +49 -0
- package/r/agent-pane.json +93 -0
- package/r/ai-action.json +56 -0
- package/r/annotate.json +3 -3
- package/r/approval-request.json +2 -2
- package/r/approval.json +54 -0
- package/r/ask-bar.json +50 -0
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/automation-editor.json +66 -0
- package/r/automation-list.json +57 -0
- package/r/automation-model.json +118 -0
- package/r/automations-screen.json +71 -0
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +2 -2
- package/r/chat-pane.json +84 -0
- package/r/chat.json +2 -2
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/connect-card.json +51 -0
- package/r/contact-section.json +2 -2
- package/r/core.json +13 -7
- package/r/cost.json +51 -0
- package/r/csv-import.json +3 -3
- package/r/data-picker.json +60 -0
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/first-run.json +63 -0
- package/r/fix-note.json +57 -0
- package/r/flow-canvas.json +2 -2
- package/r/flow-editor.json +79 -0
- package/r/footer.json +2 -2
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/library-shelf.json +56 -0
- package/r/map.json +2 -2
- package/r/margin-notes.json +67 -0
- package/r/menubar.json +2 -2
- package/r/mind-map.json +3 -3
- package/r/navigation-menu.json +2 -2
- package/r/needs-you.json +55 -0
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +3 -3
- package/r/pace-bar.json +69 -0
- package/r/page-builder.json +2 -2
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +2 -2
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-grid.json +52 -0
- package/r/record-notes.json +4 -4
- package/r/registry.json +4086 -1512
- package/r/run-history.json +52 -0
- package/r/run-view.json +80 -0
- package/r/schedule-card.json +54 -0
- package/r/schedule-engine.json +79 -0
- package/r/schedule-gantt.json +83 -0
- package/r/schedule-ruler.json +64 -0
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +2 -2
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +2 -2
- package/r/source-receipt.json +52 -0
- package/r/spreadsheet.json +3 -3
- package/r/stat-cards.json +3 -3
- package/r/step-picker.json +53 -0
- package/r/step-settings.json +72 -0
- package/r/suggestion.json +58 -0
- package/r/sum-bar.json +64 -0
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/tier-map.json +67 -0
- package/r/time-grid.json +73 -0
- package/r/time-strip.json +64 -0
- package/r/top-bar.json +66 -0
- package/r/transaction-list.json +2 -2
- package/r/trigger-picker.json +61 -0
- package/r/whiteboard.json +3 -3
- package/r/workflow-view.json +55 -0
- package/registry/blocks/activity-log/README.md +112 -0
- package/registry/blocks/activity-log/activity-log.jsx +351 -0
- package/registry/blocks/activity-log/block.json +33 -0
- package/registry/blocks/activity-log/demo.jsx +48 -0
- package/registry/blocks/activity-log/index.js +4 -0
- package/registry/blocks/agent-changes/README.md +94 -0
- package/registry/blocks/agent-changes/agent-changes.jsx +118 -0
- package/registry/blocks/agent-changes/block.json +28 -0
- package/registry/blocks/agent-changes/demo.jsx +39 -0
- package/registry/blocks/agent-changes/index.js +3 -0
- package/registry/blocks/agent-chat/README.md +145 -0
- package/registry/blocks/agent-chat/agent-chat.jsx +415 -0
- package/registry/blocks/agent-chat/block.json +30 -0
- package/registry/blocks/agent-chat/demo.jsx +165 -0
- package/registry/blocks/agent-chat/index.js +3 -0
- package/registry/blocks/agent-column/README.md +197 -0
- package/registry/blocks/agent-column/agent-column.jsx +492 -0
- package/registry/blocks/agent-column/block.json +67 -0
- package/registry/blocks/agent-column/column-picker.jsx +163 -0
- package/registry/blocks/agent-column/demo.jsx +50 -0
- package/registry/blocks/agent-column/index.js +7 -0
- package/registry/blocks/agent-column/model.js +164 -0
- package/registry/blocks/agent-column/receipt.jsx +483 -0
- package/registry/blocks/agent-column/spreadsheet-type.jsx +24 -0
- package/registry/blocks/agent-dock/README.md +210 -0
- package/registry/blocks/agent-dock/agent-dock.jsx +356 -0
- package/registry/blocks/agent-dock/block.json +47 -0
- package/registry/blocks/agent-dock/demo.jsx +153 -0
- package/registry/blocks/agent-dock/dock-model.js +118 -0
- package/registry/blocks/agent-dock/index.js +5 -0
- package/registry/blocks/agent-kit/README.md +73 -0
- package/registry/blocks/agent-kit/agent-kit.jsx +86 -0
- package/registry/blocks/agent-kit/block.json +10 -0
- package/registry/blocks/agent-kit/demo.jsx +35 -0
- package/registry/blocks/agent-kit/index.js +3 -0
- package/registry/blocks/agent-marks/README.md +80 -0
- package/registry/blocks/agent-marks/agent-marks.jsx +133 -0
- package/registry/blocks/agent-marks/block.json +27 -0
- package/registry/blocks/agent-marks/demo.jsx +49 -0
- package/registry/blocks/agent-marks/index.js +3 -0
- package/registry/blocks/agent-pane/README.md +234 -0
- package/registry/blocks/agent-pane/agent-pane.jsx +246 -0
- package/registry/blocks/agent-pane/block.json +41 -0
- package/registry/blocks/agent-pane/composer.jsx +6 -0
- package/registry/blocks/agent-pane/demo.jsx +233 -0
- package/registry/blocks/agent-pane/dock-context.js +10 -0
- package/registry/blocks/agent-pane/index.js +4 -0
- package/registry/blocks/agent-pane/prose.jsx +84 -0
- package/registry/blocks/agent-pane/questions.jsx +134 -0
- package/registry/blocks/agent-pane/transcript.jsx +268 -0
- package/registry/blocks/agent-pane/work.jsx +368 -0
- package/registry/blocks/ai-action/README.md +94 -0
- package/registry/blocks/ai-action/ai-action.jsx +188 -0
- package/registry/blocks/ai-action/block.json +34 -0
- package/registry/blocks/ai-action/demo.jsx +93 -0
- package/registry/blocks/ai-action/index.js +3 -0
- package/registry/blocks/approval/README.md +145 -0
- package/registry/blocks/approval/approval.jsx +163 -0
- package/registry/blocks/approval/block.json +31 -0
- package/registry/blocks/approval/demo.jsx +121 -0
- package/registry/blocks/approval/index.js +3 -0
- package/registry/blocks/ask-bar/README.md +105 -0
- package/registry/blocks/ask-bar/ask-bar.jsx +166 -0
- package/registry/blocks/ask-bar/block.json +26 -0
- package/registry/blocks/ask-bar/demo.jsx +78 -0
- package/registry/blocks/ask-bar/index.js +3 -0
- package/registry/blocks/automation-editor/README.md +291 -0
- package/registry/blocks/automation-editor/automation-editor.jsx +656 -0
- package/registry/blocks/automation-editor/block.json +39 -0
- package/registry/blocks/automation-editor/demo.jsx +284 -0
- package/registry/blocks/automation-editor/index.js +4 -0
- package/registry/blocks/automation-editor/run-inputs.jsx +82 -0
- package/registry/blocks/automation-list/README.md +129 -0
- package/registry/blocks/automation-list/automation-list.jsx +476 -0
- package/registry/blocks/automation-list/block.json +35 -0
- package/registry/blocks/automation-list/demo.jsx +67 -0
- package/registry/blocks/automation-list/index.js +3 -0
- package/registry/blocks/automation-model/README.md +109 -0
- package/registry/blocks/automation-model/block.json +135 -0
- package/registry/blocks/automation-model/catalog.js +256 -0
- package/registry/blocks/automation-model/check.js +161 -0
- package/registry/blocks/automation-model/demo.jsx +42 -0
- package/registry/blocks/automation-model/edit.js +262 -0
- package/registry/blocks/automation-model/fixes.js +287 -0
- package/registry/blocks/automation-model/glyph.jsx +13 -0
- package/registry/blocks/automation-model/graph.js +410 -0
- package/registry/blocks/automation-model/index.js +33 -0
- package/registry/blocks/automation-model/overlay.js +175 -0
- package/registry/blocks/automation-model/parts.jsx +94 -0
- package/registry/blocks/automation-model/schedule.js +53 -0
- package/registry/blocks/automation-model/templates.js +128 -0
- package/registry/blocks/automation-model/values.js +29 -0
- package/registry/blocks/automation-model/words.js +439 -0
- package/registry/blocks/automations-screen/README.md +207 -0
- package/registry/blocks/automations-screen/automations-screen.jsx +452 -0
- package/registry/blocks/automations-screen/block.json +47 -0
- package/registry/blocks/automations-screen/demo.jsx +364 -0
- package/registry/blocks/automations-screen/index.js +4 -0
- package/registry/blocks/automations-screen/screen-model.js +129 -0
- package/registry/blocks/chat-pane/README.md +143 -0
- package/registry/blocks/chat-pane/block.json +48 -0
- package/registry/blocks/chat-pane/chat-attach.js +166 -0
- package/registry/blocks/chat-pane/chat-composer.jsx +195 -0
- package/registry/blocks/chat-pane/chat-context.js +17 -0
- package/registry/blocks/chat-pane/chat-model.js +43 -0
- package/registry/blocks/chat-pane/chat-pane.jsx +398 -0
- package/registry/blocks/chat-pane/chats-list.jsx +52 -0
- package/registry/blocks/chat-pane/demo.jsx +103 -0
- package/registry/blocks/chat-pane/index.js +9 -0
- package/registry/blocks/connect-card/README.md +99 -0
- package/registry/blocks/connect-card/block.json +10 -0
- package/registry/blocks/connect-card/connect-card.jsx +103 -0
- package/registry/blocks/connect-card/demo.jsx +49 -0
- package/registry/blocks/connect-card/index.js +3 -0
- package/registry/blocks/cost/README.md +115 -0
- package/registry/blocks/cost/block.json +10 -0
- package/registry/blocks/cost/cost.jsx +176 -0
- package/registry/blocks/cost/demo.jsx +66 -0
- package/registry/blocks/cost/index.js +3 -0
- package/registry/blocks/data-picker/README.md +81 -0
- package/registry/blocks/data-picker/block.json +38 -0
- package/registry/blocks/data-picker/data-picker.jsx +210 -0
- package/registry/blocks/data-picker/demo.jsx +34 -0
- package/registry/blocks/data-picker/fields.js +89 -0
- package/registry/blocks/data-picker/index.js +4 -0
- package/registry/blocks/data-table/demo.jsx +1 -1
- package/registry/blocks/first-run/README.md +118 -0
- package/registry/blocks/first-run/block.json +11 -0
- package/registry/blocks/first-run/demo.jsx +88 -0
- package/registry/blocks/first-run/first-run-model.js +51 -0
- package/registry/blocks/first-run/first-run.jsx +156 -0
- package/registry/blocks/first-run/index.js +5 -0
- package/registry/blocks/first-run/probe-rules.js +26 -0
- package/registry/blocks/fix-note/README.md +79 -0
- package/registry/blocks/fix-note/block.json +35 -0
- package/registry/blocks/fix-note/demo.jsx +30 -0
- package/registry/blocks/fix-note/fix-note.jsx +235 -0
- package/registry/blocks/fix-note/index.js +3 -0
- package/registry/blocks/flow-editor/README.md +108 -0
- package/registry/blocks/flow-editor/block.json +44 -0
- package/registry/blocks/flow-editor/demo.jsx +115 -0
- package/registry/blocks/flow-editor/flow-canvas.jsx +568 -0
- package/registry/blocks/flow-editor/flow-editor.jsx +154 -0
- package/registry/blocks/flow-editor/index.js +6 -0
- package/registry/blocks/flow-editor/minimap.jsx +78 -0
- package/registry/blocks/flow-editor/outline.jsx +134 -0
- package/registry/blocks/library-shelf/README.md +117 -0
- package/registry/blocks/library-shelf/block.json +28 -0
- package/registry/blocks/library-shelf/demo.jsx +68 -0
- package/registry/blocks/library-shelf/index.js +3 -0
- package/registry/blocks/library-shelf/library-shelf.jsx +211 -0
- package/registry/blocks/library-shelf/probe-rules.js +15 -0
- package/registry/blocks/margin-notes/README.md +222 -0
- package/registry/blocks/margin-notes/block.json +10 -0
- package/registry/blocks/margin-notes/demo.jsx +144 -0
- package/registry/blocks/margin-notes/index.js +20 -0
- package/registry/blocks/margin-notes/margin-model.js +224 -0
- package/registry/blocks/margin-notes/margin-notes.jsx +465 -0
- package/registry/blocks/needs-you/README.md +177 -0
- package/registry/blocks/needs-you/block.json +34 -0
- package/registry/blocks/needs-you/demo.jsx +143 -0
- package/registry/blocks/needs-you/index.js +3 -0
- package/registry/blocks/needs-you/needs-you.jsx +441 -0
- package/registry/blocks/pace-bar/README.md +230 -0
- package/registry/blocks/pace-bar/block.json +10 -0
- package/registry/blocks/pace-bar/demo.jsx +255 -0
- package/registry/blocks/pace-bar/index.js +22 -0
- package/registry/blocks/pace-bar/pace-bar.jsx +461 -0
- package/registry/blocks/pace-bar/pace-model.js +288 -0
- package/registry/blocks/record-grid/README.md +100 -0
- package/registry/blocks/record-grid/block.json +28 -0
- package/registry/blocks/record-grid/demo.jsx +43 -0
- package/registry/blocks/record-grid/index.js +3 -0
- package/registry/blocks/record-grid/record-grid.jsx +251 -0
- package/registry/blocks/run-history/README.md +138 -0
- package/registry/blocks/run-history/block.json +10 -0
- package/registry/blocks/run-history/demo.jsx +74 -0
- package/registry/blocks/run-history/index.js +3 -0
- package/registry/blocks/run-history/run-history.jsx +218 -0
- package/registry/blocks/run-view/README.md +174 -0
- package/registry/blocks/run-view/block.json +50 -0
- package/registry/blocks/run-view/demo.jsx +286 -0
- package/registry/blocks/run-view/index.js +5 -0
- package/registry/blocks/run-view/run-controls.jsx +266 -0
- package/registry/blocks/run-view/run-view.jsx +236 -0
- package/registry/blocks/run-view/runs-list.jsx +99 -0
- package/registry/blocks/schedule-card/README.md +121 -0
- package/registry/blocks/schedule-card/block.json +10 -0
- package/registry/blocks/schedule-card/demo.jsx +82 -0
- package/registry/blocks/schedule-card/index.js +3 -0
- package/registry/blocks/schedule-card/schedule-card.jsx +136 -0
- package/registry/blocks/schedule-engine/README.md +137 -0
- package/registry/blocks/schedule-engine/block.json +10 -0
- package/registry/blocks/schedule-engine/demo.jsx +82 -0
- package/registry/blocks/schedule-engine/engine.js +769 -0
- package/registry/blocks/schedule-engine/index.js +9 -0
- package/registry/blocks/schedule-engine/ops.js +71 -0
- package/registry/blocks/schedule-engine/place.js +294 -0
- package/registry/blocks/schedule-engine/tree.js +79 -0
- package/registry/blocks/schedule-engine/waits.js +80 -0
- package/registry/blocks/schedule-gantt/README.md +159 -0
- package/registry/blocks/schedule-gantt/block.json +10 -0
- package/registry/blocks/schedule-gantt/demo.jsx +223 -0
- package/registry/blocks/schedule-gantt/gantt-model.js +219 -0
- package/registry/blocks/schedule-gantt/geometry.js +176 -0
- package/registry/blocks/schedule-gantt/index.js +26 -0
- package/registry/blocks/schedule-gantt/schedule-gantt.jsx +882 -0
- package/registry/blocks/schedule-gantt/sheet.jsx +372 -0
- package/registry/blocks/schedule-gantt/track.jsx +190 -0
- package/registry/blocks/schedule-ruler/README.md +148 -0
- package/registry/blocks/schedule-ruler/block.json +10 -0
- package/registry/blocks/schedule-ruler/demo.jsx +181 -0
- package/registry/blocks/schedule-ruler/index.js +21 -0
- package/registry/blocks/schedule-ruler/ruler-model.js +239 -0
- package/registry/blocks/schedule-ruler/schedule-ruler.jsx +548 -0
- package/registry/blocks/source-receipt/README.md +97 -0
- package/registry/blocks/source-receipt/block.json +30 -0
- package/registry/blocks/source-receipt/demo.jsx +71 -0
- package/registry/blocks/source-receipt/index.js +3 -0
- package/registry/blocks/source-receipt/source-receipt.jsx +107 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +15 -1
- package/registry/blocks/step-picker/README.md +78 -0
- package/registry/blocks/step-picker/block.json +31 -0
- package/registry/blocks/step-picker/demo.jsx +23 -0
- package/registry/blocks/step-picker/index.js +3 -0
- package/registry/blocks/step-picker/step-picker.jsx +144 -0
- package/registry/blocks/step-settings/README.md +100 -0
- package/registry/blocks/step-settings/block.json +44 -0
- package/registry/blocks/step-settings/demo.jsx +93 -0
- package/registry/blocks/step-settings/fields.jsx +58 -0
- package/registry/blocks/step-settings/flow-settings.jsx +115 -0
- package/registry/blocks/step-settings/index.js +5 -0
- package/registry/blocks/step-settings/step-settings.jsx +290 -0
- package/registry/blocks/suggestion/README.md +171 -0
- package/registry/blocks/suggestion/block.json +40 -0
- package/registry/blocks/suggestion/demo.jsx +91 -0
- package/registry/blocks/suggestion/index.js +3 -0
- package/registry/blocks/suggestion/suggestion.jsx +436 -0
- package/registry/blocks/sum-bar/README.md +122 -0
- package/registry/blocks/sum-bar/block.json +10 -0
- package/registry/blocks/sum-bar/demo.jsx +110 -0
- package/registry/blocks/sum-bar/index.js +5 -0
- package/registry/blocks/sum-bar/sum-bar.jsx +209 -0
- package/registry/blocks/sum-bar/sum-model.js +126 -0
- package/registry/blocks/tier-map/README.md +217 -0
- package/registry/blocks/tier-map/block.json +10 -0
- package/registry/blocks/tier-map/demo.jsx +222 -0
- package/registry/blocks/tier-map/index.js +24 -0
- package/registry/blocks/tier-map/tier-map.jsx +574 -0
- package/registry/blocks/tier-map/tier-model.js +318 -0
- package/registry/blocks/time-grid/README.md +305 -0
- package/registry/blocks/time-grid/block.json +10 -0
- package/registry/blocks/time-grid/demo.jsx +279 -0
- package/registry/blocks/time-grid/grid-model.js +322 -0
- package/registry/blocks/time-grid/index.js +25 -0
- package/registry/blocks/time-grid/time-grid.jsx +1325 -0
- package/registry/blocks/time-strip/README.md +141 -0
- package/registry/blocks/time-strip/block.json +10 -0
- package/registry/blocks/time-strip/demo.jsx +129 -0
- package/registry/blocks/time-strip/index.js +19 -0
- package/registry/blocks/time-strip/strip-model.js +178 -0
- package/registry/blocks/time-strip/time-strip.jsx +260 -0
- package/registry/blocks/top-bar/README.md +149 -0
- package/registry/blocks/top-bar/block.json +35 -0
- package/registry/blocks/top-bar/demo.jsx +78 -0
- package/registry/blocks/top-bar/index.js +5 -0
- package/registry/blocks/top-bar/probe-rules.js +74 -0
- package/registry/blocks/top-bar/top-bar-model.js +152 -0
- package/registry/blocks/top-bar/top-bar.jsx +244 -0
- package/registry/blocks/trigger-picker/README.md +98 -0
- package/registry/blocks/trigger-picker/block.json +38 -0
- package/registry/blocks/trigger-picker/demo.jsx +59 -0
- package/registry/blocks/trigger-picker/index.js +3 -0
- package/registry/blocks/trigger-picker/trigger-picker.jsx +374 -0
- package/registry/blocks/workflow-view/README.md +123 -0
- package/registry/blocks/workflow-view/block.json +10 -0
- package/registry/blocks/workflow-view/demo.jsx +90 -0
- package/registry/blocks/workflow-view/index.js +3 -0
- package/registry/blocks/workflow-view/workflow-view.jsx +203 -0
- package/src/install.mjs +77 -16
- package/src/registry.mjs +3 -0
- package/templates/next/browser/lib/data.js +6 -1
- package/templates/next/files/README.md +627 -24
- package/templates/next/files/check/automation-format.ts +2103 -0
- package/templates/next/files/check/automation-package.ts +142 -0
- package/templates/next/files/check/run.mjs +968 -9
- package/templates/next/files/components/ui/field.jsx +17 -4
- package/templates/next/files/components/ui/icon.jsx +13 -0
- package/templates/next/files/components/ui/index.js +2 -1
- package/templates/next/files/components/ui/layout.jsx +77 -2
- package/templates/next/files/components/ui/notice.jsx +70 -0
- package/templates/next/files/components/ui/page.jsx +124 -0
- package/templates/next/files/components/ui/stat.jsx +22 -7
- package/templates/next/files/components/ui/toast.jsx +6 -3
- package/templates/next/files/lib/automations.js +660 -0
- package/templates/next/files/lib/claw.js +241 -5
- package/templates/next/files/lib/door.js +92 -0
- package/templates/next/files/lib/hooks.js +1677 -8
- package/templates/next/files/lib/platform.js +529 -21
- package/templates/next/template.json +13 -3
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// A value the agent proposes, drawn in pencil: dashed while it is a
|
|
4
|
+
// proposal, solid once kept (the app kit, D15; grown in the CRM as
|
|
5
|
+
// field-suggestion, 0.7.0). Five shapes, one idea — the source is always
|
|
6
|
+
// shown, and a person keeps or drops it:
|
|
7
|
+
//
|
|
8
|
+
// FieldSuggestion under a field, with what it holds now
|
|
9
|
+
// InlineSuggestion found in place: a dashed chip in the cell it would fill
|
|
10
|
+
// TextSuggestion a span of text struck, its replacement beside it
|
|
11
|
+
// Pencil the dashed frame for a whole record ("pencil to ink")
|
|
12
|
+
// SuggestionGroup the head over several: Keep all, Drop all
|
|
13
|
+
//
|
|
14
|
+
// The verbs are props (Keep/Drop, Apply/Dismiss, Accept/Decline). A keep
|
|
15
|
+
// calls your callback, which runs the app's own declared action, so a
|
|
16
|
+
// keep is the same write the agent would make. Props in, callbacks out;
|
|
17
|
+
// the block never reads a database. Tokens only.
|
|
18
|
+
|
|
19
|
+
import { useState } from "react";
|
|
20
|
+
import { Button, Icon, IconButton, cx } from "../../ui";
|
|
21
|
+
|
|
22
|
+
/** "her email signature, Sat 12 Sep" from { from, at }. `at` is a
|
|
23
|
+
* "YYYY-MM-DD" day (or anything Date reads); `format(date)` words it,
|
|
24
|
+
* default "Sat 12 Sep". */
|
|
25
|
+
export function suggestionSource({ from, at } = {}, format) {
|
|
26
|
+
if (!from && !at) return "";
|
|
27
|
+
let when = "";
|
|
28
|
+
if (at) {
|
|
29
|
+
const d = typeof at === "string" && /^\d{4}-\d{2}-\d{2}$/.test(at) ? new Date(`${at}T12:00:00Z`) : new Date(at);
|
|
30
|
+
if (!Number.isNaN(d.getTime())) {
|
|
31
|
+
when = format ? format(d) : d.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", timeZone: "UTC" }).replace(",", "");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [from, when].filter(Boolean).join(", ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const FRAME = {
|
|
38
|
+
pencil: "border-[1.5px] border-dashed border-foreground/35 bg-card",
|
|
39
|
+
kept: "border-[1.5px] border-solid border-success/60 bg-card",
|
|
40
|
+
failed: "border-[1.5px] border-dashed border-destructive/60 bg-card",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** The dashed frame for any proposal: a kicker ("A NEW DEAL"), a title,
|
|
44
|
+
* the reason, your content, your actions. `state` "pencil" (default),
|
|
45
|
+
* "kept" (solid) or "failed" (with `error`). */
|
|
46
|
+
export function Pencil({ kicker, title, reason, state = "pencil", error, actions, label, className, children }) {
|
|
47
|
+
return (
|
|
48
|
+
<section
|
|
49
|
+
aria-label={label ?? (typeof title === "string" ? `Suggested: ${title}` : "A suggestion")}
|
|
50
|
+
className={cx("grid grid-cols-1 min-w-0 gap-2 rounded-lg p-3", FRAME[state] ?? FRAME.pencil, className)}
|
|
51
|
+
>
|
|
52
|
+
{kicker ? <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">{kicker}</p> : null}
|
|
53
|
+
{title ? <div className="min-w-0 text-[15px] font-semibold leading-snug">{title}</div> : null}
|
|
54
|
+
{children}
|
|
55
|
+
{reason ? <p className="text-sm text-muted-foreground">{reason}</p> : null}
|
|
56
|
+
{state === "failed" && error ? (
|
|
57
|
+
<p role="alert" className="text-sm text-destructive">
|
|
58
|
+
{error}
|
|
59
|
+
</p>
|
|
60
|
+
) : null}
|
|
61
|
+
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
|
62
|
+
</section>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* One proposed value for one field.
|
|
68
|
+
*
|
|
69
|
+
* label the field's name ("Job title")
|
|
70
|
+
* current what it holds now; empty → "empty now"
|
|
71
|
+
* value the proposed value (text or a node)
|
|
72
|
+
* source { from, at } or a string: where it came from
|
|
73
|
+
* onKeep() async → { ok } | { ok: false, error }
|
|
74
|
+
* onDrop() async → { ok } | { ok: false, error }
|
|
75
|
+
* compact icon Keep/Drop at the row's end (a list of several)
|
|
76
|
+
* showLabel false when the field's label is drawn by you above it
|
|
77
|
+
* keepLabel · dropLabel · keptLabel the verbs ("Apply", "Dismiss", "Applied")
|
|
78
|
+
*/
|
|
79
|
+
export function FieldSuggestion({
|
|
80
|
+
label,
|
|
81
|
+
current,
|
|
82
|
+
value,
|
|
83
|
+
source,
|
|
84
|
+
onKeep,
|
|
85
|
+
onDrop,
|
|
86
|
+
compact = false,
|
|
87
|
+
showLabel = true,
|
|
88
|
+
keepLabel = "Keep",
|
|
89
|
+
dropLabel = "Drop",
|
|
90
|
+
keptLabel = "Kept",
|
|
91
|
+
formatDate,
|
|
92
|
+
className,
|
|
93
|
+
}) {
|
|
94
|
+
const [busy, setBusy] = useState(null);
|
|
95
|
+
const [state, setState] = useState("pencil");
|
|
96
|
+
const [error, setError] = useState(null);
|
|
97
|
+
const from = typeof source === "string" ? source : suggestionSource(source, formatDate);
|
|
98
|
+
const empty = current == null || current === "";
|
|
99
|
+
|
|
100
|
+
async function run(which, fn) {
|
|
101
|
+
if (!fn || busy) return;
|
|
102
|
+
setBusy(which);
|
|
103
|
+
setError(null);
|
|
104
|
+
let r;
|
|
105
|
+
try {
|
|
106
|
+
r = await fn();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
109
|
+
}
|
|
110
|
+
setBusy(null);
|
|
111
|
+
if (r && r.ok === false) {
|
|
112
|
+
setState("failed");
|
|
113
|
+
setError(r.error || `Couldn't ${(which === "keep" ? keepLabel : dropLabel).toLowerCase()} it. Try again.`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
setState(which === "keep" ? "kept" : "dropped");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (state === "dropped") return null;
|
|
120
|
+
const name = typeof label === "string" ? label.toLowerCase() : "this";
|
|
121
|
+
return (
|
|
122
|
+
<div className={cx("@container grid grid-cols-1 min-w-0 gap-1", className)}>
|
|
123
|
+
{showLabel && label ? (
|
|
124
|
+
<p className="text-xs text-muted-foreground">
|
|
125
|
+
{label}
|
|
126
|
+
{state === "kept" ? ` · ${keptLabel.toLowerCase()}` : empty ? " · empty now" : ` · now ${typeof current === "string" ? current : ""}`}
|
|
127
|
+
</p>
|
|
128
|
+
) : null}
|
|
129
|
+
<div
|
|
130
|
+
role="group"
|
|
131
|
+
aria-label={`Suggested ${name}`}
|
|
132
|
+
className={cx(
|
|
133
|
+
"grid min-w-0 gap-2 rounded-md px-3 py-2",
|
|
134
|
+
// Side by side from 22rem of its own width; under it the
|
|
135
|
+
// buttons go below the value. Compact icons always fit beside.
|
|
136
|
+
compact ? "grid-cols-[minmax(0,1fr)_auto] items-center gap-3" : "grid-cols-1 @[22rem]:grid-cols-[minmax(0,1fr)_auto] @[22rem]:items-center @[22rem]:gap-3",
|
|
137
|
+
FRAME[state] ?? FRAME.pencil,
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
<div className="grid grid-cols-1 min-w-0 gap-0.5">
|
|
141
|
+
{compact && label ? <span className="text-xs text-muted-foreground">{label}</span> : null}
|
|
142
|
+
<span className="min-w-0 break-words text-sm font-semibold">{value}</span>
|
|
143
|
+
{from ? <span className="text-xs text-muted-foreground">{from}</span> : null}
|
|
144
|
+
{state === "failed" && error ? (
|
|
145
|
+
<span role="alert" className="text-xs text-destructive">
|
|
146
|
+
{error}
|
|
147
|
+
</span>
|
|
148
|
+
) : null}
|
|
149
|
+
</div>
|
|
150
|
+
{state === "kept" ? (
|
|
151
|
+
<span className="inline-flex items-center gap-1 text-xs font-medium text-success">
|
|
152
|
+
<Icon name="check" size={14} />
|
|
153
|
+
{keptLabel}
|
|
154
|
+
</span>
|
|
155
|
+
) : compact ? (
|
|
156
|
+
<span className="flex items-center gap-1">
|
|
157
|
+
<IconButton icon="check" size="sm" variant="secondary" label={`${keepLabel} ${name}`} disabled={Boolean(busy)} onClick={() => run("keep", onKeep)} />
|
|
158
|
+
<IconButton icon="x" size="sm" label={`${dropLabel} ${name}`} disabled={Boolean(busy)} onClick={() => run("drop", onDrop)} />
|
|
159
|
+
</span>
|
|
160
|
+
) : (
|
|
161
|
+
<span className="flex flex-wrap items-center gap-1">
|
|
162
|
+
<Button size="sm" variant="primary" loading={busy === "keep"} disabled={Boolean(busy)} onClick={() => run("keep", onKeep)} aria-label={`${keepLabel} ${name}`}>
|
|
163
|
+
{keepLabel}
|
|
164
|
+
</Button>
|
|
165
|
+
<Button size="sm" variant="quiet" loading={busy === "drop"} disabled={Boolean(busy)} onClick={() => run("drop", onDrop)} aria-label={`${dropLabel} ${name}`}>
|
|
166
|
+
{dropLabel}
|
|
167
|
+
</Button>
|
|
168
|
+
</span>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The header for several suggestions on one record: what was found and
|
|
176
|
+
* where from, Keep all and Drop all. */
|
|
177
|
+
export function SuggestionGroup({ title, description, count, onKeepAll, onDropAll, keepAllLabel, dropAllLabel = "Drop all", className, children }) {
|
|
178
|
+
const [busy, setBusy] = useState(null);
|
|
179
|
+
const [error, setError] = useState(null);
|
|
180
|
+
async function run(which, fn) {
|
|
181
|
+
if (!fn || busy) return;
|
|
182
|
+
setBusy(which);
|
|
183
|
+
setError(null);
|
|
184
|
+
let r;
|
|
185
|
+
try {
|
|
186
|
+
r = await fn();
|
|
187
|
+
} catch (e) {
|
|
188
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
189
|
+
}
|
|
190
|
+
setBusy(null);
|
|
191
|
+
if (r && r.ok === false) setError(r.error || "That didn't work. Try again.");
|
|
192
|
+
}
|
|
193
|
+
return (
|
|
194
|
+
<div className={cx("grid grid-cols-1 min-w-0 gap-3", className)}>
|
|
195
|
+
<div role="status" className={cx("flex min-w-0 flex-wrap items-center gap-3 rounded-lg px-4 py-3", FRAME.pencil)}>
|
|
196
|
+
<Icon name="sparkles" className="text-muted-foreground" />
|
|
197
|
+
<div className="grid min-w-0 flex-1 basis-56 grid-cols-1 gap-0.5">
|
|
198
|
+
<p className="text-sm font-semibold">{title}</p>
|
|
199
|
+
{description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
|
|
200
|
+
{error ? (
|
|
201
|
+
<p role="alert" className="text-sm text-destructive">
|
|
202
|
+
{error}
|
|
203
|
+
</p>
|
|
204
|
+
) : null}
|
|
205
|
+
</div>
|
|
206
|
+
<span className="flex flex-wrap items-center gap-2">
|
|
207
|
+
{onKeepAll ? (
|
|
208
|
+
<Button size="sm" variant="primary" loading={busy === "keep"} disabled={Boolean(busy)} onClick={() => run("keep", onKeepAll)}>
|
|
209
|
+
{keepAllLabel ?? (count ? `Keep all ${count}` : "Keep all")}
|
|
210
|
+
</Button>
|
|
211
|
+
) : null}
|
|
212
|
+
{onDropAll ? (
|
|
213
|
+
<Button size="sm" variant="quiet" loading={busy === "drop"} disabled={Boolean(busy)} onClick={() => run("drop", onDropAll)}>
|
|
214
|
+
{dropAllLabel}
|
|
215
|
+
</Button>
|
|
216
|
+
) : null}
|
|
217
|
+
</span>
|
|
218
|
+
</div>
|
|
219
|
+
{children}
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Found in place: the proposed value inside the cell or field it would
|
|
226
|
+
* fill, as a small dashed chip in the agent's tone, with where it came
|
|
227
|
+
* from and a tick to keep and a cross to drop (0.7.0, the CRM's v2
|
|
228
|
+
* signature). Same callbacks as FieldSuggestion.
|
|
229
|
+
*
|
|
230
|
+
* value the proposed value
|
|
231
|
+
* source { from, at } or a string ("her email signature, Sat 12 Sep")
|
|
232
|
+
* label the field's name, for the buttons ("Keep job title")
|
|
233
|
+
* onKeep() · onDrop() async → { ok } | { ok: false, error }
|
|
234
|
+
* keepLabel · dropLabel the verbs the buttons say to a reader
|
|
235
|
+
*/
|
|
236
|
+
export function InlineSuggestion({ value, source, label, onKeep, onDrop, keepLabel = "Keep", dropLabel = "Drop", formatDate, className }) {
|
|
237
|
+
const [busy, setBusy] = useState(null);
|
|
238
|
+
const [state, setState] = useState("pencil");
|
|
239
|
+
const [error, setError] = useState(null);
|
|
240
|
+
const from = typeof source === "string" ? source : suggestionSource(source, formatDate);
|
|
241
|
+
const name = typeof label === "string" ? label.toLowerCase() : "this";
|
|
242
|
+
async function run(which, fn) {
|
|
243
|
+
if (!fn || busy) return;
|
|
244
|
+
setBusy(which);
|
|
245
|
+
setError(null);
|
|
246
|
+
let r;
|
|
247
|
+
try {
|
|
248
|
+
r = await fn();
|
|
249
|
+
} catch (e) {
|
|
250
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
251
|
+
}
|
|
252
|
+
setBusy(null);
|
|
253
|
+
if (r && r.ok === false) {
|
|
254
|
+
setState("failed");
|
|
255
|
+
setError(r.error || "That didn't work. Try again.");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
setState(which === "keep" ? "kept" : "dropped");
|
|
259
|
+
}
|
|
260
|
+
if (state === "dropped") return null;
|
|
261
|
+
return (
|
|
262
|
+
<span
|
|
263
|
+
role="group"
|
|
264
|
+
aria-label={`Suggested ${name}: ${typeof value === "string" ? value : ""}`}
|
|
265
|
+
title={error ?? (from ? `Found by the agent: ${from}` : undefined)}
|
|
266
|
+
className={cx(
|
|
267
|
+
"inline-flex h-[22px] min-w-0 max-w-full items-center gap-1.5 whitespace-nowrap rounded-[4px] border border-dashed pl-[7px] pr-[3px] align-middle text-[13px] text-foreground",
|
|
268
|
+
state === "kept" ? "border-solid border-success" : state === "failed" ? "border-destructive" : "border-info",
|
|
269
|
+
className,
|
|
270
|
+
)}
|
|
271
|
+
>
|
|
272
|
+
<span className="min-w-0 truncate">{value}</span>
|
|
273
|
+
{from ? <span className="min-w-0 truncate text-[11.5px] text-muted-foreground">{from}</span> : null}
|
|
274
|
+
{state === "kept" ? (
|
|
275
|
+
<Icon name="check" size={12} className="shrink-0 text-success" label="Kept" />
|
|
276
|
+
) : (
|
|
277
|
+
<>
|
|
278
|
+
<button
|
|
279
|
+
type="button"
|
|
280
|
+
aria-label={`${keepLabel} ${name}`}
|
|
281
|
+
disabled={Boolean(busy)}
|
|
282
|
+
onClick={(e) => {
|
|
283
|
+
e.stopPropagation();
|
|
284
|
+
run("keep", onKeep);
|
|
285
|
+
}}
|
|
286
|
+
className="grid grid-cols-1 size-[18px] shrink-0 place-items-center rounded-[3px] text-success outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
287
|
+
>
|
|
288
|
+
<Icon name="check" size={12} />
|
|
289
|
+
</button>
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
aria-label={`${dropLabel} ${name}`}
|
|
293
|
+
disabled={Boolean(busy)}
|
|
294
|
+
onClick={(e) => {
|
|
295
|
+
e.stopPropagation();
|
|
296
|
+
run("drop", onDrop);
|
|
297
|
+
}}
|
|
298
|
+
className="grid grid-cols-1 size-[18px] shrink-0 place-items-center rounded-[3px] text-muted-foreground outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
299
|
+
>
|
|
300
|
+
<Icon name="x" size={12} />
|
|
301
|
+
</button>
|
|
302
|
+
</>
|
|
303
|
+
)}
|
|
304
|
+
{state === "failed" && error ? <span className="sr-only" role="alert">{error}</span> : null}
|
|
305
|
+
</span>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** "Looking…" in the agent's tone, where a value is still being found. */
|
|
310
|
+
export function Looking({ children = "Looking…", className }) {
|
|
311
|
+
return (
|
|
312
|
+
<span className={cx("inline-flex items-center gap-[5px] text-[13px] text-info motion-safe:animate-pulse", className)}>
|
|
313
|
+
<Icon name="sparkles" size={12} />
|
|
314
|
+
{children}
|
|
315
|
+
</span>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* A change to a span of text, as an editor's proof mark: the old words
|
|
321
|
+
* struck, the new beside them, why, and where from (Writing Review's proof
|
|
322
|
+
* marks; Notes, Landing Page Reviewer). Kept, the new words stand alone in
|
|
323
|
+
* a solid frame; dropped, it goes.
|
|
324
|
+
*
|
|
325
|
+
* before the words now (struck); empty for an insertion
|
|
326
|
+
* after the words proposed; empty for a cut
|
|
327
|
+
* lead · tail a few words around the span, muted, so it reads in place
|
|
328
|
+
* reason why, one line ("“your” is the owner's, “you're” is “you are”")
|
|
329
|
+
* source { from, at } or a string: what it leans on (a style guide, a page)
|
|
330
|
+
* onKeep() · onDrop() async → { ok } | { ok: false, error }
|
|
331
|
+
* keepLabel · dropLabel · keptLabel default "Keep", "Drop", "Kept"
|
|
332
|
+
*/
|
|
333
|
+
export function TextSuggestion({
|
|
334
|
+
before,
|
|
335
|
+
after,
|
|
336
|
+
lead,
|
|
337
|
+
tail,
|
|
338
|
+
reason,
|
|
339
|
+
source,
|
|
340
|
+
onKeep,
|
|
341
|
+
onDrop,
|
|
342
|
+
keepLabel = "Keep",
|
|
343
|
+
dropLabel = "Drop",
|
|
344
|
+
keptLabel = "Kept",
|
|
345
|
+
formatDate,
|
|
346
|
+
className,
|
|
347
|
+
}) {
|
|
348
|
+
const [busy, setBusy] = useState(null);
|
|
349
|
+
const [state, setState] = useState("pencil");
|
|
350
|
+
const [error, setError] = useState(null);
|
|
351
|
+
const from = typeof source === "string" ? source : suggestionSource(source, formatDate);
|
|
352
|
+
const cut = after == null || after === "";
|
|
353
|
+
const insert = before == null || before === "";
|
|
354
|
+
|
|
355
|
+
async function run(which, fn) {
|
|
356
|
+
if (!fn || busy) return;
|
|
357
|
+
setBusy(which);
|
|
358
|
+
setError(null);
|
|
359
|
+
let r;
|
|
360
|
+
try {
|
|
361
|
+
r = await fn();
|
|
362
|
+
} catch (e) {
|
|
363
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
364
|
+
}
|
|
365
|
+
setBusy(null);
|
|
366
|
+
if (r && r.ok === false) {
|
|
367
|
+
setState("failed");
|
|
368
|
+
setError(r.error || `Couldn't ${(which === "keep" ? keepLabel : dropLabel).toLowerCase()} it. Try again.`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
setState(which === "keep" ? "kept" : "dropped");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (state === "dropped") return null;
|
|
375
|
+
const kept = state === "kept";
|
|
376
|
+
const what = cut ? "Cut" : insert ? "Add" : "Change";
|
|
377
|
+
return (
|
|
378
|
+
<div
|
|
379
|
+
role="group"
|
|
380
|
+
aria-label={`Suggested ${what.toLowerCase()}${typeof after === "string" && after ? `: ${after}` : ""}`}
|
|
381
|
+
className={cx("@container grid grid-cols-1 min-w-0 gap-2 rounded-md px-3 py-2 text-sm", FRAME[state] ?? FRAME.pencil, className)}
|
|
382
|
+
>
|
|
383
|
+
<p className="min-w-0 break-words leading-relaxed">
|
|
384
|
+
{lead ? <span className="text-muted-foreground">{lead}</span> : null}
|
|
385
|
+
{kept ? (
|
|
386
|
+
cut ? null : <span className="font-semibold">{after}</span>
|
|
387
|
+
) : (
|
|
388
|
+
<>
|
|
389
|
+
{insert ? null : (
|
|
390
|
+
<del className="text-muted-foreground decoration-foreground decoration-[1.5px]">
|
|
391
|
+
<span className="sr-only">Remove: </span>
|
|
392
|
+
{before}
|
|
393
|
+
</del>
|
|
394
|
+
)}
|
|
395
|
+
{!insert && !cut ? (
|
|
396
|
+
<Icon name="arrow-right" size={12} className="mx-1 inline-block align-[-0.1em] text-muted-foreground" />
|
|
397
|
+
) : null}
|
|
398
|
+
{cut ? null : (
|
|
399
|
+
<ins className="font-semibold no-underline">
|
|
400
|
+
<span className="sr-only">Add: </span>
|
|
401
|
+
{after}
|
|
402
|
+
</ins>
|
|
403
|
+
)}
|
|
404
|
+
</>
|
|
405
|
+
)}
|
|
406
|
+
{tail ? <span className="text-muted-foreground">{tail}</span> : null}
|
|
407
|
+
</p>
|
|
408
|
+
{reason && !kept ? <p className="text-xs text-muted-foreground">{reason}</p> : null}
|
|
409
|
+
{from && !kept ? <p className="text-xs text-muted-foreground">{from}</p> : null}
|
|
410
|
+
{state === "failed" && error ? (
|
|
411
|
+
<p role="alert" className="text-xs text-destructive">
|
|
412
|
+
{error}
|
|
413
|
+
</p>
|
|
414
|
+
) : null}
|
|
415
|
+
{kept ? (
|
|
416
|
+
<span className="inline-flex items-center gap-1 text-xs font-medium text-success">
|
|
417
|
+
<Icon name="check" size={14} />
|
|
418
|
+
{keptLabel}
|
|
419
|
+
</span>
|
|
420
|
+
) : onKeep || onDrop ? (
|
|
421
|
+
<span className="flex flex-wrap items-center gap-1">
|
|
422
|
+
{onKeep ? (
|
|
423
|
+
<Button size="sm" variant="primary" loading={busy === "keep"} disabled={Boolean(busy)} onClick={() => run("keep", onKeep)}>
|
|
424
|
+
{keepLabel}
|
|
425
|
+
</Button>
|
|
426
|
+
) : null}
|
|
427
|
+
{onDrop ? (
|
|
428
|
+
<Button size="sm" variant="quiet" loading={busy === "drop"} disabled={Boolean(busy)} onClick={() => run("drop", onDrop)}>
|
|
429
|
+
{dropLabel}
|
|
430
|
+
</Button>
|
|
431
|
+
) : null}
|
|
432
|
+
</span>
|
|
433
|
+
) : null}
|
|
434
|
+
</div>
|
|
435
|
+
);
|
|
436
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# SumBar
|
|
2
|
+
|
|
3
|
+
A total with its parts cut out of it, so a number can be checked, not trusted.
|
|
4
|
+
|
|
5
|
+
A health score of 35 says little. "100, less 22 for the schedule, 25 for the
|
|
6
|
+
budget, 5 late, 8 blocked, 5 quiet client" says everything. This block draws
|
|
7
|
+
that sum to scale:
|
|
8
|
+
|
|
9
|
+
- **The bar** runs from 0 to the total (100). What is left is solid, with its
|
|
10
|
+
number in it.
|
|
11
|
+
- **Each part is a hatched block cut from the top end downward**, in the tone
|
|
12
|
+
of its part (`warning`, `danger`, `info`, `neutral`), its cost written in it
|
|
13
|
+
and its name under it. A part can be pressed.
|
|
14
|
+
- **A rule that holds the result down** (a cap) is a **post** standing on the
|
|
15
|
+
bar with a flag above it. When the cap bites, the stretch between the sum
|
|
16
|
+
and the result is dotted and the receipt says so.
|
|
17
|
+
- **Bands** under the bar name the ranges (off track, at risk, on track).
|
|
18
|
+
- **The receipt** writes the sum out: `100 − 22 − 25 − 5 − 8 − 0 − 5 = 35`,
|
|
19
|
+
or `100 − 44 = 56, held at 49`.
|
|
20
|
+
|
|
21
|
+
It draws a result the app has already worked out. It scores nothing and
|
|
22
|
+
changes no number. For a total split into shares use the kit's `Distribution`;
|
|
23
|
+
for progress against a clock use `pace-bar`.
|
|
24
|
+
|
|
25
|
+
Born in a project monitor's score sheet ("57% done, 88% of the time gone" is
|
|
26
|
+
`pace-bar`; the 35 it costs is this). Fits any app that scores out of 100 and
|
|
27
|
+
must show its working.
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
import { SumBar, receiptOf, resultOf, sumWords } from "../components/blocks/sum-bar"; // a Makersclaw app ("./blocks/sum-bar" from components/)
|
|
31
|
+
import { SumBar } from "@/components/makersclaw/blocks/sum-bar"; // a shadcn project
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A client component. No other block, no npm package. Positions are percentages
|
|
35
|
+
of the total, so a server render and a browser render agree.
|
|
36
|
+
|
|
37
|
+
## Props
|
|
38
|
+
|
|
39
|
+
- `total`: the whole (default `100`).
|
|
40
|
+
- `parts`: `[{ id, label, cost, tone, note }]`, taken off in the order given,
|
|
41
|
+
from the top end of the bar. `cost` is a number (0 or more; anything else
|
|
42
|
+
counts as 0). A part that costs nothing is no block on the bar but is a term
|
|
43
|
+
in the receipt. `tone`: `neutral` (default), `info`, `warning`, `danger`.
|
|
44
|
+
- `result`: the number after a rule held it down. Left out, it is the total
|
|
45
|
+
less the costs. Lower than that, the difference is "held" (dotted).
|
|
46
|
+
- `posts`: `[{ at, label, tone }]`: a dashed post at `at` (0 to the total),
|
|
47
|
+
with a flag above. `tone: "danger"` draws it in the danger colour.
|
|
48
|
+
- `bands`: `[{ from, to, tone, label }]`: a thin coloured strip under the bar
|
|
49
|
+
and a label centred under each band. `tone`: `success`, `warning`, `danger`,
|
|
50
|
+
`info`, `neutral`.
|
|
51
|
+
- `selected`, `onSelect(part)`: with `onSelect` every cut is a button; the
|
|
52
|
+
selected one is ringed. Without it the cuts are read-only.
|
|
53
|
+
- `size`: `sm` (24 px), `md` (36 px, default), `lg` (48 px).
|
|
54
|
+
- `names`: the names under the cuts (default `true`). They show from 32rem of
|
|
55
|
+
the block's own width, and only under cuts wide enough to hold a name.
|
|
56
|
+
- `legend`: a list of every part and its cost under the bar (default `false`).
|
|
57
|
+
- `receipt`: the written sum (default `true`).
|
|
58
|
+
- `unit`: appended to the receipt (`"points"`). `noun`: what a screen reader
|
|
59
|
+
calls one unit (default `"point"`).
|
|
60
|
+
- `label`: what a screen reader calls the whole (default "Score sheet").
|
|
61
|
+
- `status`: `ready` (default), `loading` (a skeleton), `failed` (a danger
|
|
62
|
+
notice with `error` and a Try again when `onRetry` is given).
|
|
63
|
+
- `className`: layout around it. Never colour.
|
|
64
|
+
|
|
65
|
+
## The sums (plain functions, no React)
|
|
66
|
+
|
|
67
|
+
- `resultOf({ total, parts, result })` → `{ total, lost, kept, result, held, raised }`
|
|
68
|
+
- `cutsOf({ total, parts })` → each part with `from`, `to`, `leftPct`, `widthPct`, `cut`
|
|
69
|
+
- `postsOf({ total, posts })`, `bandsOf({ total, bands })`: placed, out-of-range ones dropped
|
|
70
|
+
- `receiptOf({ total, parts, result, unit })` → `{ …resultOf, terms, text }`
|
|
71
|
+
- `sumWords({ total, parts, result, posts, noun })`: the whole bar in a sentence
|
|
72
|
+
- `costOf(value)`, `totalOf(total)`, `lostOf(parts)`, `labelledCuts(cuts, minPct)`
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
```jsx
|
|
77
|
+
// A project's score: 100 less what each signal cost, held at 49 by two reds.
|
|
78
|
+
<SumBar
|
|
79
|
+
label="Website rebuild, score sheet"
|
|
80
|
+
parts={[
|
|
81
|
+
{ id: "schedule", label: "Schedule", cost: 22, tone: "danger" },
|
|
82
|
+
{ id: "budget", label: "Budget", cost: 25, tone: "danger" },
|
|
83
|
+
{ id: "late", label: "Late work", cost: 5, tone: "warning" },
|
|
84
|
+
{ id: "blocked", label: "Blocked", cost: 8, tone: "warning" },
|
|
85
|
+
{ id: "activity", label: "Activity", cost: 0 },
|
|
86
|
+
{ id: "client", label: "Client", cost: 5, tone: "warning" },
|
|
87
|
+
]}
|
|
88
|
+
result={35}
|
|
89
|
+
posts={[{ at: 74, label: "cap 74 · one red" }, { at: 49, label: "cap 49 · two reds" }]}
|
|
90
|
+
bands={[
|
|
91
|
+
{ from: 0, to: 50, tone: "danger", label: "off track" },
|
|
92
|
+
{ from: 50, to: 75, tone: "warning", label: "at risk" },
|
|
93
|
+
{ from: 75, to: 100, tone: "success", label: "on track" },
|
|
94
|
+
]}
|
|
95
|
+
unit="points"
|
|
96
|
+
onSelect={(part) => scrollToSignal(part.id)}
|
|
97
|
+
/>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## States drawn in the demo
|
|
101
|
+
|
|
102
|
+
- **Nothing taken off**: a full solid bar, the receipt `100 = 100`.
|
|
103
|
+
- **One part, many parts**, a part that costs nothing (in the receipt, not on
|
|
104
|
+
the bar).
|
|
105
|
+
- **Held by a rule**: the sum above the result, the dotted stretch, the receipt
|
|
106
|
+
"held at 49".
|
|
107
|
+
- **Posts and bands**; **pressable** (a selected part, arrow keys by Tab).
|
|
108
|
+
- **Very small cuts** (under 5% wide: no cost written in the block, still in the
|
|
109
|
+
tooltip and the receipt).
|
|
110
|
+
- **Odd input**: a negative cost, a cost past the total, text for a cost, a
|
|
111
|
+
zero total.
|
|
112
|
+
- **Narrow** (a 320 px container: names under the cuts give way to the legend
|
|
113
|
+
you ask for), **wide**, **loading**, **failed**.
|
|
114
|
+
|
|
115
|
+
## Notes for the app that places it
|
|
116
|
+
|
|
117
|
+
- Colour is a signal. Tone a cut `danger` or `warning` only when its signal is
|
|
118
|
+
in the red or a warning; a neutral cut is a cost that is not a problem.
|
|
119
|
+
- Never rely on tone alone: the cost is written in the cut, in the tooltip and
|
|
120
|
+
in the receipt.
|
|
121
|
+
- Put the same numbers in words beside it (the rows of the sum). The bar is a
|
|
122
|
+
picture of them.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sum-bar",
|
|
3
|
+
"title": "SumBar",
|
|
4
|
+
"description": "A total with its parts cut out of it, drawn to scale. The bar runs from 0 to the total (100 by default); what is left is solid with its number, each part's cost is a hatched block cut from the top end downward in the tone of its part (warning, danger, info, neutral) with its cost written in it and its name under it, and a part can be pressed. When a rule holds the result below the sum, the stretch between them is dotted and says so. Posts stand on the bar where a rule applies (a cap at 74, a cap at 49), each with a flag above. Coloured bands under the bar name the ranges (off track under 50, at risk to 75, on track). Under it the sum is written out so no number is taken on trust: \"100 − 22 − 25 − 5 − 8 − 0 − 5 = 35\", or \"100 − 44 = 56, held at 49\". Positions are percentages, so it draws the same on the server; a screen reader hears the whole bar as one sentence; loading, failed and nothing-taken-off are drawn; the sums (resultOf, cutsOf, postsOf, bandsOf, receiptOf, sumWords) are exported as plain functions.",
|
|
5
|
+
"useWhen": "Any screen where a number is a total with things taken off it and the member should be able to check the arithmetic: a health score of 100 points less what each signal cost, a risk score, a quote's price less its discounts, a budget less what each line spent, a credit balance less each charge, a grade out of 100 less each lost mark. It draws a result the app has already worked out and changes no number. Not a total split into shares (Distribution, breakdown), progress against a clock (pace-bar), a meter against a limit (Meter), or a time series (chart).",
|
|
6
|
+
"tags": ["sum", "score", "total", "points", "cost", "deduction", "receipt", "arithmetic", "cap", "ceiling", "threshold", "health", "risk", "budget", "breakdown", "waterfall", "bar", "hatched"],
|
|
7
|
+
"exports": ["SumBar", "bandsOf", "costOf", "cutsOf", "labelledCuts", "lostOf", "postsOf", "receiptOf", "resultOf", "sumWords", "totalOf"],
|
|
8
|
+
"files": ["index.js", "sum-bar.jsx", "sum-model.js"],
|
|
9
|
+
"requires": []
|
|
10
|
+
}
|