@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,189 @@
|
|
|
1
|
+
# Timeline
|
|
2
|
+
|
|
3
|
+
Scheduled work across a few tracks on one time ruler, such as releases and maintenance per service or hiring campaigns per team. Spans and instants, a labelled Now line, four zoom levels; selecting an event opens it in the side panel.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Seeing what runs in parallel across services, teams or resources, such as release and maintenance windows, and where it overlaps.
|
|
10
|
+
- Planning or reviewing phases with a start and an end, such as hiring campaigns per team, with deadlines as instants.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- For what happened, one item after another, in one stream. Use the Agent activity log or a Grouped list.
|
|
15
|
+
- For amounts over time. Use the Line chart card or a Bar chart.
|
|
16
|
+
- For editing dates by dragging. The Timeline is read-only; change dates in the record's side panel.
|
|
17
|
+
- For more than about 12 tracks or when people compare exact dates. Use a List table with start and end columns.
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Toolbar: period, Go to now, zoom
|
|
22
|
+
2. Ruler with UK date ticks
|
|
23
|
+
3. Sticky track label column
|
|
24
|
+
4. Spans (start to end) and instants (diamond)
|
|
25
|
+
5. Lanes for overlapping events in a track
|
|
26
|
+
6. Now line labelled with the time
|
|
27
|
+
7. Tooltip: track, status word, start, end, length
|
|
28
|
+
|
|
29
|
+
## Guidelines
|
|
30
|
+
|
|
31
|
+
- Keep to about 12 tracks and one period people plan in, such as two weeks or a quarter.
|
|
32
|
+
- Give every event a status word with statusLabel (Done, At risk, Blocked); the tint repeats it, never replaces it.
|
|
33
|
+
- Selecting an event opens its record in the SidePanel; pass its id back as selectedId so the timeline marks it.
|
|
34
|
+
- Pass now for current and future periods; leave it out for past ones. Go to now appears only when Now is in the period.
|
|
35
|
+
- Offer the zoom levels that fit the period with zooms: hour and day for a week, week and month for a quarter.
|
|
36
|
+
- Wrap it in ChartFrame for the title, the Table view (timelineTable) and the loading, error and no match states.
|
|
37
|
+
|
|
38
|
+
## Content
|
|
39
|
+
|
|
40
|
+
- Dates are UK format, "Wed 11 Nov, 09:00"; the tooltip and table give the full date with zone, as Timestamp does.
|
|
41
|
+
- Name tracks with nouns (Payments API, Engineering) and events by what happens (Release 4.13, Database upgrade).
|
|
42
|
+
|
|
43
|
+
## Accessibility
|
|
44
|
+
|
|
45
|
+
- 2.1.1 Keyboard and the APG grid: one tab stop, arrow keys move between events, Enter or Space opens one.
|
|
46
|
+
- Each event is a gridcell named "Label, track, start to end, status"; each track is a row with a row header.
|
|
47
|
+
- 1.4.1 Use of Color: status is a word in the tooltip, accessible name and table view, not the tint alone.
|
|
48
|
+
- 1.4.10 Reflow: only the time area scrolls, inside its own box, with the track column sticky.
|
|
49
|
+
- 2.1.4 Character Key Shortcuts: + and − zoom only while the timeline has focus.
|
|
50
|
+
- 1.1.1 and complex images: a description gives tracks, events, period and current time, and the Table view lists every event.
|
|
51
|
+
- 1.4.13 Content on Hover or Focus: the tooltip follows hover and focus and closes with Esc.
|
|
52
|
+
|
|
53
|
+
## Keyboard
|
|
54
|
+
|
|
55
|
+
| Keys | Action |
|
|
56
|
+
|---|---|
|
|
57
|
+
| Tab | Focus the timeline |
|
|
58
|
+
| ← → | Previous or next event in the track |
|
|
59
|
+
| ↑ ↓ | Nearest event in time on the track above or below |
|
|
60
|
+
| Home End | First or last event in the track (with Ctrl, overall) |
|
|
61
|
+
| Enter Space | Open the event in the side panel |
|
|
62
|
+
| + − | Zoom in or out (off when single-key shortcuts are turned off) |
|
|
63
|
+
| Esc | Clear the focused event and hide the tooltip |
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
### hiring-campaigns
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { ChartFrame, ChartStatus, Segment, SegmentedControl, Timeline, TimelineTrack, timelineTable } from "@twoux/ui";
|
|
71
|
+
import { useState } from "react";
|
|
72
|
+
|
|
73
|
+
export const meta = { title: "Hiring campaign phases by team", description: "A quarter of hiring campaigns at month and week zoom, with closing dates as instants. The state control shows the frame's loading, empty and error states.", order: 2 };
|
|
74
|
+
|
|
75
|
+
const TRACKS: TimelineTrack[] = [
|
|
76
|
+
{ id: "eng", label: "Engineering", events: [
|
|
77
|
+
{ id: "c1", label: "Sourcing: backend", start: "2026-10-05", end: "2026-10-30", status: "ok", statusLabel: "Done" },
|
|
78
|
+
{ id: "c2", label: "Interviews: backend", start: "2026-10-26", end: "2026-11-27", status: "warn", statusLabel: "Behind plan" },
|
|
79
|
+
{ id: "c3", label: "Offers close", start: "2026-12-04", status: "neutral", statusLabel: "Planned" },
|
|
80
|
+
] },
|
|
81
|
+
{ id: "sales", label: "Sales", events: [
|
|
82
|
+
{ id: "c4", label: "Account executives, London", start: "2026-10-12", end: "2026-12-11", status: "ok", statusLabel: "On track" },
|
|
83
|
+
{ id: "c5", label: "SDR graduate intake", start: "2026-11-16", end: "2026-12-18", status: "neutral", statusLabel: "Planned" },
|
|
84
|
+
] },
|
|
85
|
+
{ id: "support", label: "Customer support", events: [
|
|
86
|
+
{ id: "c6", label: "Weekend cover", start: "2026-10-19", end: "2026-11-13", status: "bad", statusLabel: "Paused" },
|
|
87
|
+
{ id: "c7", label: "Listing closes", start: "2026-11-13", status: "bad", statusLabel: "Paused" },
|
|
88
|
+
] },
|
|
89
|
+
{ id: "finance", label: "Finance", events: [
|
|
90
|
+
{ id: "c8", label: "Financial controller", start: "2026-11-02", end: "2026-12-23", status: "ok", statusLabel: "On track" },
|
|
91
|
+
] },
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
export default function Example() {
|
|
95
|
+
const [state, setState] = useState<ChartStatus>("ready");
|
|
96
|
+
return (
|
|
97
|
+
<div className="grid w-full gap-2">
|
|
98
|
+
<SegmentedControl size="sm" value={state} onValueChange={(v) => setState(v as ChartStatus)} aria-label="Example state" className="justify-self-start">
|
|
99
|
+
<Segment value="ready">Data</Segment><Segment value="loading">Loading</Segment><Segment value="error">Error</Segment>
|
|
100
|
+
</SegmentedControl>
|
|
101
|
+
<ChartFrame className="w-full" title="Hiring campaigns" description="Q4 2026, all teams" status={state} onRetry={() => setState("ready")} table={timelineTable(TRACKS)} height={200}>
|
|
102
|
+
<Timeline tracks={TRACKS} start="2026-10-01" end="2027-01-01" now="2026-11-11T09:00" defaultZoom="week" zooms={["week", "month"]} aria-label="Hiring campaigns" />
|
|
103
|
+
</ChartFrame>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### release-schedule
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { Badge, Button, ChartFrame, SidePanel, Timeline, TimelineEvent, TimelineTrack, timelineDate, timelineTable } from "@twoux/ui";
|
|
113
|
+
import { useState } from "react";
|
|
114
|
+
|
|
115
|
+
export const meta = { title: "Releases and maintenance by service", description: "Two weeks of releases, maintenance windows and freezes across five services. Select an event, or press Enter on it, to open it in the side panel; + and − zoom.", order: 1 };
|
|
116
|
+
|
|
117
|
+
const TRACKS: TimelineTrack[] = [
|
|
118
|
+
{ id: "payments", label: "Payments API", events: [
|
|
119
|
+
{ id: "REL-412", label: "Release 4.12", start: "2026-11-10T09:00", end: "2026-11-10T11:00", status: "ok", statusLabel: "Done" },
|
|
120
|
+
{ id: "MNT-88", label: "Database upgrade", start: "2026-11-12T22:00", end: "2026-11-13T02:00", status: "warn", statusLabel: "At risk" },
|
|
121
|
+
{ id: "REL-413", label: "Release 4.13", start: "2026-11-17T09:00", end: "2026-11-17T10:30", status: "neutral" },
|
|
122
|
+
] },
|
|
123
|
+
{ id: "billing", label: "Billing", events: [
|
|
124
|
+
{ id: "FRZ-11", label: "Month-end freeze", start: "2026-11-16T00:00", end: "2026-11-19T00:00", status: "neutral", statusLabel: "Scheduled" },
|
|
125
|
+
{ id: "REL-207", label: "Invoice PDF fix", start: "2026-11-11T13:00", status: "ok", statusLabel: "Deployed" },
|
|
126
|
+
] },
|
|
127
|
+
{ id: "auth", label: "Identity", events: [
|
|
128
|
+
{ id: "MNT-91", label: "Certificate rotation", start: "2026-11-11T06:00", end: "2026-11-11T08:00", status: "ok", statusLabel: "Done" },
|
|
129
|
+
{ id: "REL-330", label: "SSO for Okta", start: "2026-11-11T12:00", end: "2026-11-12T18:00", status: "bad", statusLabel: "Blocked" },
|
|
130
|
+
] },
|
|
131
|
+
{ id: "search", label: "Search", events: [
|
|
132
|
+
{ id: "MNT-93", label: "Index rebuild", start: "2026-11-14T01:00", end: "2026-11-14T07:00", status: "neutral" },
|
|
133
|
+
{ id: "REL-119", label: "Ranking change", start: "2026-11-18T10:00", status: "warn", statusLabel: "Needs review" },
|
|
134
|
+
] },
|
|
135
|
+
{ id: "web", label: "Web app", events: [
|
|
136
|
+
{ id: "REL-905", label: "Weekly release", start: "2026-11-12T10:00", end: "2026-11-12T12:00", status: "ok", statusLabel: "On track" },
|
|
137
|
+
{ id: "REL-906", label: "Weekly release", start: "2026-11-19T10:00", end: "2026-11-19T12:00", status: "neutral" },
|
|
138
|
+
] },
|
|
139
|
+
];
|
|
140
|
+
const VARIANT = { ok: "success", warn: "warning", bad: "destructive", neutral: "secondary" } as const;
|
|
141
|
+
|
|
142
|
+
export default function Example() {
|
|
143
|
+
const [open, setOpen] = useState<{ e: TimelineEvent; track: string } | null>(null);
|
|
144
|
+
return (
|
|
145
|
+
<div className="flex w-full overflow-hidden rounded-sm border border-line bg-surface">
|
|
146
|
+
<div className="min-w-0 flex-1 p-3">
|
|
147
|
+
<ChartFrame className="w-full" title="Releases and maintenance" description="All services, 9 to 22 November" table={timelineTable(TRACKS)} height={260}>
|
|
148
|
+
<Timeline
|
|
149
|
+
tracks={TRACKS}
|
|
150
|
+
start="2026-11-09T00:00"
|
|
151
|
+
end="2026-11-23T00:00"
|
|
152
|
+
now="2026-11-11T14:30"
|
|
153
|
+
defaultZoom="day"
|
|
154
|
+
zooms={["hour", "day", "week"]}
|
|
155
|
+
selectedId={open?.e.id ?? null}
|
|
156
|
+
onSelect={(e, t) => setOpen({ e, track: t.label })}
|
|
157
|
+
aria-label="Releases and maintenance"
|
|
158
|
+
/>
|
|
159
|
+
</ChartFrame>
|
|
160
|
+
</div>
|
|
161
|
+
{open && (
|
|
162
|
+
<SidePanel
|
|
163
|
+
open
|
|
164
|
+
onOpenChange={(o) => !o && setOpen(null)}
|
|
165
|
+
label="Change details"
|
|
166
|
+
title={open.e.label}
|
|
167
|
+
meta={<span className="flex items-center gap-2"><span className="font-mono">{open.e.id}</span><Badge variant={VARIANT[open.e.status ?? "neutral"]}>{open.e.statusLabel ?? "Planned"}</Badge></span>}
|
|
168
|
+
footer={<Button size="sm">Edit change</Button>}
|
|
169
|
+
>
|
|
170
|
+
<dl className="grid grid-cols-2 gap-x-3 gap-y-2 text-base">
|
|
171
|
+
<dt className="text-ink-2">Service</dt><dd className="text-ink">{open.track}</dd>
|
|
172
|
+
<dt className="text-ink-2">Starts</dt><dd className="text-ink">{timelineDate(new Date(open.e.start))}</dd>
|
|
173
|
+
<dt className="text-ink-2">Ends</dt><dd className="text-ink">{open.e.end ? timelineDate(new Date(open.e.end)) : "Same time"}</dd>
|
|
174
|
+
</dl>
|
|
175
|
+
</SidePanel>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Related
|
|
183
|
+
|
|
184
|
+
- Side panel (drawer)
|
|
185
|
+
- List table
|
|
186
|
+
- Agent activity log
|
|
187
|
+
- Timestamp
|
|
188
|
+
- Segmented control
|
|
189
|
+
- Line chart card
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Timestamp
|
|
2
|
+
|
|
3
|
+
One component for every date and time in the product: relative or absolute display, the exact value on hover and focus, and an optional "by" source.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Content · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Every time a date or time appears: table columns, side panel details, activity, Inbox items, "Last updated" after a refresh.
|
|
10
|
+
- format="relative" for recent past events in lists; format="absolute" for details and deadlines; format="iso" for code and exports.
|
|
11
|
+
- source="Priya Shah" when it matters who made the change, such as Last edited on a listing.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- For date entry. Use a date input.
|
|
16
|
+
- For durations, such as "Time to hire: 18 days"; that is a number with a unit, not a timestamp.
|
|
17
|
+
- For a date with no time that is a property of the record, such as a start date. Show it as an absolute date.
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Optional label, when not given by a column header or key: "Last edited"
|
|
22
|
+
2. Value: relative ("3 hours ago") or absolute ("6 November 2026, 14:32 GMT")
|
|
23
|
+
3. Optional source: "by Priya Shah", linked to the profile, with "via Assistant" when applied from a proposal
|
|
24
|
+
4. Tooltip with the absolute value on hover and focus
|
|
25
|
+
|
|
26
|
+
## Guidelines
|
|
27
|
+
|
|
28
|
+
- Renders a <time> element with the ISO 8601 value in datetime.
|
|
29
|
+
- Relative values tick over once a minute; they never animate or announce.
|
|
30
|
+
- Time zone is the viewer's own and is always shown on absolute values.
|
|
31
|
+
- In compact table cells, `short` shows "3h ago" or "20 Nov"; the tooltip and accessible name keep the full form.
|
|
32
|
+
- Future deadlines use absolute dates, optionally followed by relative time in muted text.
|
|
33
|
+
- Give deadlines, start dates and due dates as a date with no time (2026-12-07). They show as 7 December 2026, with no time or zone, and fall on the same day in every time zone.
|
|
34
|
+
|
|
35
|
+
## Content
|
|
36
|
+
|
|
37
|
+
- Relative: "Just now", "5 minutes ago", "3 hours ago", "2 days ago", "In 3 days".
|
|
38
|
+
- Absolute: day, month in full, year, 24-hour time, zone: "6 November 2026, 14:32 GMT".
|
|
39
|
+
- Source: "Edited 6 hours ago by Priya Shah".
|
|
40
|
+
|
|
41
|
+
## Accessibility
|
|
42
|
+
|
|
43
|
+
- The accessible description carries the absolute value, so screen reader users don't need the tooltip.
|
|
44
|
+
- The tooltip appears on focus of the cell or link that contains the timestamp, without adding a separate tab stop.
|
|
45
|
+
- Never put a ticking relative time in a live region.
|
|
46
|
+
|
|
47
|
+
## Keyboard
|
|
48
|
+
|
|
49
|
+
| Keys | Action |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Tab or arrows (to the containing cell or link) | Show the absolute time tooltip |
|
|
52
|
+
| Esc | Hide the tooltip |
|
|
53
|
+
| Tab (to a source link) | Move to the person's profile link |
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### times
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Timestamp } from "@twoux/ui";
|
|
61
|
+
|
|
62
|
+
export const meta = { title: "Relative, short and absolute", description: "Relative in lists, with the full date and time zone on hover or focus. Short units in compact cells. Deadlines are always absolute.", order: 1 };
|
|
63
|
+
|
|
64
|
+
const ago = (m: number) => new Date(Date.now() - m * 60000);
|
|
65
|
+
|
|
66
|
+
export default function Example() {
|
|
67
|
+
return (
|
|
68
|
+
<dl className="grid grid-cols-[160px_1fr] gap-x-4 gap-y-2 text-base">
|
|
69
|
+
<dt className="text-ink-2">Last edited</dt><dd><Timestamp value={ago(180)} by={<a href="#priya" className="font-medium text-primary-ink hover:underline">Priya Shah</a>} /></dd>
|
|
70
|
+
<dt className="text-ink-2">In a table cell</dt><dd><Timestamp value={ago(60 * 26)} short /></dd>
|
|
71
|
+
<dt className="text-ink-2">Closing date</dt><dd><Timestamp value="2026-12-04T17:00:00Z" format="absolute" /></dd>
|
|
72
|
+
<dt className="text-ink-2">Applied via Assistant</dt><dd><Timestamp value={ago(12)} by={<span>Tom Becker via Assistant</span>} /></dd>
|
|
73
|
+
</dl>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Related
|
|
79
|
+
|
|
80
|
+
- Timestamps
|
|
81
|
+
- List table
|
|
82
|
+
- Side panel (drawer)
|
|
83
|
+
- Tooltip
|
|
84
|
+
- Unsaved changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Timestamps
|
|
2
|
+
|
|
3
|
+
Show when things happened as relative time in lists ("3 hours ago"), the exact date and time on hover and in record details, and always say what the time refers to and who caused it.
|
|
4
|
+
|
|
5
|
+
Kind: pattern · Group: Patterns: general
|
|
6
|
+
|
|
7
|
+
**Problem:** "12/03" means different days to different readers, times without a zone mislead remote teams, and a bare date in a column says nothing about whether it is when the listing was posted, edited or closes.
|
|
8
|
+
|
|
9
|
+
**Solution:** Use the timestamp component everywhere. It renders relative time for past events in lists, absolute time in UK format in details and tooltips, and ISO 8601 in exports and code. Every timestamp has a label of noun and verb, and the person or system that made the change when it matters.
|
|
10
|
+
|
|
11
|
+
**Built from:** Timestamp, List table, Side panel (drawer), Tooltip, Export menu
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
1. Choose the format by the reader's need: how long ago (relative), exactly when (absolute), or a machine (ISO 8601).
|
|
16
|
+
2. Label it with what happened: "Posted", "Last edited", "Applied". In a table the label is the column header.
|
|
17
|
+
3. Add the source when it matters: "Edited 6 hours ago by Priya Shah", linked to their profile.
|
|
18
|
+
4. Give every relative time its absolute value on hover and focus, and in the accessible name.
|
|
19
|
+
|
|
20
|
+
## When to use
|
|
21
|
+
|
|
22
|
+
- Relative: activity feeds, Applied and Last edited columns, the Inbox, "Last updated" after a refresh.
|
|
23
|
+
- Absolute: the Details section of a record, audit history, and deadlines such as a listing's closing date.
|
|
24
|
+
- ISO 8601: CSV exports, API responses and anything people paste into code.
|
|
25
|
+
|
|
26
|
+
## When not to use
|
|
27
|
+
|
|
28
|
+
- Relative time for deadlines. "Closes in 2 weeks" hides the actual day; show "Closes 20 November 2026", with "in 14 days" in muted text if useful.
|
|
29
|
+
- Relative time for billing or usage periods; use the unit being counted, such as hours.
|
|
30
|
+
- Durations such as time to hire. Show a number and unit ("18 days"), not a timestamp.
|
|
31
|
+
|
|
32
|
+
## Guidelines
|
|
33
|
+
|
|
34
|
+
- Relative units: "Just now" under a minute, then minutes, hours, days, weeks, months and years ago, in the largest whole unit.
|
|
35
|
+
- Absolute format in the UK: "6 November 2026, 14:32 GMT" (BST in summer). Teammates in other time zones see their own zone, and the zone is always shown.
|
|
36
|
+
- Spell months and units in full in details and tooltips: "November", "minutes". Omit the year in tables when it is the current year.
|
|
37
|
+
- Relative times refresh about once a minute without announcing each change.
|
|
38
|
+
- When the change came from an applied assistant proposal, the source is the person, with "via Assistant": "Edited 2 minutes ago by Priya Shah via Assistant".
|
|
39
|
+
|
|
40
|
+
## Content
|
|
41
|
+
|
|
42
|
+
- Labels are noun and verb, with no "date" or "time": "Listing posted", not "Posted date".
|
|
43
|
+
- Past tense for things that happened ("Closed"), present or future for things to come ("Closes").
|
|
44
|
+
- Add "Last" only for events that repeat: "Last edited", "Last updated".
|
|
45
|
+
- In tables where rows start and end at different times, "[noun] [verb] time" is allowed: "Interview start time".
|
|
46
|
+
|
|
47
|
+
## Accessibility
|
|
48
|
+
|
|
49
|
+
- Render with <time datetime="2026-11-06T14:32:00Z"> using the ISO value, and the absolute human date in the tooltip and accessible description.
|
|
50
|
+
- Don't rely on the title attribute for the absolute time; it is not available to keyboard or touch users. Use TwoUX's tooltip on focus and hover.
|
|
51
|
+
- Relative times that update do not sit inside a live region.
|
|
52
|
+
|
|
53
|
+
## Do
|
|
54
|
+
|
|
55
|
+
- Do label every timestamp with its event.
|
|
56
|
+
- Do show the time zone on every absolute time.
|
|
57
|
+
- Do use ISO 8601 in exports.
|
|
58
|
+
|
|
59
|
+
## Don't
|
|
60
|
+
|
|
61
|
+
- Don't write numeric dates such as 06/11/2026; they read differently in the UK and US.
|
|
62
|
+
- Don't abbreviate units: "5 minutes ago", not "5m ago", outside dense tables.
|
|
63
|
+
- Don't show a relative deadline without the date.
|
|
64
|
+
|
|
65
|
+
## Related
|
|
66
|
+
|
|
67
|
+
- Empty, loading and error states
|
|
68
|
+
- Record detail: side panel, overlay or full page
|
|
69
|
+
- Data export
|
|
70
|
+
- Timestamp
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
A short, non-blocking confirmation of something the person just did, with Undo when the action can be reversed.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Feedback and overlays · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Confirm an action the person just took in the current view, such as publishing, extending or closing a listing.
|
|
10
|
+
- Offer Undo right after a reversible change, so the change can go ahead without a confirmation dialog.
|
|
11
|
+
- Confirm accelerated actions done by keyboard (T, D, E, ⇧C), where the person has no other visual cue that the key worked.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- Errors and anything the person must act on. Use an Error state in place, or a Banner on the page.
|
|
16
|
+
- Events the person did not cause, such as an agent finishing or a listing about to close. Send them to the Inbox.
|
|
17
|
+
- When the result is already visible on screen and there is nothing else to know, show nothing.
|
|
18
|
+
- Irreversible, high-impact actions. Ask first with a Confirmation dialog.
|
|
19
|
+
|
|
20
|
+
## Structure
|
|
21
|
+
|
|
22
|
+
1. Status icon (check for success)
|
|
23
|
+
2. Title: what happened, past tense
|
|
24
|
+
3. Optional body: which record, one line
|
|
25
|
+
4. Undo button (ghost, small)
|
|
26
|
+
5. Live region container (role="status")
|
|
27
|
+
|
|
28
|
+
## Guidelines
|
|
29
|
+
|
|
30
|
+
- Place the toast at the bottom right, 16px from the edges, at most 340px wide, on a solid white surface. It rises above the focused element and anything marked data-toast-avoid, such as a sticky footer, so it never covers them.
|
|
31
|
+
- Show one toast at a time. A new toast replaces the one on screen rather than stacking.
|
|
32
|
+
- Hide it after 6 seconds. Pause the timer while the pointer is over the toast or focus is inside it.
|
|
33
|
+
- Allow one action only, and only Undo. Never put a Close or Cancel button next to it.
|
|
34
|
+
- Undo must also be reachable without the toast: ⌘Z undoes the last action for as long as the record allows.
|
|
35
|
+
- Never move focus to the toast. The person keeps working where they were.
|
|
36
|
+
|
|
37
|
+
## Content
|
|
38
|
+
|
|
39
|
+
- Title in past tense saying what happened: "Listing published", "Snoozed until Mon 9:00".
|
|
40
|
+
- Name the record in the body when the title alone is ambiguous: "Junior Product Designer is now live."
|
|
41
|
+
- Sentence case, UK spelling, one short sentence, no exclamation marks and no "successfully".
|
|
42
|
+
- Do not apologise or thank. Say the outcome and stop.
|
|
43
|
+
|
|
44
|
+
## Accessibility
|
|
45
|
+
|
|
46
|
+
- Render toasts into a role="status" live region that already exists in the DOM, so screen readers announce them without moving focus (4.1.3).
|
|
47
|
+
- Because Undo disappears with the toast, the same undo is available through ⌘Z and the record's activity, which meets the intent of 2.2.1 Timing Adjustable.
|
|
48
|
+
- The toast must not hide the focused element: TwoUX's Toaster measures the focused element and any data-toast-avoid element in its corner and moves up above them (2.4.11).
|
|
49
|
+
- Do not rely on the green check alone: the title says the outcome in words (1.4.1).
|
|
50
|
+
- Respect prefers-reduced-motion: the toast appears without sliding when motion is reduced (2.3.3).
|
|
51
|
+
|
|
52
|
+
## Keyboard
|
|
53
|
+
|
|
54
|
+
| Keys | Action |
|
|
55
|
+
|---|---|
|
|
56
|
+
| ⌘Z | Undo the last action, whether or not the toast is still visible |
|
|
57
|
+
| Tab | Reaches the Undo button while the toast is shown; the timer pauses while focus is inside |
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
### undo
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { Button, toast } from "@twoux/ui";
|
|
65
|
+
import { useEffect, useState } from "react";
|
|
66
|
+
|
|
67
|
+
export const meta = { title: "Confirm with Undo", description: "Close a listing. The toast says what happened, offers Undo, and leaves in 6 seconds. ⌘Z undoes it too, even after the toast has gone.", order: 1 };
|
|
68
|
+
|
|
69
|
+
export default function Example() {
|
|
70
|
+
const [open, setOpen] = useState(true);
|
|
71
|
+
|
|
72
|
+
const reopen = () => {
|
|
73
|
+
setOpen(true);
|
|
74
|
+
toast.success("Listing reopened", { description: "Senior Product Designer, Leeds" });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const close = () => {
|
|
78
|
+
setOpen(false);
|
|
79
|
+
toast.success("Listing closed", {
|
|
80
|
+
description: "Senior Product Designer, Leeds",
|
|
81
|
+
action: { label: "Undo", onClick: reopen },
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Undo stays reachable without the toast
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const onKey = (e: KeyboardEvent) => {
|
|
88
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "z" && !open) {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
reopen();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
window.addEventListener("keydown", onKey);
|
|
94
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className="flex w-full max-w-md items-center justify-between rounded-sm border border-line bg-surface px-3 py-2.5">
|
|
99
|
+
<div className="grid">
|
|
100
|
+
<span className="text-base font-medium text-ink">Senior Product Designer</span>
|
|
101
|
+
<span className="text-sm text-ink-3">Leeds · {open ? "Open" : "Closed"}</span>
|
|
102
|
+
</div>
|
|
103
|
+
<Button variant="outline" size="sm" onClick={open ? close : reopen}>
|
|
104
|
+
{open ? "Close listing" : "Reopen"}
|
|
105
|
+
</Button>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Related
|
|
112
|
+
|
|
113
|
+
- Banner (inline alert)
|
|
114
|
+
- Confirmation dialog
|
|
115
|
+
- Inbox (triage)
|
|
116
|
+
- Notifications vs inbox vs toast
|
|
117
|
+
- Error state (failed to load)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
Shows a short, non-essential label or hint on hover or focus, such as an icon button's name and its shortcut.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- To name an icon-only button: More, Add to Interviewing.
|
|
10
|
+
- To show the shortcut for a control: Search listings /.
|
|
11
|
+
- To show the full text of a truncated value, or the exact date behind a relative date.
|
|
12
|
+
- To give a short reason why a control is disabled.
|
|
13
|
+
|
|
14
|
+
## When not to use
|
|
15
|
+
|
|
16
|
+
- For anything needed to finish the task, such as format rules. Use hint text under the field.
|
|
17
|
+
- For links, buttons or more than a sentence. Use a popover.
|
|
18
|
+
- On labelled buttons, repeating the label.
|
|
19
|
+
- On status badges or other non-interactive elements.
|
|
20
|
+
|
|
21
|
+
## Structure
|
|
22
|
+
|
|
23
|
+
1. Dark container (6px radius)
|
|
24
|
+
2. Text
|
|
25
|
+
3. Shortcut hint (optional)
|
|
26
|
+
|
|
27
|
+
## Guidelines
|
|
28
|
+
|
|
29
|
+
- Dark ink background, white 12px medium text, 5 x 8px padding, 6px radius.
|
|
30
|
+
- Appears after a short delay (about 500ms) on hover or keyboard focus, so it does not flicker.
|
|
31
|
+
- Stays while the pointer is over the trigger or the tooltip, until Esc or blur.
|
|
32
|
+
- Placed so it never covers the thing it describes, and flips to stay on screen.
|
|
33
|
+
- One line; never truncated.
|
|
34
|
+
|
|
35
|
+
## Content
|
|
36
|
+
|
|
37
|
+
- A few words, no full stop: Search listings.
|
|
38
|
+
- For icon buttons, name the action in one or two words.
|
|
39
|
+
- Use the same words as the accessible name.
|
|
40
|
+
|
|
41
|
+
## Accessibility
|
|
42
|
+
|
|
43
|
+
- role="tooltip", referenced by the trigger's aria-describedby. Focus stays on the trigger.
|
|
44
|
+
- Esc dismisses it without moving focus; the pointer can move onto it; it does not time out.
|
|
45
|
+
- A tooltip does not give an icon button its name; the button also needs aria-label.
|
|
46
|
+
- Only on focusable triggers, so keyboard users see it too.
|
|
47
|
+
- Shortcuts in tooltips are hidden from assistive technology; list them in the shortcut sheet as well.
|
|
48
|
+
|
|
49
|
+
## Keyboard
|
|
50
|
+
|
|
51
|
+
| Keys | Action |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Tab to trigger | Show the tooltip |
|
|
54
|
+
| Esc | Hide the tooltip |
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
### icon-buttons
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { Button, Kbd, Tooltip, TooltipContent, TooltipTrigger } from "@twoux/ui";
|
|
62
|
+
import { Bell, Pencil, X } from "lucide-react";
|
|
63
|
+
|
|
64
|
+
export const meta = { title: "Name an icon button", description: "Hover or Tab to a button. The tooltip repeats the accessible name and its shortcut. It never holds information found nowhere else.", order: 1 };
|
|
65
|
+
|
|
66
|
+
export default function Example() {
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex items-center gap-2">
|
|
69
|
+
<Tooltip>
|
|
70
|
+
<TooltipTrigger asChild><Button variant="ghost" size="icon" aria-label="Edit listing"><Pencil /></Button></TooltipTrigger>
|
|
71
|
+
<TooltipContent>Edit listing <Kbd>E</Kbd></TooltipContent>
|
|
72
|
+
</Tooltip>
|
|
73
|
+
<Tooltip>
|
|
74
|
+
<TooltipTrigger asChild><Button variant="ghost" size="icon" aria-label="Inbox"><Bell /></Button></TooltipTrigger>
|
|
75
|
+
<TooltipContent>Inbox</TooltipContent>
|
|
76
|
+
</Tooltip>
|
|
77
|
+
<Tooltip>
|
|
78
|
+
<TooltipTrigger asChild><Button variant="ghost" size="icon" aria-label="Close details"><X /></Button></TooltipTrigger>
|
|
79
|
+
<TooltipContent side="bottom">Close details <Kbd>Esc</Kbd></TooltipContent>
|
|
80
|
+
</Tooltip>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Related
|
|
87
|
+
|
|
88
|
+
- Button
|
|
89
|
+
- Keyboard shortcut hint
|
|
90
|
+
- Popover rules
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
Geist for interface text, Geist Mono for IDs, keys and code. A seven-step scale from 11px to 24px with 13px as the default, tuned for dense tables. Numbers in columns use tabular figures.
|
|
4
|
+
|
|
5
|
+
Kind: foundation · Group: Visual foundation
|
|
6
|
+
|
|
7
|
+
## Principles
|
|
8
|
+
|
|
9
|
+
- 13px is the working size. Tables, forms and panels set at --t-base so a row of 36px holds one line with air. Body larger than this is for reading, not scanning.
|
|
10
|
+
- Hierarchy by size and weight, not by colour. Headings use the heading scale; never fake a heading with bold body text.
|
|
11
|
+
- Mono for values, not for style. Geist Mono marks IDs, keyboard keys and code: dedicated value types only.
|
|
12
|
+
- Sentence case everywhere, including headings, buttons and menu items.
|
|
13
|
+
|
|
14
|
+
## Guidelines
|
|
15
|
+
|
|
16
|
+
- Scale: --t-xs 11px (keycaps, counters, chart ticks), --t-sm 12px (metadata, table headers, helper text), --t-base 13px (default UI and table cells), --t-md 14px (reading text in panels and AI answers), --t-lg 16px (section titles), --t-xl 20px (panel titles), --t-2xl 24px (page title).
|
|
17
|
+
- No sentence text below 12px. TwoUX allows 11px only for non-sentence content (a keycap, a count, an axis tick). Never set a sentence at --t-xs.
|
|
18
|
+
- Line height between 1.2 and 1.45 of the size: tight for headings, about 1.4 for body.
|
|
19
|
+
- Line length for reading text (AI answers, descriptions) is capped near 80 characters, inside the comfortable range of 45 to 90.
|
|
20
|
+
- Weights: 400 for body, 500 for labels and controls, 600 for headings. Medium weight carries most component text and bold stays rare.
|
|
21
|
+
- Numbers that line up in columns (counts, salaries, dates) use font-variant-numeric: tabular-nums, the .num utility. Tabular figures keep vertically aligned columns readable.
|
|
22
|
+
- Avoid all caps. If a short label must be in caps, letterspace it, and keep caps to short labels.
|
|
23
|
+
- Text is left aligned, ragged right. Centred and justified text is atypical for product UI.
|
|
24
|
+
|
|
25
|
+
## Accessibility
|
|
26
|
+
|
|
27
|
+
- Text colour must reach 4.5:1; --ink-3 at 5.7:1 is the lightest allowed for text.
|
|
28
|
+
- Text must reflow at 320 CSS px wide without horizontal scrolling, except inside data tables.
|
|
29
|
+
- Use real heading elements in order; heading structure matters for accessibility, not just bold or size.
|
|
30
|
+
|
|
31
|
+
## Do
|
|
32
|
+
|
|
33
|
+
- Set listing IDs like JOB-1042 in --font-mono at --t-sm.
|
|
34
|
+
- Use tabular figures in every numeric column.
|
|
35
|
+
- Write headings in sentence case: 'Open listings', not 'Open Listings'.
|
|
36
|
+
|
|
37
|
+
## Don't
|
|
38
|
+
|
|
39
|
+
- Do not set font-size in px, rem or em directly; use a --t-* token.
|
|
40
|
+
- Do not use mono for emphasis or decoration.
|
|
41
|
+
- Do not set paragraphs in all caps.
|
|
42
|
+
|
|
43
|
+
## Tokens
|
|
44
|
+
|
|
45
|
+
| Token | Value | Use |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `--font-ui` | `"Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif` | All interface text |
|
|
48
|
+
| `--font-mono` | `"Geist Mono", ui-monospace, "SF Mono", Menlo, monospace` | IDs, keycaps, code |
|
|
49
|
+
| `--t-xs` | `11px` | Keycaps, counters, chart ticks only |
|
|
50
|
+
| `--t-sm` | `12px` | Metadata, table headers, helper text |
|
|
51
|
+
| `--t-base` | `13px` | Default UI text and table cells |
|
|
52
|
+
| `--t-md` | `14px` | Reading text in panels and AI answers |
|
|
53
|
+
| `--t-lg` | `16px` | Section titles |
|
|
54
|
+
| `--t-xl` | `20px` | Panel and dialog titles |
|
|
55
|
+
| `--t-2xl` | `24px` | Page title (h1) |
|
|
56
|
+
|
|
57
|
+
## Related
|
|
58
|
+
|
|
59
|
+
- Spacing
|
|
60
|
+
- Content density
|
|
61
|
+
- Writing
|
|
62
|
+
- Design tokens
|