makersclaw 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +225 -0
- package/registry/blocks/agent-pane/agent-pane.jsx +244 -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 +1627 -8
- package/templates/next/files/lib/platform.js +529 -21
- package/templates/next/template.json +13 -3
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button, Empty, Notice, Skeleton, cx } from "../../ui";
|
|
4
|
+
import { AgentMark } from "../agent-marks";
|
|
5
|
+
import { axisOf, dayName, groupRows, marksOnWeeks, paceOf, paceSentence, paceWords, percent, share, shortDay, toneOf, weeksOf, WORDS } from "./pace-model";
|
|
6
|
+
|
|
7
|
+
// Progress against a clock. Four pieces that share one idea, "how far
|
|
8
|
+
// along, and how far along should it be by now":
|
|
9
|
+
//
|
|
10
|
+
// PaceBar one bar: filled to `value`, a hard upright tick at the pace,
|
|
11
|
+
// and the stretch between them hatched, so "behind" is a shape
|
|
12
|
+
// you see and not a sum you do.
|
|
13
|
+
// PaceList many bars on ONE axis: the period's months and Mondays above,
|
|
14
|
+
// one today line running through the whole list, and each
|
|
15
|
+
// row's gap said in weeks ("4 wk behind"). Rows can sit in
|
|
16
|
+
// groups.
|
|
17
|
+
// PaceRuler the axis alone (month names, a tick each Monday, a flag for
|
|
18
|
+
// today), for a screen that lays out its own rows.
|
|
19
|
+
// WeekStrip the period as its weeks in one row: the weeks gone filled,
|
|
20
|
+
// this one marked, a mark on the week each check-in was made,
|
|
21
|
+
// by a person, by the agent, or by no one.
|
|
22
|
+
//
|
|
23
|
+
// Props in, one callback out (`onOpen`); the block imports only React, the
|
|
24
|
+
// kit and the agent's mark. README.md beside this file lists every prop.
|
|
25
|
+
// Server-safe: nothing here measures the page, positions are percentages.
|
|
26
|
+
|
|
27
|
+
// ---- looks (literal class names, so Tailwind sees every one)
|
|
28
|
+
|
|
29
|
+
const FILL = {
|
|
30
|
+
ink: "bg-foreground",
|
|
31
|
+
quiet: "bg-muted-foreground",
|
|
32
|
+
info: "bg-info",
|
|
33
|
+
success: "bg-success",
|
|
34
|
+
warning: "bg-warning",
|
|
35
|
+
danger: "bg-destructive",
|
|
36
|
+
};
|
|
37
|
+
// The hatch is drawn in currentColor, so a text-* token paints it.
|
|
38
|
+
const HATCH_TEXT = {
|
|
39
|
+
ink: "text-foreground",
|
|
40
|
+
quiet: "text-muted-foreground",
|
|
41
|
+
info: "text-info",
|
|
42
|
+
success: "text-success",
|
|
43
|
+
warning: "text-warning",
|
|
44
|
+
danger: "text-destructive",
|
|
45
|
+
};
|
|
46
|
+
const GAP_TEXT = { ...HATCH_TEXT, ink: "text-muted-foreground" };
|
|
47
|
+
const HATCH = { backgroundImage: "repeating-linear-gradient(135deg, currentColor 0 2px, transparent 2px 5px)" };
|
|
48
|
+
|
|
49
|
+
const SIZE = {
|
|
50
|
+
lg: { track: "h-3.5", tick: "-inset-y-2" },
|
|
51
|
+
md: { track: "h-2.5", tick: "-inset-y-1" },
|
|
52
|
+
sm: { track: "h-1.5", tick: "-inset-y-1" },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const pct = (n) => `${Math.round(n * 1000) / 1000}%`;
|
|
56
|
+
const asTone = (tone, p, cut) => (tone === "auto" || tone == null ? toneOf(p, cut) : FILL[tone] ? tone : "ink");
|
|
57
|
+
|
|
58
|
+
/** What the bar is made of, without a meter's role: the fill, the hatched gap, and the tick when asked. */
|
|
59
|
+
function Bars({ p, tone, size, hatch, tick, animate, delay }) {
|
|
60
|
+
const s = SIZE[size] ?? SIZE.md;
|
|
61
|
+
const fill = (p.value ?? 0) * 100;
|
|
62
|
+
const mark = p.pace == null ? null : p.pace * 100;
|
|
63
|
+
const spend = p.kind === "spend";
|
|
64
|
+
let from = 0;
|
|
65
|
+
let width = 0;
|
|
66
|
+
if (hatch && mark != null) {
|
|
67
|
+
if (!spend && mark > fill) [from, width] = [fill, mark - fill];
|
|
68
|
+
if (spend && fill > mark) [from, width] = [mark, fill - mark];
|
|
69
|
+
}
|
|
70
|
+
const grow = animate ? "transition-[width] duration-700 ease-out starting:w-0 motion-reduce:transition-none" : "";
|
|
71
|
+
const wait = animate && delay ? { transitionDelay: `${Math.min(540, Math.max(0, Number(delay) || 0))}ms` } : undefined;
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<span className={cx("relative block overflow-hidden rounded-[1px] bg-muted", s.track)}>
|
|
75
|
+
{width ? (
|
|
76
|
+
<span
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
className={cx("absolute inset-y-0 left-(--from) w-(--width) opacity-60", HATCH_TEXT[tone])}
|
|
79
|
+
style={{ ...HATCH, "--from": pct(from), "--width": pct(width) }}
|
|
80
|
+
/>
|
|
81
|
+
) : null}
|
|
82
|
+
<span aria-hidden="true" className={cx("absolute inset-y-0 left-0 w-(--fill) rounded-[1px]", FILL[tone], grow)} style={{ "--fill": pct(fill), ...wait }} />
|
|
83
|
+
</span>
|
|
84
|
+
{tick && mark != null ? (
|
|
85
|
+
<span aria-hidden="true" className={cx("absolute left-(--mark) w-0.5 -translate-x-1/2 bg-foreground ring-1 ring-background", s.tick)} style={{ "--mark": pct(mark) }} />
|
|
86
|
+
) : null}
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* One bar against a pace. Give `pace` (0..1) yourself, or give the period
|
|
93
|
+
* (`start`, `end`, `today`) and the share of the time gone is the pace.
|
|
94
|
+
*
|
|
95
|
+
* Props: see README.md. In short: `value`, `pace`, `start`, `end`, `today`,
|
|
96
|
+
* `kind` (progress | spend), `tone` (ink | quiet | info | success | warning
|
|
97
|
+
* | danger | auto), `size` (lg | md | sm), `hatch`, `animate`, `delay`,
|
|
98
|
+
* `words`, `label`, `className`.
|
|
99
|
+
*/
|
|
100
|
+
export function PaceBar({ value, pace, start, end, today, kind = "progress", tone = "ink", size = "md", hatch = true, animate = false, delay = 0, words, tolerance, graceDays, graceFraction, label = "Progress", className }) {
|
|
101
|
+
const p = paceOf({ value, pace, start, end, today, kind, tolerance, graceDays, graceFraction });
|
|
102
|
+
const t = asTone(tone, p);
|
|
103
|
+
const valuetext = paceSentence(p, words) || (p.valuePct == null ? "not known" : `${p.valuePct}%`);
|
|
104
|
+
return (
|
|
105
|
+
<span
|
|
106
|
+
role="meter"
|
|
107
|
+
aria-label={label}
|
|
108
|
+
aria-valuemin={0}
|
|
109
|
+
aria-valuemax={100}
|
|
110
|
+
aria-valuenow={p.valuePct ?? 0}
|
|
111
|
+
aria-valuetext={`${valuetext}${p.state === "worse" || p.state === "better" || p.state === "on" ? `. ${paceWords(p, words)}` : ""}`}
|
|
112
|
+
className={cx("relative block min-w-0", className)}
|
|
113
|
+
>
|
|
114
|
+
<Bars p={p} tone={t} size={size} hatch={hatch} tick animate={animate} delay={delay} />
|
|
115
|
+
</span>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- marks
|
|
120
|
+
|
|
121
|
+
const MARK_WORDS = { person: "A person", agent: "The agent" };
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Who made a check-in: a filled dot for a person, the platform's mark for
|
|
125
|
+
* the agent, nothing for no one. With `empty` a mark made by no one is a
|
|
126
|
+
* hollow ring (the place a mark would be); with `missed` it is a dashed
|
|
127
|
+
* ring (a week that should have one and has none). `label` is what a
|
|
128
|
+
* screen reader hears; leave it out when words beside the mark already say it.
|
|
129
|
+
*/
|
|
130
|
+
export function PaceMark({ by, label, empty = false, missed = false, size = "sm", className }) {
|
|
131
|
+
const aria = label ? { role: "img", "aria-label": label, title: label } : { "aria-hidden": "true" };
|
|
132
|
+
const dot = size === "sm" ? "size-2" : "size-2.5";
|
|
133
|
+
if (by === "agent") return <AgentMark size={size === "sm" ? "xs" : size} label={label} className={className} />;
|
|
134
|
+
if (by === "person") return <span {...aria} className={cx("inline-block shrink-0 rounded-full bg-foreground", dot, className)} />;
|
|
135
|
+
if (missed) return <span {...aria} className={cx("inline-block shrink-0 rounded-full border border-dashed border-muted-foreground", dot, className)} />;
|
|
136
|
+
if (empty) return <span {...aria} className={cx("inline-block shrink-0 rounded-full border border-muted-foreground", dot, className)} />;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---- the ruler
|
|
141
|
+
|
|
142
|
+
/** The flag's sides: a flag near the right edge hangs left of the line, near the left edge right of it. */
|
|
143
|
+
const flagSide = (at) => (at > 0.78 ? "-translate-x-full" : at < 0.22 ? "" : "-translate-x-1/2");
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The axis of a period: month names, a tick each week start, the today
|
|
147
|
+
* line and its flag. Place it in the column your rows' bars share.
|
|
148
|
+
* Props: `start`, `end`, `today`, `weekStartsOn`, `words`, `showToday`,
|
|
149
|
+
* `label`, `className`.
|
|
150
|
+
*/
|
|
151
|
+
export function PaceRuler({ start, end, today, weekStartsOn = 0, words, showToday = true, label = "Period", className }) {
|
|
152
|
+
const w = { ...WORDS, ...words };
|
|
153
|
+
const axis = axisOf({ start, end, today, weekStartsOn });
|
|
154
|
+
if (!axis) return null;
|
|
155
|
+
const { period } = axis;
|
|
156
|
+
const at = showToday ? axis.todayAt : null;
|
|
157
|
+
const flag =
|
|
158
|
+
at == null
|
|
159
|
+
? period.where === "before"
|
|
160
|
+
? w.before
|
|
161
|
+
: null
|
|
162
|
+
: period.where === "after"
|
|
163
|
+
? w.after
|
|
164
|
+
: `${w.today} · ${w.gone(percent(period.share))}`;
|
|
165
|
+
const summary = `${label}: ${shortDay(period.s)} to ${shortDay(period.e)}${period.t != null ? `. ${dayName(period.t)}${period.where === "in" ? `, ${percent(period.share)}% of the time gone, ${period.left} ${period.left === 1 ? "day" : "days"} left` : period.where === "after" ? ", ended" : ", not started"}` : ""}`;
|
|
166
|
+
return (
|
|
167
|
+
<span role="img" aria-label={summary} className={cx("relative block h-11 min-w-0", className)}>
|
|
168
|
+
{flag ? (
|
|
169
|
+
<span
|
|
170
|
+
aria-hidden="true"
|
|
171
|
+
className={cx(
|
|
172
|
+
"absolute top-0 z-10 whitespace-nowrap rounded-sm px-1.5 text-xs font-semibold leading-4",
|
|
173
|
+
at == null ? "left-0 bg-muted text-muted-foreground" : cx("bg-foreground text-background", flagSide(at)),
|
|
174
|
+
)}
|
|
175
|
+
style={at == null ? undefined : { left: pct(at * 100) }}
|
|
176
|
+
>
|
|
177
|
+
{flag}
|
|
178
|
+
</span>
|
|
179
|
+
) : null}
|
|
180
|
+
<span aria-hidden="true" className="absolute inset-x-0 bottom-0 h-px bg-border" />
|
|
181
|
+
{axis.weeks.map((d) => (
|
|
182
|
+
<span key={`w${d.day}`} aria-hidden="true" className="absolute bottom-0 h-1.5 w-px bg-border" style={{ left: pct(d.at * 100) }} />
|
|
183
|
+
))}
|
|
184
|
+
{axis.months.map((m) => (
|
|
185
|
+
<span key={`m${m.day}`} aria-hidden="true">
|
|
186
|
+
<span className="absolute bottom-0 h-2.5 w-px bg-foreground" style={{ left: pct(m.at * 100) }} />
|
|
187
|
+
<span className="absolute bottom-3 whitespace-nowrap pl-1 text-xs font-semibold text-foreground" style={{ left: pct(m.at * 100) }}>
|
|
188
|
+
{m.label}
|
|
189
|
+
</span>
|
|
190
|
+
</span>
|
|
191
|
+
))}
|
|
192
|
+
{at != null ? <span aria-hidden="true" className="absolute inset-y-0 w-0.5 -translate-x-1/2 bg-foreground" style={{ left: pct(at * 100) }} /> : null}
|
|
193
|
+
</span>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---- the list
|
|
198
|
+
|
|
199
|
+
// Rows are one grid, the same columns in every row, so the bars of every
|
|
200
|
+
// row start and end at the same x: that is the shared axis. Narrow, a row is
|
|
201
|
+
// its name on one line and the bar and number under it.
|
|
202
|
+
const ROW = "grid grid-cols-[minmax(0,1fr)_4rem] items-center gap-x-3 gap-y-1 px-3 py-2 @2xl:grid-cols-(--cols)";
|
|
203
|
+
// The name takes the whole narrow row; wide it is one column, or two (name and
|
|
204
|
+
// meta) in a heading, where nothing sits in the meta column.
|
|
205
|
+
const NAME = "col-span-2 @2xl:col-span-1";
|
|
206
|
+
const NAME_WIDE = "col-span-2";
|
|
207
|
+
const HEAD = "text-xs font-semibold uppercase tracking-wide text-muted-foreground";
|
|
208
|
+
// The today line reaches into the row's padding and its border, so the lines
|
|
209
|
+
// of the rows below one another read as one.
|
|
210
|
+
const LINE = "pointer-events-none absolute -top-2 -bottom-[calc(0.5rem+1px)] w-0.5 -translate-x-1/2 bg-foreground";
|
|
211
|
+
|
|
212
|
+
function Track({ children, at, className }) {
|
|
213
|
+
return (
|
|
214
|
+
<span className={cx("relative min-h-5 self-stretch", className)}>
|
|
215
|
+
{children}
|
|
216
|
+
{at != null ? <span aria-hidden="true" className={LINE} style={{ left: pct(at * 100) }} /> : null}
|
|
217
|
+
</span>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Many bars on one axis. `rows` are `{ id, label, sub, value, pace, kind,
|
|
223
|
+
* tone, by, byLabel, meta, note, group }`; `groups` are `{ id, label, sub,
|
|
224
|
+
* note, empty }`.
|
|
225
|
+
*
|
|
226
|
+
* Props: see README.md. In short: `rows`, `groups`, `start`, `end`,
|
|
227
|
+
* `today`, `pace`, `kind`, `tolerance`, `graceDays`, `graceFraction`, `unit`, `words`,
|
|
228
|
+
* `heads`, `ruler`, `metaWidth`, `trackWidth`, `gapWidth`, `size`, `selected`,
|
|
229
|
+
* `onOpen`, `loading`, `error`, `onRetry`, `empty`, `emptyAction`, `label`,
|
|
230
|
+
* `className`.
|
|
231
|
+
*/
|
|
232
|
+
export function PaceList({
|
|
233
|
+
rows,
|
|
234
|
+
groups,
|
|
235
|
+
start,
|
|
236
|
+
end,
|
|
237
|
+
today,
|
|
238
|
+
pace,
|
|
239
|
+
kind = "progress",
|
|
240
|
+
tolerance,
|
|
241
|
+
graceDays,
|
|
242
|
+
graceFraction,
|
|
243
|
+
unit,
|
|
244
|
+
words: wordsIn,
|
|
245
|
+
heads,
|
|
246
|
+
ruler = true,
|
|
247
|
+
weekStartsOn = 0,
|
|
248
|
+
warnAt,
|
|
249
|
+
dangerAt,
|
|
250
|
+
metaWidth,
|
|
251
|
+
trackWidth = "24rem",
|
|
252
|
+
gapWidth = "9rem",
|
|
253
|
+
size = "sm",
|
|
254
|
+
selected,
|
|
255
|
+
onOpen,
|
|
256
|
+
loading = false,
|
|
257
|
+
error,
|
|
258
|
+
onRetry,
|
|
259
|
+
empty,
|
|
260
|
+
emptyAction,
|
|
261
|
+
label = "Progress against the clock",
|
|
262
|
+
className,
|
|
263
|
+
}) {
|
|
264
|
+
const words = { ...WORDS, ...wordsIn };
|
|
265
|
+
if (loading) {
|
|
266
|
+
return (
|
|
267
|
+
<div aria-busy="true" aria-label={`${label}, loading`} className={cx("@container grid min-w-0 gap-3 px-3 py-2", className)}>
|
|
268
|
+
<Skeleton shape="rect" className="h-8 w-full" />
|
|
269
|
+
{[0, 1, 2, 3].map((i) => (
|
|
270
|
+
<Skeleton key={i} shape="rect" className="h-5 w-full" />
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (error) {
|
|
276
|
+
return (
|
|
277
|
+
<div className={cx("@container min-w-0", className)}>
|
|
278
|
+
<Notice tone="danger" title={`${label} did not load`} action={onRetry ? <Button size="sm" onClick={onRetry}>Try again</Button> : null}>
|
|
279
|
+
{typeof error === "string" ? error : "Something went wrong reading the rows."}
|
|
280
|
+
</Notice>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const axis = axisOf({ start, end, today, weekStartsOn });
|
|
286
|
+
const sharedPace = pace === undefined ? undefined : share(pace);
|
|
287
|
+
// The line: the end of today on the period, or the pace given.
|
|
288
|
+
const lineAt = axis ? axis.todayAt : sharedPace ?? null;
|
|
289
|
+
const list = rows ?? [];
|
|
290
|
+
const cols = ["minmax(0,1fr)", metaWidth, `minmax(10rem,${trackWidth})`, "4rem", gapWidth].filter(Boolean).join(" ");
|
|
291
|
+
const style = { "--cols": cols };
|
|
292
|
+
// In a heading the name also takes the meta column's place.
|
|
293
|
+
const headName = cx("min-w-0", metaWidth ? NAME_WIDE : NAME);
|
|
294
|
+
|
|
295
|
+
if (!list.length && !(groups ?? []).length) {
|
|
296
|
+
return (
|
|
297
|
+
<div className={cx("@container min-w-0", className)}>
|
|
298
|
+
<Empty icon="clock" title={empty ?? "Nothing to measure yet"} action={emptyAction} />
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const read = new Map(
|
|
304
|
+
list.map((r) => {
|
|
305
|
+
const p = paceOf({
|
|
306
|
+
value: r.value,
|
|
307
|
+
pace: r.pace === undefined ? sharedPace : r.pace,
|
|
308
|
+
start,
|
|
309
|
+
end,
|
|
310
|
+
today,
|
|
311
|
+
kind: r.kind ?? kind,
|
|
312
|
+
tolerance,
|
|
313
|
+
graceDays,
|
|
314
|
+
graceFraction,
|
|
315
|
+
unit: r.unit ?? unit,
|
|
316
|
+
});
|
|
317
|
+
return [r, { p, tone: asTone(r.tone, p, { warnAt, dangerAt }) }];
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
const grouped = groupRows(list, groups);
|
|
321
|
+
|
|
322
|
+
const head = heads ?? {};
|
|
323
|
+
const showRuler = ruler && axis;
|
|
324
|
+
const summary = `${label}: ${list.length} ${list.length === 1 ? "row" : "rows"}${axis?.period.t != null && axis.period.where === "in" ? `, ${percent(axis.period.share)}% of the time gone` : ""}`;
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<div role="group" aria-label={summary} className={cx("@container min-w-0", className)}>
|
|
328
|
+
{showRuler || head.label || head.pct || head.gap ? (
|
|
329
|
+
<div className={cx(ROW, "border-b border-border bg-background")} style={style}>
|
|
330
|
+
<span className={cx(HEAD, "self-end", headName)}>{head.label}</span>
|
|
331
|
+
<span className="relative col-span-2 min-w-0 self-stretch @2xl:col-span-1">
|
|
332
|
+
{showRuler ? <PaceRuler start={start} end={end} today={today} weekStartsOn={weekStartsOn} words={words} label={label} /> : null}
|
|
333
|
+
{showRuler && lineAt != null ? <span aria-hidden="true" className="absolute top-full h-[calc(0.5rem+1px)] w-0.5 -translate-x-1/2 bg-foreground" style={{ left: pct(lineAt * 100) }} /> : null}
|
|
334
|
+
</span>
|
|
335
|
+
<span className={cx(HEAD, "hidden self-end text-right @2xl:block")}>{head.pct}</span>
|
|
336
|
+
<span className={cx(HEAD, "hidden self-end @2xl:block")}>{head.gap}</span>
|
|
337
|
+
</div>
|
|
338
|
+
) : null}
|
|
339
|
+
{grouped.map(({ group, rows: inGroup }, gi) => (
|
|
340
|
+
<div key={group?.id ?? `rest-${gi}`} role="group" aria-label={group?.label ?? undefined}>
|
|
341
|
+
{group ? (
|
|
342
|
+
<div className={cx(ROW, "border-b border-border bg-background")} style={style}>
|
|
343
|
+
<div className={cx("flex flex-wrap items-baseline gap-x-3 gap-y-0.5", headName)}>
|
|
344
|
+
<h3 className="text-sm font-semibold">{group.label}</h3>
|
|
345
|
+
{group.sub ? <span className="text-xs text-muted-foreground">{group.sub}</span> : null}
|
|
346
|
+
{!inGroup.length && group.empty ? <span className="text-xs text-muted-foreground">{group.empty}</span> : null}
|
|
347
|
+
{group.note ? <span className="text-xs text-muted-foreground">{group.note}</span> : null}
|
|
348
|
+
</div>
|
|
349
|
+
<Track at={lineAt} className="hidden @2xl:block" />
|
|
350
|
+
<span className="hidden @2xl:block" />
|
|
351
|
+
<span className="hidden @2xl:block" />
|
|
352
|
+
</div>
|
|
353
|
+
) : null}
|
|
354
|
+
<ul className="m-0 grid list-none p-0">
|
|
355
|
+
{inGroup.map((r, i) => {
|
|
356
|
+
const { p, tone } = read.get(r);
|
|
357
|
+
const said = r.note ?? paceWords(p, words);
|
|
358
|
+
const Tag = onOpen ? "button" : "div";
|
|
359
|
+
const on = selected != null && selected === r.id;
|
|
360
|
+
// A row with a pace of its own gets its own tick; one on the shared clock leaves it to the line.
|
|
361
|
+
const own = p.pace != null && lineAt != null && Math.abs(p.pace - lineAt) > 0.004;
|
|
362
|
+
return (
|
|
363
|
+
<li key={r.id ?? i} className="min-w-0">
|
|
364
|
+
<Tag
|
|
365
|
+
{...(onOpen ? { type: "button", onClick: () => onOpen(r), "aria-current": on ? "true" : undefined } : {})}
|
|
366
|
+
className={cx(
|
|
367
|
+
ROW,
|
|
368
|
+
"w-full border-b border-border text-left outline-none",
|
|
369
|
+
onOpen && "cursor-pointer hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
370
|
+
on && "bg-accent",
|
|
371
|
+
)}
|
|
372
|
+
style={style}
|
|
373
|
+
>
|
|
374
|
+
<span className={cx("min-w-0", NAME)}>
|
|
375
|
+
<span className="block break-words text-sm font-medium leading-snug">{r.label}</span>
|
|
376
|
+
{r.sub ? <span className="block break-words text-xs text-muted-foreground">{r.sub}</span> : null}
|
|
377
|
+
</span>
|
|
378
|
+
{metaWidth ? <span className="col-span-2 min-w-0 @2xl:col-span-1">{r.meta}</span> : null}
|
|
379
|
+
<Track at={lineAt} className="col-start-1 @2xl:col-start-auto">
|
|
380
|
+
<span className="absolute inset-x-0 top-1/2 block -translate-y-1/2">
|
|
381
|
+
<span className="relative block">
|
|
382
|
+
<Bars p={p} tone={tone} size={size} hatch tick={own} />
|
|
383
|
+
</span>
|
|
384
|
+
</span>
|
|
385
|
+
</Track>
|
|
386
|
+
<span className="col-start-2 flex items-center justify-end gap-1.5 text-sm font-semibold tabular-nums @2xl:col-start-auto">
|
|
387
|
+
<PaceMark by={r.by} label={r.byLabel ?? (r.by ? MARK_WORDS[r.by] : undefined)} />
|
|
388
|
+
<span>{p.valuePct == null ? "–" : `${p.valuePct}%`}</span>
|
|
389
|
+
</span>
|
|
390
|
+
<span className={cx("col-span-2 min-w-0 break-words text-xs @2xl:col-span-1", p.state === "worse" && tone !== "ink" ? cx("font-medium", GAP_TEXT[tone]) : "text-muted-foreground")}>{said}</span>
|
|
391
|
+
</Tag>
|
|
392
|
+
</li>
|
|
393
|
+
);
|
|
394
|
+
})}
|
|
395
|
+
</ul>
|
|
396
|
+
</div>
|
|
397
|
+
))}
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---- the week strip
|
|
403
|
+
|
|
404
|
+
const WEEK_WORD = "Week";
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* The period as its weeks in one row. A week gone is filled, the current
|
|
408
|
+
* one is marked, weeks to come are empty. A mark sits on the week its date
|
|
409
|
+
* falls in: a filled dot for a person, the agent's mark, or (with `missed`)
|
|
410
|
+
* a dashed ring on a past week with no mark at all.
|
|
411
|
+
*
|
|
412
|
+
* Props: `start`, `end`, `today`, `marks` [{ id, date, by, label }],
|
|
413
|
+
* `weekStartsOn`, `missed`, `onOpen(week)`, `words`, `label`, `className`.
|
|
414
|
+
*/
|
|
415
|
+
export function WeekStrip({ start, end, today, marks, weekStartsOn = 0, missed = false, onOpen, words, label = "Weeks", className }) {
|
|
416
|
+
const w = { week: WEEK_WORD, ...words };
|
|
417
|
+
const base = weeksOf({ start, end, today, weekStartsOn });
|
|
418
|
+
if (!base.length) return null;
|
|
419
|
+
const { weeks, skipped } = marksOnWeeks(base, marks);
|
|
420
|
+
const current = weeks.find((x) => x.current);
|
|
421
|
+
const total = weeks.reduce((n, x) => n + x.marks.length, 0);
|
|
422
|
+
const summary = `${label}: ${weeks.length} ${weeks.length === 1 ? "week" : "weeks"}${current ? `, ${w.week.toLowerCase()} ${current.n} now` : ""}, ${total} ${total === 1 ? "mark" : "marks"}${skipped ? `, ${skipped} without a date left off` : ""}`;
|
|
423
|
+
return (
|
|
424
|
+
<div className={cx("@container min-w-0", className)}>
|
|
425
|
+
<div role="region" aria-label={`${label}, scrolls sideways`} tabIndex={0} className="overflow-x-auto rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
426
|
+
<ol aria-label={summary} className="m-0 grid list-none gap-0.5 p-0" style={{ gridTemplateColumns: `repeat(${weeks.length}, minmax(1.5rem, 1fr))` }}>
|
|
427
|
+
{weeks.map((x) => {
|
|
428
|
+
const Tag = onOpen ? "button" : "div";
|
|
429
|
+
const name = `${w.week} ${x.n}, ${shortDay(x.from)} to ${shortDay(x.to)}${x.current ? ", now" : ""}${x.marks.length ? `, ${x.marks.map((m) => m.label ?? MARK_WORDS[m.by] ?? "a mark").join(", ")}` : ""}`;
|
|
430
|
+
return (
|
|
431
|
+
<li key={x.n} className="min-w-0">
|
|
432
|
+
<Tag
|
|
433
|
+
{...(onOpen ? { type: "button", onClick: () => onOpen(x), "aria-label": name } : { role: "img", "aria-label": name })}
|
|
434
|
+
{...(x.current ? { "aria-current": "date" } : {})}
|
|
435
|
+
className={cx("grid w-full min-w-0 justify-items-center gap-1 rounded-sm px-0.5 py-1 outline-none", onOpen && "cursor-pointer hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring")}
|
|
436
|
+
>
|
|
437
|
+
<span aria-hidden="true" className={cx("text-xs tabular-nums", x.current ? "font-semibold text-foreground" : "text-muted-foreground")}>
|
|
438
|
+
{x.n}
|
|
439
|
+
</span>
|
|
440
|
+
<span
|
|
441
|
+
aria-hidden="true"
|
|
442
|
+
className={cx("block w-full rounded-[1px]", x.state === "past" ? "h-1.5 bg-muted-foreground" : x.current ? "h-2.5 bg-foreground" : "h-1.5 bg-muted")}
|
|
443
|
+
/>
|
|
444
|
+
<span aria-hidden="true" className="flex min-h-3 flex-wrap items-center justify-center gap-0.5">
|
|
445
|
+
{x.marks.slice(0, 3).map((m) => (
|
|
446
|
+
<PaceMark key={m.id} by={m.by} empty />
|
|
447
|
+
))}
|
|
448
|
+
{x.marks.length > 3 ? <span className="text-xs text-muted-foreground">+{x.marks.length - 3}</span> : null}
|
|
449
|
+
{missed && x.state === "past" && !x.marks.length ? <PaceMark missed /> : null}
|
|
450
|
+
</span>
|
|
451
|
+
</Tag>
|
|
452
|
+
</li>
|
|
453
|
+
);
|
|
454
|
+
})}
|
|
455
|
+
</ol>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export default PaceBar;
|