@twoux/ui 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +61 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/NOTICE.md +64 -0
- package/SHADCN-DIFF.md +228 -0
- package/agent/AGENTS.md +358 -0
- package/agent/docs-md/accessibility.md +49 -0
- package/agent/docs-md/action-catalogue.md +81 -0
- package/agent/docs-md/actions.md +74 -0
- package/agent/docs-md/agent-activity-log.md +70 -0
- package/agent/docs-md/agent-permissions.md +82 -0
- package/agent/docs-md/agent-run.md +90 -0
- package/agent/docs-md/agents-on-behalf.md +74 -0
- package/agent/docs-md/ai-column.md +85 -0
- package/agent/docs-md/ai-origin-label.md +70 -0
- package/agent/docs-md/announcing-features.md +73 -0
- package/agent/docs-md/answer-actions.md +81 -0
- package/agent/docs-md/app-shell.md +114 -0
- package/agent/docs-md/artifact-card.md +89 -0
- package/agent/docs-md/ask-on-screen.md +72 -0
- package/agent/docs-md/assistant-page.md +100 -0
- package/agent/docs-md/assistant-panel.md +142 -0
- package/agent/docs-md/avatar.md +82 -0
- package/agent/docs-md/banner.md +106 -0
- package/agent/docs-md/board.md +163 -0
- package/agent/docs-md/breadcrumb.md +89 -0
- package/agent/docs-md/breakdown-table.md +97 -0
- package/agent/docs-md/bulk-action-bar.md +112 -0
- package/agent/docs-md/button.md +293 -0
- package/agent/docs-md/card.md +115 -0
- package/agent/docs-md/change-list.md +99 -0
- package/agent/docs-md/change-request.md +166 -0
- package/agent/docs-md/chart-area.md +87 -0
- package/agent/docs-md/chart-bar.md +100 -0
- package/agent/docs-md/chart-box-plot.md +88 -0
- package/agent/docs-md/chart-bullet.md +89 -0
- package/agent/docs-md/chart-candlestick.md +112 -0
- package/agent/docs-md/chart-card-line.md +120 -0
- package/agent/docs-md/chart-donut.md +90 -0
- package/agent/docs-md/chart-drill-down.md +72 -0
- package/agent/docs-md/chart-funnel.md +71 -0
- package/agent/docs-md/chart-heatmap.md +76 -0
- package/agent/docs-md/chart-histogram.md +81 -0
- package/agent/docs-md/chart-map.md +165 -0
- package/agent/docs-md/chart-mixed.md +88 -0
- package/agent/docs-md/chart-price.md +92 -0
- package/agent/docs-md/chart-radial-bars.md +109 -0
- package/agent/docs-md/chart-radial-lines.md +111 -0
- package/agent/docs-md/chart-sankey.md +104 -0
- package/agent/docs-md/chart-scatter.md +87 -0
- package/agent/docs-md/chart-share-bar.md +89 -0
- package/agent/docs-md/chart-treemap.md +98 -0
- package/agent/docs-md/chart-waterfall.md +93 -0
- package/agent/docs-md/checkbox.md +131 -0
- package/agent/docs-md/choosing-selection-control.md +71 -0
- package/agent/docs-md/citations-sources.md +79 -0
- package/agent/docs-md/citing-sources.md +63 -0
- package/agent/docs-md/clarify-before-acting.md +69 -0
- package/agent/docs-md/clarifying-question.md +92 -0
- package/agent/docs-md/code-block.md +174 -0
- package/agent/docs-md/colours.md +87 -0
- package/agent/docs-md/columns-menu.md +140 -0
- package/agent/docs-md/combobox.md +161 -0
- package/agent/docs-md/command-palette.md +126 -0
- package/agent/docs-md/confidence-note.md +87 -0
- package/agent/docs-md/configurable-dashboard.md +76 -0
- package/agent/docs-md/confirmation-dialog.md +93 -0
- package/agent/docs-md/connected-tools.md +80 -0
- package/agent/docs-md/connector-picker.md +127 -0
- package/agent/docs-md/consistency.md +53 -0
- package/agent/docs-md/content-density.md +61 -0
- package/agent/docs-md/conversation-history.md +78 -0
- package/agent/docs-md/create-record.md +80 -0
- package/agent/docs-md/csv-import.md +88 -0
- package/agent/docs-md/dashboard-items.md +78 -0
- package/agent/docs-md/data-export.md +61 -0
- package/agent/docs-md/data-grid.md +137 -0
- package/agent/docs-md/data-vis-colours.md +76 -0
- package/agent/docs-md/data-visualisation.md +76 -0
- package/agent/docs-md/date-picker.md +178 -0
- package/agent/docs-md/delete-record.md +75 -0
- package/agent/docs-md/dense-table.md +495 -0
- package/agent/docs-md/design-tokens.md +53 -0
- package/agent/docs-md/disabled-read-only.md +75 -0
- package/agent/docs-md/drag-and-drop.md +74 -0
- package/agent/docs-md/dropdown-menu.md +137 -0
- package/agent/docs-md/edit-record.md +73 -0
- package/agent/docs-md/empty-loading-error.md +78 -0
- package/agent/docs-md/empty-state.md +128 -0
- package/agent/docs-md/error-messages.md +75 -0
- package/agent/docs-md/error-state.md +123 -0
- package/agent/docs-md/export-menu.md +86 -0
- package/agent/docs-md/feedback-prompt.md +88 -0
- package/agent/docs-md/field-mapping.md +78 -0
- package/agent/docs-md/file-drop.md +110 -0
- package/agent/docs-md/filter-bar.md +138 -0
- package/agent/docs-md/filter-panel.md +103 -0
- package/agent/docs-md/filtering-search.md +79 -0
- package/agent/docs-md/follow-ups-starters.md +79 -0
- package/agent/docs-md/form-validation.md +95 -0
- package/agent/docs-md/generated-columns.md +65 -0
- package/agent/docs-md/generated-drafts-files.md +71 -0
- package/agent/docs-md/grouped-list.md +89 -0
- package/agent/docs-md/guide-cue.md +170 -0
- package/agent/docs-md/help-panel.md +103 -0
- package/agent/docs-md/help-system.md +74 -0
- package/agent/docs-md/iconography.md +49 -0
- package/agent/docs-md/inbox.md +108 -0
- package/agent/docs-md/inline-editing-validation.md +80 -0
- package/agent/docs-md/inline-input-in-answer.md +70 -0
- package/agent/docs-md/items-palette.md +116 -0
- package/agent/docs-md/keyboard-model.md +47 -0
- package/agent/docs-md/keyboard-navigation.md +68 -0
- package/agent/docs-md/keyboard-shortcut-hint.md +87 -0
- package/agent/docs-md/keyboard-shortcut-sheet.md +90 -0
- package/agent/docs-md/labelling-ai-output.md +66 -0
- package/agent/docs-md/layout.md +47 -0
- package/agent/docs-md/list-table.md +254 -0
- package/agent/docs-md/loading-skeleton.md +91 -0
- package/agent/docs-md/managing-agents.md +71 -0
- package/agent/docs-md/message-composer.md +105 -0
- package/agent/docs-md/migrating.md +49 -0
- package/agent/docs-md/motion.md +93 -0
- package/agent/docs-md/navigation.md +76 -0
- package/agent/docs-md/notification-channels.md +65 -0
- package/agent/docs-md/notifications-decision.md +100 -0
- package/agent/docs-md/onboarding-step.md +114 -0
- package/agent/docs-md/order-book.md +108 -0
- package/agent/docs-md/page-header.md +96 -0
- package/agent/docs-md/pagination.md +88 -0
- package/agent/docs-md/pane-layout.md +105 -0
- package/agent/docs-md/period-comparison.md +66 -0
- package/agent/docs-md/plan-card.md +120 -0
- package/agent/docs-md/popover-rules.md +88 -0
- package/agent/docs-md/prompt-editor.md +90 -0
- package/agent/docs-md/propose-apply.md +65 -0
- package/agent/docs-md/provider-logo.md +97 -0
- package/agent/docs-md/radio.md +83 -0
- package/agent/docs-md/ranked-list.md +102 -0
- package/agent/docs-md/reasoning-progress.md +72 -0
- package/agent/docs-md/record-detail.md +71 -0
- package/agent/docs-md/regenerating-answers.md +70 -0
- package/agent/docs-md/run-status-bar.md +94 -0
- package/agent/docs-md/saved-tasks-schedules.md +63 -0
- package/agent/docs-md/saved-views.md +149 -0
- package/agent/docs-md/saving-views.md +68 -0
- package/agent/docs-md/scheduler.md +258 -0
- package/agent/docs-md/secondary-panels.md +72 -0
- package/agent/docs-md/segmented-control.md +88 -0
- package/agent/docs-md/selection-bulk-actions.md +70 -0
- package/agent/docs-md/separator.md +75 -0
- package/agent/docs-md/setup-checklist.md +92 -0
- package/agent/docs-md/shape-elevation.md +55 -0
- package/agent/docs-md/sheet-grid.md +211 -0
- package/agent/docs-md/side-panel.md +134 -0
- package/agent/docs-md/sign-in.md +114 -0
- package/agent/docs-md/spacing.md +55 -0
- package/agent/docs-md/split-button.md +100 -0
- package/agent/docs-md/stat-compare.md +82 -0
- package/agent/docs-md/static-dashboard.md +75 -0
- package/agent/docs-md/status-badge.md +140 -0
- package/agent/docs-md/step-card.md +75 -0
- package/agent/docs-md/stepper.md +92 -0
- package/agent/docs-md/suggested-changes.md +91 -0
- package/agent/docs-md/switch.md +78 -0
- package/agent/docs-md/table-settings.md +81 -0
- package/agent/docs-md/tabs.md +85 -0
- package/agent/docs-md/tag-input.md +108 -0
- package/agent/docs-md/tag.md +79 -0
- package/agent/docs-md/task-builder.md +87 -0
- package/agent/docs-md/team-instructions.md +72 -0
- package/agent/docs-md/templates-gallery.md +85 -0
- package/agent/docs-md/text-input.md +160 -0
- package/agent/docs-md/thinking-steps.md +89 -0
- package/agent/docs-md/timeline.md +189 -0
- package/agent/docs-md/timestamp.md +84 -0
- package/agent/docs-md/timestamps.md +70 -0
- package/agent/docs-md/toast.md +117 -0
- package/agent/docs-md/tooltip.md +90 -0
- package/agent/docs-md/typography.md +62 -0
- package/agent/docs-md/undo-destructive.md +66 -0
- package/agent/docs-md/unsaved-changes.md +71 -0
- package/agent/docs-md/usage-meter.md +80 -0
- package/agent/docs-md/user-feedback.md +71 -0
- package/agent/docs-md/voice-and-tone.md +60 -0
- package/agent/docs-md/why-choice.md +93 -0
- package/agent/docs-md/workspace-onboarding.md +74 -0
- package/agent/docs-md/writing.md +48 -0
- package/agent/llms-full.txt +18369 -0
- package/agent/llms.txt +198 -0
- package/agent/skills/twoux-audit/SKILL.md +49 -0
- package/agent/skills/twoux-build/SKILL.md +39 -0
- package/agent/skills/twoux-design/SKILL.md +54 -0
- package/agent/skills/twoux-write/SKILL.md +32 -0
- package/bin/check.mjs +123 -0
- package/bin/docs.mjs +24 -0
- package/bin/guard.mjs +27 -0
- package/bin/init.mjs +113 -0
- package/bin/knowledge.mjs +87 -0
- package/bin/mcp.mjs +116 -0
- package/bin/theme.mjs +168 -0
- package/bin/twoux.mjs +25 -0
- package/package.json +80 -3
- package/registry/jobs.json +1028 -0
- package/registry/palette.json +770 -0
- package/registry/rules.json +913 -0
- package/src/components/agent-review.tsx +364 -0
- package/src/components/ai-basics.tsx +212 -0
- package/src/components/ai-work.tsx +304 -0
- package/src/components/ai.tsx +227 -0
- package/src/components/alert-dialog.tsx +203 -0
- package/src/components/alert.tsx +86 -0
- package/src/components/avatar.tsx +114 -0
- package/src/components/badge.tsx +59 -0
- package/src/components/board.tsx +397 -0
- package/src/components/breadcrumb.tsx +114 -0
- package/src/components/button-group.tsx +90 -0
- package/src/components/button.tsx +109 -0
- package/src/components/calendar.tsx +247 -0
- package/src/components/card.tsx +102 -0
- package/src/components/chart-map.tsx +336 -0
- package/src/components/charts-more.tsx +1331 -0
- package/src/components/charts-radial.tsx +328 -0
- package/src/components/charts.tsx +390 -0
- package/src/components/checkbox.tsx +40 -0
- package/src/components/code.tsx +139 -0
- package/src/components/collapsible.tsx +37 -0
- package/src/components/combobox.tsx +339 -0
- package/src/components/command-extras.tsx +11 -0
- package/src/components/command.tsx +196 -0
- package/src/components/data-grid.tsx +213 -0
- package/src/components/data-table.tsx +334 -0
- package/src/components/data-views.tsx +740 -0
- package/src/components/date-picker.tsx +686 -0
- package/src/components/dense-table.tsx +207 -0
- package/src/components/dialog.tsx +162 -0
- package/src/components/dropdown-menu.tsx +264 -0
- package/src/components/empty.tsx +114 -0
- package/src/components/feedback-prompt.tsx +80 -0
- package/src/components/field.tsx +254 -0
- package/src/components/fields.tsx +112 -0
- package/src/components/flow.tsx +840 -0
- package/src/components/guide-cue.tsx +273 -0
- package/src/components/inbox.tsx +113 -0
- package/src/components/input-group.tsx +176 -0
- package/src/components/input.tsx +26 -0
- package/src/components/kbd.tsx +34 -0
- package/src/components/label.tsx +27 -0
- package/src/components/labels.tsx +176 -0
- package/src/components/motion.tsx +136 -0
- package/src/components/onboarding.tsx +454 -0
- package/src/components/order-book.tsx +185 -0
- package/src/components/pane-layout.tsx +417 -0
- package/src/components/popover.tsx +96 -0
- package/src/components/provider-logos.data.ts +49 -0
- package/src/components/radio-group.tsx +52 -0
- package/src/components/scheduler.tsx +997 -0
- package/src/components/segmented-control.tsx +28 -0
- package/src/components/select.tsx +195 -0
- package/src/components/separator.tsx +31 -0
- package/src/components/sheet.tsx +148 -0
- package/src/components/shell.tsx +426 -0
- package/src/components/shortcut-sheet.tsx +83 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/sonner.tsx +107 -0
- package/src/components/spinner.tsx +13 -0
- package/src/components/split-button.tsx +58 -0
- package/src/components/stepper.tsx +78 -0
- package/src/components/switch.tsx +43 -0
- package/src/components/table.tsx +126 -0
- package/src/components/tabs.tsx +118 -0
- package/src/components/tag-input.tsx +253 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/theme.tsx +80 -0
- package/src/components/timeline.tsx +408 -0
- package/src/components/toggle-group.tsx +87 -0
- package/src/components/toggle.tsx +54 -0
- package/src/components/tooltip.tsx +65 -0
- package/src/components/workspace-actions.tsx +294 -0
- package/src/components/workspace-import.tsx +465 -0
- package/src/components/workspace.tsx +1884 -0
- package/src/index.ts +80 -0
- package/src/lib/active-indicator.tsx +53 -0
- package/src/lib/chart-kit.tsx +72 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/shortcuts.tsx +61 -0
- package/src/lib/workspace.tsx +10 -0
- package/src/shadcn/alert-dialog.tsx +196 -0
- package/src/shadcn/alert.tsx +65 -0
- package/src/shadcn/avatar.tsx +108 -0
- package/src/shadcn/badge.tsx +47 -0
- package/src/shadcn/breadcrumb.tsx +108 -0
- package/src/shadcn/button-group.tsx +83 -0
- package/src/shadcn/button.tsx +63 -0
- package/src/shadcn/calendar.tsx +220 -0
- package/src/shadcn/card.tsx +91 -0
- package/src/shadcn/checkbox.tsx +31 -0
- package/src/shadcn/collapsible.tsx +33 -0
- package/src/shadcn/command.tsx +184 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/dropdown-menu.tsx +256 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +248 -0
- package/src/shadcn/input-group.tsx +170 -0
- package/src/shadcn/input.tsx +20 -0
- package/src/shadcn/kbd.tsx +28 -0
- package/src/shadcn/label.tsx +23 -0
- package/src/shadcn/popover.tsx +88 -0
- package/src/shadcn/radio-group.tsx +44 -0
- package/src/shadcn/select.tsx +189 -0
- package/src/shadcn/separator.tsx +27 -0
- package/src/shadcn/sheet.tsx +142 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/sonner.tsx +40 -0
- package/src/shadcn/switch.tsx +34 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +90 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +83 -0
- package/src/shadcn/toggle.tsx +46 -0
- package/src/shadcn/tooltip.tsx +56 -0
- package/src/styles/palette.css +127 -0
- package/src/styles/theme.css +35 -0
- package/src/styles/twoux.css +236 -0
- package/theme.json +14 -0
- package/README.md +0 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Data visualisation colours
|
|
2
|
+
|
|
3
|
+
One categorical palette of eight colours in a fixed order, one single-hue ordinal ramp of five steps, and a hard rule that status colours never become a series. Validated on white (#ffffff) with an OKLab and colour vision deficiency (CVD) simulator; every colour reaches at least 3:1.
|
|
4
|
+
|
|
5
|
+
Kind: foundation · Group: Visual foundation
|
|
6
|
+
|
|
7
|
+
## Principles
|
|
8
|
+
|
|
9
|
+
- Fixed order, always. Series take --c1, --c2, --c3 and so on in sequence, so 'Applications' is the same colour in every chart on a page. The categorical palette is applied strictly in sequence and stays consistent across charts.
|
|
10
|
+
- Every mark is visible. All eight categorical colours reach at least 3:1 on white, the WCAG minimum for graphical objects needed to understand a chart.
|
|
11
|
+
- No palette is colour-blind safe. There is no such thing as a true colour-blind safe palette, so colour is never the only way to tell series apart.
|
|
12
|
+
- Status is not data. --ok, --warn and --bad keep their meaning; a series that happens to be 'bad' news is still drawn in its categorical slot.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- Categorical (--c1 to --c8): unordered groups such as source (job board, referral, careers site) or listing type. Up to about eight series for line and bar charts.
|
|
17
|
+
- Ordinal ramp (--q1 to --q5): ordered buckets such as days open (0 to 7, 8 to 14...) or match score bands, and sequential heatmaps. More means more contrast with the surface: darker in the light theme, brighter in the dark theme, with a sequential palette for each theme.
|
|
18
|
+
|
|
19
|
+
## When not to use
|
|
20
|
+
|
|
21
|
+
- Do not use data colours for buttons, badges, tags or any UI element.
|
|
22
|
+
- Do not use more than three categorical colours in a scatter plot or categorical heatmap; fold the rest into 'Other' or split into small multiples.
|
|
23
|
+
- Do not use pie or donut charts for more than about five slices.
|
|
24
|
+
|
|
25
|
+
## Guidelines
|
|
26
|
+
|
|
27
|
+
- Why this order: adjacent pairs were ordered to stay apart. Worst adjacent difference under normal vision is OKLab ΔE 18.0; worst adjacent difference under simulated CVD is ΔE 10.0 (protan, --c3 teal against --c2 orange). Neighbours in a legend or stacked bar are the pairs people compare most, so adjacency is what the order optimises.
|
|
28
|
+
- Why only three for scatter and heatmaps: in those forms any colour can sit next to any other, so all pairs matter, not just neighbours. Only --c1, --c2 and --c3 pass the all-pairs check; from the fourth colour some pair falls too close under CVD.
|
|
29
|
+
- Why one hue for the ramp: --q1 to --q5 are indigo with lightness falling monotonically, so order reads from lightness alone, which survives CVD and greyscale print. One hue also means the ramp cannot be mistaken for categorical series.
|
|
30
|
+
- Limit of the ramp: --q1 is 2.23:1 on white, below 3:1. The lightest bucket must carry a value label or sit inside a bordered cell, so understanding never depends on seeing it.
|
|
31
|
+
- Separate touching segments (stacked bars, pies) with a 2px --surface gap so edges do not rely on hue difference.
|
|
32
|
+
- Charts sit on --surface (#ffffff), the background the palette was validated on. Do not place charts on --bg or tinted cards.
|
|
33
|
+
- Every chart has a legend or direct labels, and a table view of the same data.
|
|
34
|
+
|
|
35
|
+
## Accessibility
|
|
36
|
+
|
|
37
|
+
- 3:1 is required for the parts of a chart needed to understand it. When the values are also printed as text, the marks themselves are exempt, which is how --q1 stays usable.
|
|
38
|
+
- Colour is never the only cue: use direct labels, a legend in series order, and patterns or markers where lines cross.
|
|
39
|
+
- CVD was checked with simulation; the worst adjacent case is protan. Simulation models such as Machado et al. are a check, not a guarantee; about 200 million people have some CVD.
|
|
40
|
+
- Offer a 'View as table' toggle on every chart so screen reader and keyboard users get the same data.
|
|
41
|
+
|
|
42
|
+
## Do
|
|
43
|
+
|
|
44
|
+
- Assign series in order and keep a series on the same colour across every chart in a view.
|
|
45
|
+
- Fold small categories into 'Other' (drawn in --ink-3) rather than reaching for a ninth colour.
|
|
46
|
+
- Label the lightest ordinal bucket with its value.
|
|
47
|
+
|
|
48
|
+
## Don't
|
|
49
|
+
|
|
50
|
+
- Do not use --ok green for 'hired' and --bad red for 'rejected' in a chart; use categorical slots and say the meaning in the legend.
|
|
51
|
+
- Do not use colour for decoration, or give different colours to points in the same series.
|
|
52
|
+
- Do not reorder or skip palette colours to make a chart look nicer.
|
|
53
|
+
|
|
54
|
+
## Tokens
|
|
55
|
+
|
|
56
|
+
| Token | Value | Use |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `--c1` | `#5753e0` | Series 1, indigo (5.65:1) |
|
|
59
|
+
| `--c2` | `#e0602b` | Series 2, orange (3.57:1) |
|
|
60
|
+
| `--c3` | `#00897b` | Series 3, teal (4.32:1) |
|
|
61
|
+
| `--c4` | `#a87000` | Series 4, ochre (4.21:1) |
|
|
62
|
+
| `--c5` | `#cc4a7a` | Series 5, magenta (4.35:1) |
|
|
63
|
+
| `--c6` | `#008300` | Series 6, green (4.95:1) |
|
|
64
|
+
| `--c7` | `#2a78d6` | Series 7, blue (4.42:1) |
|
|
65
|
+
| `--c8` | `#d93f3e` | Series 8, red (4.44:1) |
|
|
66
|
+
| `--q1` | `#a9a6f3` | Ordinal lowest (2.23:1, needs a label) |
|
|
67
|
+
| `--q2` | `#7f7beb` | Ordinal 2 (3.54:1) |
|
|
68
|
+
| `--q3` | `#5753e0` | Ordinal midpoint (5.65:1) |
|
|
69
|
+
| `--q4` | `#3d39b0` | Ordinal 4 (8.71:1) |
|
|
70
|
+
| `--q5` | `#24226f` | Ordinal highest (13.7:1) |
|
|
71
|
+
|
|
72
|
+
## Related
|
|
73
|
+
|
|
74
|
+
- Colours
|
|
75
|
+
- Accessibility
|
|
76
|
+
- Design tokens
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Data visualisation
|
|
2
|
+
|
|
3
|
+
Start from the question the person is asking, pick the one chart that answers it, give it a noun title, units and a period, and ship it with a table view and its four states.
|
|
4
|
+
|
|
5
|
+
Kind: pattern · Group: Patterns: data and dashboards
|
|
6
|
+
|
|
7
|
+
**Problem:** Hiring leads open dashboards to answer a few questions: is volume going up, did something break, how do teams compare, why did this week drop. When charts are picked for looks, or each team invents its own axis and colour habits, people misread them or stop trusting them.
|
|
8
|
+
|
|
9
|
+
**Solution:** Name the objective first (trend, anomaly, comparison, investigation), then choose the chart from a fixed table, show the fewest series that answer it, and finish it the same way every time: noun title, period, one y-axis, colours in order, table view, accessible name and the four states.
|
|
10
|
+
|
|
11
|
+
**Built from:** Line chart card, Bar chart, Stacked area chart, Bar and line chart, Donut chart, Scatter chart, Heatmap, Funnel, Stat card with comparison, Breakdown table, Data visualisation colours, Board and board item
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
1. Write the question in one line, then tag it: trend over time, spotting something unusual, comparing groups, or investigating a known problem.
|
|
16
|
+
2. Choose the chart: change over time is a line; amounts per category or period are bars; parts of a total over time are a stacked area; two to five parts at one moment are a donut; ordered stages are a funnel; two measures across records are a scatter; one number is a stat card.
|
|
17
|
+
3. Show the fewest series that answer the question: up to eight lines or bars, four on a bar and line chart, five parts on a donut. Past that, fold into "Other" or add a series filter.
|
|
18
|
+
4. Finish the chart: noun title, period subtitle, axis titles with units, one y-axis, legend only for two or more series, categorical colours in order.
|
|
19
|
+
5. Add the table view and the accessible name and description.
|
|
20
|
+
6. Build the four states: loading, empty, no match and error.
|
|
21
|
+
|
|
22
|
+
## When to use
|
|
23
|
+
|
|
24
|
+
- Whenever a screen shows numbers that people read as a shape: trends, comparisons, splits.
|
|
25
|
+
- On dashboards and report pages, and in a record's detail when history matters, such as applications per day for a listing.
|
|
26
|
+
|
|
27
|
+
## When not to use
|
|
28
|
+
|
|
29
|
+
- For decoration or to fill space. If nobody acts on it, leave it out.
|
|
30
|
+
- When the person needs exact values per record. Use the List table or Breakdown table.
|
|
31
|
+
- When there is one number that matters. Use the Stat card with comparison.
|
|
32
|
+
|
|
33
|
+
## Guidelines
|
|
34
|
+
|
|
35
|
+
- Never let the chart suggest something the data does not say. Bars and areas start at zero; a line that starts elsewhere says so on the axis.
|
|
36
|
+
- Keep the number of charts per view small, and give charts of equal importance the same size.
|
|
37
|
+
- A series keeps its colour on every chart on the page, and hiding a series never repaints the others.
|
|
38
|
+
- Show gaps where data is missing. A missing day is not a zero.
|
|
39
|
+
- When a chart has three or more series, offer a series filter; with one series, offer neither a filter nor a legend.
|
|
40
|
+
- Thresholds are dashed, fixed lines with a text label, such as "Target 40". They are not a series and never use the status colours on their own.
|
|
41
|
+
|
|
42
|
+
## Content
|
|
43
|
+
|
|
44
|
+
- Titles are nouns that name the measure: "Applications", "Time to first reply". Never "Chart 1" or a sentence about the result.
|
|
45
|
+
- Axis titles are one to three words with the unit: "Salary (£k)". Time axes need no title; the dates speak for themselves.
|
|
46
|
+
- Dates on axes are short and UK style: "3 Nov", "Mon", "Nov". Currency carries its symbol: "£32k".
|
|
47
|
+
- State copy follows TwoUX's states pattern: "Loading applications", "No applications in this period", "No data matches these filters" with Clear filters, and "Couldn't load applications" with Try again.
|
|
48
|
+
- Use "previous" and "following", not "above" and "below", and "select", not "click".
|
|
49
|
+
|
|
50
|
+
## Accessibility
|
|
51
|
+
|
|
52
|
+
- Give every chart an accessible name matching its visible title and a description in the form "[Chart type] showing [data], [period]."
|
|
53
|
+
- Every chart has a Table view with the same values; it is the long description and the fallback for anyone who reads tables more easily.
|
|
54
|
+
- Colour is never the only cue: series have names in the tooltip and direct labels, groups have shapes or line styles.
|
|
55
|
+
- Marks needed to read the data reach 3:1 against the background; text uses text colours, never the series colour.
|
|
56
|
+
- The plot, legend and popover are reachable by keyboard in a logical order, and pinned popovers stay until dismissed.
|
|
57
|
+
|
|
58
|
+
## Do
|
|
59
|
+
|
|
60
|
+
- Do start from the question and name it in the chart title.
|
|
61
|
+
- Do ship a Table view with every chart.
|
|
62
|
+
- Do split two units into two charts side by side.
|
|
63
|
+
|
|
64
|
+
## Don't
|
|
65
|
+
|
|
66
|
+
- Don't add a second y-axis.
|
|
67
|
+
- Don't colour each bar of a single series differently.
|
|
68
|
+
- Don't use a donut for change over time or for more than five parts.
|
|
69
|
+
- Don't print values or labels in the series colour.
|
|
70
|
+
|
|
71
|
+
## Related
|
|
72
|
+
|
|
73
|
+
- Chart drill-down
|
|
74
|
+
- Comparison over time
|
|
75
|
+
- Empty, loading and error states
|
|
76
|
+
- Filtering and search
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Date picker
|
|
2
|
+
|
|
3
|
+
Enter a calendar date, or a range of dates, by typing it or choosing it on a calendar: a closing date, a start date, a reporting period.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- For a date people set on a record: closing date, start date, contract end.
|
|
10
|
+
- For a period that narrows a list or a chart: applications in the last 30 days, a quarter for a report.
|
|
11
|
+
- When people need to see the weekday or the week around a date, such as booking around weekends.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- For a date people know by heart and never browse to, such as a date of birth. Use a Text input (text-input) with the hint dd/mm/yyyy.
|
|
16
|
+
- For booking or moving events in time. Use the Scheduler (scheduler).
|
|
17
|
+
- For a chart's fixed period options such as Daily or Weekly. Use a Segmented control (segmented-control).
|
|
18
|
+
- To show a date. Use Timestamp (timestamp).
|
|
19
|
+
|
|
20
|
+
## Structure
|
|
21
|
+
|
|
22
|
+
1. Label
|
|
23
|
+
2. Field: the date as dd/mm/yyyy (From and To for a range)
|
|
24
|
+
3. Calendar button ("Choose date")
|
|
25
|
+
4. Popover: preset periods (range, optional), month heading, previous and next month, grid starting on Monday
|
|
26
|
+
5. Footer: why some dates are unavailable (optional)
|
|
27
|
+
6. Hint or error under the field
|
|
28
|
+
|
|
29
|
+
## Guidelines
|
|
30
|
+
|
|
31
|
+
- Typing is the main path: the field accepts 12/03/2026, 12/3/26, 12 Mar 2026 and 2026-03-12, and rewrites the value as 12/03/2026 when people leave it, so they see how it was read.
|
|
32
|
+
- The calendar is the second path: the button at the end of the field, or Alt+↓ in the field. It opens on the value's month, otherwise today's.
|
|
33
|
+
- Values are ISO calendar dates ("2026-03-12"), never a Date at midnight UTC, so they match Timestamp and don't land on the day before west of London.
|
|
34
|
+
- Weeks start on Monday and month and weekday names are UK English.
|
|
35
|
+
- Say why dates are unavailable in the footer, such as "Closing dates must be on a weekday." Unavailable days are skipped by the arrow keys.
|
|
36
|
+
- A range shows two months side by side from 768px, one below. The first pick sets the start, the second the end; days between are filled as people point or move.
|
|
37
|
+
- Preset periods (Last 7 days, Last 30 days, This month…) apply at once. In a filter they are stored by name, so a saved view still means "last 30 days" next month. In a filter bar use DateFilterPill, which reads as a sentence like other filter pills: Applied is in Last 30 days.
|
|
38
|
+
- Check the value when people leave the field and on submit; show the error under the field, never in a toast.
|
|
39
|
+
|
|
40
|
+
## Content
|
|
41
|
+
|
|
42
|
+
- The hint is the format: dd/mm/yyyy. Add a reason only when it helps: "The last day applications are accepted."
|
|
43
|
+
- Errors say what to do: "Enter the closing date as dd/mm/yyyy, such as 12/03/2026.", "Choose a date on or after 7 December 2026.", "The end date is before the start date. Change one of them."
|
|
44
|
+
- Preset names say the period: Last 7 days, This month, Last quarter.
|
|
45
|
+
|
|
46
|
+
## Accessibility
|
|
47
|
+
|
|
48
|
+
- The popover is a dialog named "Choose date". The grid's day buttons have full names: "Thursday 12 March 2026", with "Today" and "selected" added.
|
|
49
|
+
- The month heading is a polite live region, so changing month is read out.
|
|
50
|
+
- Focus moves to the selected day, or today, when the calendar opens, and back to the field when it closes.
|
|
51
|
+
- Today has a ring and the selected day a fill, and both are named, so neither relies on colour alone.
|
|
52
|
+
- Day cells are 32px, above the 24px minimum target.
|
|
53
|
+
|
|
54
|
+
## Keyboard
|
|
55
|
+
|
|
56
|
+
| Keys | Action |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Alt+↓ | Open the calendar from the field |
|
|
59
|
+
| Enter (in the field) | Read the typed date |
|
|
60
|
+
| ← → ↑ ↓ | Previous or next day; same day in the previous or next week |
|
|
61
|
+
| Home / End | Start or end of the week |
|
|
62
|
+
| Page Up / Page Down | Previous or next month (Shift for the year) |
|
|
63
|
+
| Enter or Space | Choose the focused day |
|
|
64
|
+
| Esc | Close the calendar without changing the value; focus returns to the field |
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
### closing-date
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { DatePicker, fromIsoDate } from "@twoux/ui";
|
|
72
|
+
import { useState } from "react";
|
|
73
|
+
|
|
74
|
+
export const meta = { title: "Closing date", description: "Type the date, or press Alt+↓ or the calendar button. Weekends and past dates can't be chosen, and the calendar says why.", order: 1 };
|
|
75
|
+
|
|
76
|
+
const today = new Date();
|
|
77
|
+
const iso = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
78
|
+
|
|
79
|
+
export default function Example() {
|
|
80
|
+
const [value, setValue] = useState<string | null>(null);
|
|
81
|
+
return (
|
|
82
|
+
<div className="w-72">
|
|
83
|
+
<DatePicker
|
|
84
|
+
label="Closing date"
|
|
85
|
+
value={value}
|
|
86
|
+
onValueChange={setValue}
|
|
87
|
+
min={iso(today)}
|
|
88
|
+
isDateUnavailable={(d) => [0, 6].includes(fromIsoDate(d).getDay())}
|
|
89
|
+
unavailableReason="Closing dates must be on a weekday."
|
|
90
|
+
hint="The last day applications are accepted. dd/mm/yyyy"
|
|
91
|
+
required
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### filter-pill
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { DateFilterPill, type DateRangeValue, FilterBar, FilterPill, FilterSummary, dateFilterToValues, describeDateFilter } from "@twoux/ui";
|
|
102
|
+
import { useState } from "react";
|
|
103
|
+
|
|
104
|
+
export const meta = { title: "In a filter bar", description: "DateFilterPill sits beside FilterPill and reads as a sentence. Presets and calendar ranges apply at once; typed dates apply on Enter. The value is stored as URL-safe strings.", order: 3 };
|
|
105
|
+
|
|
106
|
+
const STAGES = [{ value: "screen", label: "Screen", count: 42 }, { value: "interview", label: "Interview", count: 18 }, { value: "offer", label: "Offer", count: 4 }];
|
|
107
|
+
|
|
108
|
+
export default function Example() {
|
|
109
|
+
const [stages, setStages] = useState(["interview"]);
|
|
110
|
+
const [applied, setApplied] = useState<DateRangeValue>({ preset: "last-30-days" });
|
|
111
|
+
const [closing, setClosing] = useState<DateRangeValue>({});
|
|
112
|
+
const d = describeDateFilter(applied);
|
|
113
|
+
return (
|
|
114
|
+
<div className="grid max-w-full gap-3">
|
|
115
|
+
<FilterBar>
|
|
116
|
+
<FilterPill field="Stage" options={STAGES} selected={stages} onChange={setStages} onRemove={() => setStages([])} />
|
|
117
|
+
<DateFilterPill field="Applied" value={applied} onChange={setApplied} onRemove={() => setApplied({})} />
|
|
118
|
+
<DateFilterPill field="Closing date" value={closing} onChange={setClosing} presets={[]} />
|
|
119
|
+
</FilterBar>
|
|
120
|
+
<FilterSummary shown={12} total={64} noun="candidates" onClear={() => { setStages([]); setApplied({}); setClosing({}); }} />
|
|
121
|
+
<p className="text-sm text-ink-3">
|
|
122
|
+
URL: <code className="tabular">?applied={dateFilterToValues(applied).join(",") || "—"}</code>
|
|
123
|
+
{d && <> · Applied {d.operator} {d.spoken}</>}
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### range-presets
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { DateRangePicker, type DateRangeValue, resolveDateRange, formatDateLong } from "@twoux/ui";
|
|
134
|
+
import { useState } from "react";
|
|
135
|
+
|
|
136
|
+
export const meta = { title: "Range with presets", description: "Pick a preset or two dates. A preset is kept by name, so a saved view still means last 30 days next month.", order: 2 };
|
|
137
|
+
|
|
138
|
+
export default function Example() {
|
|
139
|
+
const [value, setValue] = useState<DateRangeValue>({ preset: "last-30-days" });
|
|
140
|
+
const { from, to } = resolveDateRange(value);
|
|
141
|
+
return (
|
|
142
|
+
<div className="grid w-80 gap-3">
|
|
143
|
+
<DateRangePicker label="Applied" value={value} onValueChange={setValue} presets maxDays={366} />
|
|
144
|
+
<p className="text-sm text-ink-2">
|
|
145
|
+
{from && to ? <>Showing applications from {formatDateLong(from)} to {formatDateLong(to)}.</> : "Choose a period."}
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### states
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { DatePicker } from "@twoux/ui";
|
|
156
|
+
|
|
157
|
+
export const meta = { title: "States", description: "The error says what to do. Read-only keeps the date selectable and drops the calendar.", order: 4 };
|
|
158
|
+
|
|
159
|
+
const noop = () => {};
|
|
160
|
+
|
|
161
|
+
export default function Example() {
|
|
162
|
+
return (
|
|
163
|
+
<div className="grid w-72 gap-4">
|
|
164
|
+
<DatePicker label="Start date" value={null} onValueChange={noop} required error="Enter a start date." />
|
|
165
|
+
<DatePicker label="Contract end" value="2027-03-31" onValueChange={noop} readOnly />
|
|
166
|
+
<DatePicker label="Probation review" value="2026-12-07" onValueChange={noop} disabled />
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Related
|
|
173
|
+
|
|
174
|
+
- Text input
|
|
175
|
+
- Timestamp
|
|
176
|
+
- Filter pills and filter bar
|
|
177
|
+
- Scheduler
|
|
178
|
+
- Form validation
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Deleting records
|
|
2
|
+
|
|
3
|
+
Deleting moves records to Deleted with Undo. Only permanent or cascading deletes ask first, and only the rare, severe ones ask people to type the name.
|
|
4
|
+
|
|
5
|
+
Kind: pattern · Group: Patterns: general
|
|
6
|
+
|
|
7
|
+
**Problem:** Delete is where TwoUX's Undo-first rule meets real loss. Without a shared ladder, teams either confirm every removal or let a single click erase a candidate's history, and typed confirmations spread to places where they only slow people down.
|
|
8
|
+
|
|
9
|
+
**Solution:** Four levels, chosen by what is lost and who it affects. Remove a part of a form you are editing at once. Delete a record to Deleted, with Undo. Permanently delete in a confirmation dialog that names what goes and what stays. Ask for the typed name only when the delete is permanent and takes other records with it.
|
|
10
|
+
|
|
11
|
+
**Built from:** Toast, Confirmation dialog, Bulk action bar, Banner (inline alert), Text input, Button, Keyboard shortcut hint
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
1. Remove a part of a form being edited, such as a screening question or a tag: no confirmation, it goes at once, with Undo where it saves straight away.
|
|
16
|
+
2. Delete a record, such as a draft listing or a duplicate candidate: it moves to Deleted, where it can be restored for 30 days. Show "Listing deleted. Undo".
|
|
17
|
+
3. Permanently delete, such as emptying Deleted or erasing a candidate on request: a confirmation dialog titled with the action and count, naming what goes and what stays.
|
|
18
|
+
4. Permanent and cascading, such as deleting a workspace or a team with live listings: the same dialog, plus a warning banner listing what else goes, and a field where the person types the name.
|
|
19
|
+
5. If something must happen first, such as closing a live listing that still has applicants, the dialog says so and links to it. The confirm field and button wait until it is done, and the reason is written in the dialog.
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
- Deleted with Undo: the default for every record people can delete.
|
|
24
|
+
- Confirmation dialog: anything that cannot come back, including bulk permanent deletes.
|
|
25
|
+
- Typed name: permanent deletes that also remove other records or affect other people's work.
|
|
26
|
+
|
|
27
|
+
## When not to use
|
|
28
|
+
|
|
29
|
+
- Don't use delete for ending a process. Closing a listing or rejecting a candidate are their own actions with their own wording.
|
|
30
|
+
- Don't ask for a typed name on routine deletes. It slows people down and they learn to type without reading.
|
|
31
|
+
- Don't let the assistant delete. It can propose a delete, which the person applies.
|
|
32
|
+
|
|
33
|
+
## Guidelines
|
|
34
|
+
|
|
35
|
+
- Choose the level by three questions: can it come back, does it take other records with it, and what would it cost to redo.
|
|
36
|
+
- The dialog body names the record in bold and states the result: "Permanently delete Priya Shah? Her application, notes and interview feedback are erased. You can't undo this."
|
|
37
|
+
- Say what stays as well as what goes: "Reports keep the anonymised counts."
|
|
38
|
+
- Typed confirmation: single record, type its name; bulk, type "delete". Matching ignores case and extra spaces, and paste works.
|
|
39
|
+
- Always confirm in a dialog, never a popover or a separate page.
|
|
40
|
+
- A bulk delete from the bulk action bar uses the level of its most severe item, and the title carries the count: "Permanently delete 12 candidates?"
|
|
41
|
+
|
|
42
|
+
## Content
|
|
43
|
+
|
|
44
|
+
- Use "Remove" for taking something out of a form or list it still exists elsewhere in, and "Delete" for the record itself.
|
|
45
|
+
- Dialog title is the action and count as a question: "Delete workspace?", "Permanently delete 3 listings?"
|
|
46
|
+
- Buttons name the outcome: "Keep candidate" and "Delete permanently". Never "OK" or "Yes".
|
|
47
|
+
- Typed confirmation label: "Type Leeds hiring team to confirm".
|
|
48
|
+
|
|
49
|
+
## Accessibility
|
|
50
|
+
|
|
51
|
+
- Delete dialogs use role alertdialog with the title as the label and the consequence as the description.
|
|
52
|
+
- Focus starts on the safe button, or on the typed confirmation field when there is one.
|
|
53
|
+
- The typed confirmation field has a visible label naming the exact text, not only a placeholder.
|
|
54
|
+
- After a delete, focus moves to the next row in the list, or the list heading if none is left, and the toast is announced.
|
|
55
|
+
|
|
56
|
+
## Do
|
|
57
|
+
|
|
58
|
+
- Do make Deleted the default and offer Undo.
|
|
59
|
+
- Do state prerequisites in the dialog and link to them.
|
|
60
|
+
- Do scale friction to severity and nothing more.
|
|
61
|
+
|
|
62
|
+
## Don't
|
|
63
|
+
|
|
64
|
+
- Don't write "Are you sure?".
|
|
65
|
+
- Don't focus the delete button by default.
|
|
66
|
+
- Don't delete a live listing that has applicants in one step; ask people to close it first.
|
|
67
|
+
|
|
68
|
+
## Related
|
|
69
|
+
|
|
70
|
+
- Undo and destructive actions
|
|
71
|
+
- Selection and bulk actions
|
|
72
|
+
- Editing a record: inline, section or whole page
|
|
73
|
+
- Confirmation dialog
|
|
74
|
+
- Toast
|
|
75
|
+
- Bulk action bar
|