@twoux/ui 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +61 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/NOTICE.md +64 -0
- package/README.md +211 -2
- 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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Stepper
|
|
2
|
+
|
|
3
|
+
A vertical list of numbered steps for a task done in order, such as setting up a workspace or publishing a listing, showing what is done, what is current and what is left.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Navigation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- For a task of 3 to 6 steps that must happen in order, where knowing what is left helps people finish.
|
|
10
|
+
- For a setup checklist on an empty workspace, where each step unlocks the next.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- For sections people can visit in any order. Use Tabs.
|
|
15
|
+
- For one long form. Split it with headings; steps add clicks without reducing work.
|
|
16
|
+
- For background progress, such as an upload. Use a progress bar or status.
|
|
17
|
+
|
|
18
|
+
## Structure
|
|
19
|
+
|
|
20
|
+
1. Marker: number, or a check when done
|
|
21
|
+
2. Line joining the markers, primary up to the current step
|
|
22
|
+
3. Title, and an optional one-line description
|
|
23
|
+
4. Current step: primary marker, aria-current="step"
|
|
24
|
+
5. Later steps: faded
|
|
25
|
+
|
|
26
|
+
## Guidelines
|
|
27
|
+
|
|
28
|
+
- Keep it to 3 to 6 steps. More than that, group steps or simplify the task.
|
|
29
|
+
- Let people go back to any done step; never skip ahead to a step that depends on an unfinished one.
|
|
30
|
+
- Keep entered data when people go back and forward.
|
|
31
|
+
- Check each step when people press Next and keep them on it until it passes, with errors at the field. A final Review step checks everything again and links each problem to its step.
|
|
32
|
+
- Mark a done step with a check and the line in primary up to the current step, so progress reads at a glance.
|
|
33
|
+
- Place it beside the step's content on wide screens, and above it on narrow ones.
|
|
34
|
+
|
|
35
|
+
## Content
|
|
36
|
+
|
|
37
|
+
- Step titles are short verb phrases in sentence case: “Add the role”, “Set salary”, “Review and publish”.
|
|
38
|
+
- The description says what the step needs, in a few words: “Title, team and location”.
|
|
39
|
+
|
|
40
|
+
## Accessibility
|
|
41
|
+
|
|
42
|
+
- Use an ordered list, so screen readers announce the count and position.
|
|
43
|
+
- The current step has aria-current="step"; done and current states are also given in text, not by colour alone.
|
|
44
|
+
- Done steps that can be revisited are buttons with a visible focus ring.
|
|
45
|
+
|
|
46
|
+
## Keyboard
|
|
47
|
+
|
|
48
|
+
| Keys | Action |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Tab | Reaches each done step that can be revisited |
|
|
51
|
+
| Enter / Space | Goes back to that step |
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### publish
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Button, Stepper } from "@twoux/ui";
|
|
59
|
+
import { useState } from "react";
|
|
60
|
+
|
|
61
|
+
export const meta = { title: "Publish a listing", description: "Done steps show a check and can be revisited. Later steps stay faded and locked until the one before is finished.", order: 1 };
|
|
62
|
+
|
|
63
|
+
const STEPS = [
|
|
64
|
+
{ title: "Add the role", description: "Title, team and location" },
|
|
65
|
+
{ title: "Set salary", description: "Band and benefits" },
|
|
66
|
+
{ title: "Screening questions", description: "Up to 5 questions" },
|
|
67
|
+
{ title: "Review and publish" },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
export default function Example() {
|
|
71
|
+
const [current, setCurrent] = useState(1);
|
|
72
|
+
return (
|
|
73
|
+
<div className="grid w-full max-w-lg grid-cols-[200px_1fr] gap-8 max-sm:grid-cols-1">
|
|
74
|
+
<Stepper steps={STEPS} current={current} onStepSelect={setCurrent} aria-label="Publish a listing" />
|
|
75
|
+
<div className="grid content-start gap-3">
|
|
76
|
+
<span className="text-md font-semibold text-ink">{STEPS[Math.min(current, STEPS.length - 1)].title}</span>
|
|
77
|
+
<span className="text-base text-ink-2">Step {Math.min(current + 1, STEPS.length)} of {STEPS.length}</span>
|
|
78
|
+
<div className="flex gap-2">
|
|
79
|
+
<Button variant="outline" size="sm" disabled={current === 0} onClick={() => setCurrent((c) => c - 1)}>Back</Button>
|
|
80
|
+
<Button size="sm" disabled={current >= STEPS.length} onClick={() => setCurrent((c) => c + 1)}>{current >= STEPS.length - 1 ? "Publish" : "Continue"}</Button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Related
|
|
89
|
+
|
|
90
|
+
- Tabs (underline)
|
|
91
|
+
- Card
|
|
92
|
+
- Onboarding step
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Suggested changes
|
|
2
|
+
|
|
3
|
+
A proposal card listing each change with a checkbox and before and after, applied with one button and undoable afterwards.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: AI and agents · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Any time the assistant or an agent would change records: extend, rename, close, email.
|
|
10
|
+
- When several changes are proposed and people may want only some of them.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- Read-only answers. Show the figure and table without a proposal (assistant-panel).
|
|
15
|
+
- Actions set to Allow in Agent permissions; those run and appear in the Agent run timeline (agent-run) with Undo.
|
|
16
|
+
- Generated values that fill a new column. Use the AI column (ai-column), which never overwrites real data.
|
|
17
|
+
|
|
18
|
+
## Structure
|
|
19
|
+
|
|
20
|
+
1. Header ("Suggested changes · 2")
|
|
21
|
+
2. Change rows (checkbox, change, detail line, inline diff)
|
|
22
|
+
3. Footer (note "Nothing changes until you apply.", Dismiss, Edit, Apply N changes ⌘↵)
|
|
23
|
+
4. Undo toast after applying
|
|
24
|
+
|
|
25
|
+
## Guidelines
|
|
26
|
+
|
|
27
|
+
- Nothing runs until the person applies. Say so in the footer.
|
|
28
|
+
- Each change has a checkbox, ticked by default, and shows the before and after (strikethrough and inserted text).
|
|
29
|
+
- The Apply button counts what it will change ("Apply 2 changes") and updates as boxes are unticked.
|
|
30
|
+
- Edit opens the changes for adjustment; Dismiss removes the card without changing anything.
|
|
31
|
+
- After applying, show a toast with Undo (⌘Z) that restores the previous values.
|
|
32
|
+
- Match friction to risk: changes that reach people outside the workspace need a stronger confirmation.
|
|
33
|
+
|
|
34
|
+
## Content
|
|
35
|
+
|
|
36
|
+
- Write each change as a plain action: "Extend Operations Coordinator by 30 days", with the consequence on the next line ("New closing date 6 Dec").
|
|
37
|
+
- State what does not change ("Title only. Description stays the same.").
|
|
38
|
+
- After Undo: "Undone. Nothing was changed."
|
|
39
|
+
|
|
40
|
+
## Accessibility
|
|
41
|
+
|
|
42
|
+
- Each change is a native checkbox with its label, so the change and its detail are read together (SC 1.3.1, 4.1.2).
|
|
43
|
+
- Before and after use del and ins elements and text, not colour alone (SC 1.4.1).
|
|
44
|
+
- Applying changes records data, so it must be reversible or confirmed (SC 3.3.4): this card is the review step and Undo makes it reversible.
|
|
45
|
+
- The applied and undone messages are status messages in a role="status" toast (SC 4.1.3).
|
|
46
|
+
|
|
47
|
+
## Keyboard
|
|
48
|
+
|
|
49
|
+
| Keys | Action |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Space | Tick or untick the focused change |
|
|
52
|
+
| ⌘↵ / Ctrl+Enter | Apply the ticked changes |
|
|
53
|
+
| ⌘Z / Ctrl+Z | Undo from the toast |
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### proposal
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { SuggestedChanges, toast } from "@twoux/ui";
|
|
61
|
+
import { useState } from "react";
|
|
62
|
+
|
|
63
|
+
export const meta = { title: "A proposal you apply", description: "Each change ticked by default with before and after. Apply counts what it will change; ⌘↵ applies. Undo follows.", order: 1 };
|
|
64
|
+
|
|
65
|
+
export default function Example() {
|
|
66
|
+
const [k, setK] = useState(0);
|
|
67
|
+
return (
|
|
68
|
+
<div className="w-full max-w-lg">
|
|
69
|
+
<SuggestedChanges
|
|
70
|
+
key={k}
|
|
71
|
+
changes={[
|
|
72
|
+
{ id: "1", label: "Rename Frontend Engineer", detail: "LST-1041", before: "Frontend Engineer", after: "Frontend Engineer (React)" },
|
|
73
|
+
{ id: "2", label: "Add salary range", detail: "LST-1041", after: "£48,000 to £56,000" },
|
|
74
|
+
{ id: "3", label: "Extend closing date by 30 days", detail: "LST-1041", before: "21 Nov", after: "21 Dec" },
|
|
75
|
+
]}
|
|
76
|
+
onApply={(ids) => { toast.success(`Applied ${ids.length} changes`, { action: { label: "Undo", onClick: () => setK((x) => x + 1) } }); setK((x) => x + 1); }}
|
|
77
|
+
onDismiss={() => toast("Dismissed")}
|
|
78
|
+
onEdit={() => toast("Opened for editing")}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Related
|
|
86
|
+
|
|
87
|
+
- Step card
|
|
88
|
+
- Agent permissions
|
|
89
|
+
- Assistant side panel
|
|
90
|
+
- Toast
|
|
91
|
+
- Change review
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Switch
|
|
2
|
+
|
|
3
|
+
Turns one setting on or off with immediate effect, without a Save button.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- For a setting that applies the moment it is flipped: Email me when someone applies.
|
|
10
|
+
- For preferences in popovers and panels: Wrap long text, Single-key shortcuts.
|
|
11
|
+
- When the label describes the on state clearly enough on its own.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- In a form that needs Save or Publish. Use Checkbox.
|
|
16
|
+
- When both states need explaining, such as cost differences. Use Radio.
|
|
17
|
+
- For more than two options. Use Radio or Segmented control.
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Track (28 x 16px)
|
|
22
|
+
2. Thumb
|
|
23
|
+
3. Label
|
|
24
|
+
4. Description (optional)
|
|
25
|
+
|
|
26
|
+
## Guidelines
|
|
27
|
+
|
|
28
|
+
- Track 28 x 16px, thumb 12px. On fills primary; off is grey.
|
|
29
|
+
- The change applies at once. Confirm it with a short toast when the effect is not visible on screen.
|
|
30
|
+
- Label to the left in settings rows, right of the switch in inline lists; keep one pattern per surface.
|
|
31
|
+
- If disabled, say why nearby.
|
|
32
|
+
- Movement of the thumb is the main cue; colour is the second.
|
|
33
|
+
|
|
34
|
+
## Content
|
|
35
|
+
|
|
36
|
+
- Label says what is on when the switch is on. Read it with on/off after it to test.
|
|
37
|
+
- The label never changes with the state.
|
|
38
|
+
- Lead with the key word, no question: Single-key shortcuts, not Do you want single-key shortcuts?
|
|
39
|
+
|
|
40
|
+
## Accessibility
|
|
41
|
+
|
|
42
|
+
- Use role="switch" (or a native checkbox) with aria-checked true/false.
|
|
43
|
+
- Space toggles; Enter may also toggle.
|
|
44
|
+
- Label tied with for/id so the label row is part of the target; the 28 x 16 track alone is under 24px high.
|
|
45
|
+
- Off track and on fill need 3:1 against the surface.
|
|
46
|
+
- Changing a switch must not move focus or open a new view.
|
|
47
|
+
|
|
48
|
+
## Keyboard
|
|
49
|
+
|
|
50
|
+
| Keys | Action |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Space | Toggle the focused switch |
|
|
53
|
+
| Enter | Also toggles (optional) |
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### settings
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { SwitchField } from "@twoux/ui";
|
|
61
|
+
export const meta = {"title": "Settings that apply at once", "description": "A switch changes something immediately, so there is no Save button. The label says what is on.", "order": 1};
|
|
62
|
+
|
|
63
|
+
export default function Example() {
|
|
64
|
+
return (
|
|
65
|
+
<div className="grid w-96 gap-4 rounded-sm border border-line bg-surface p-4 shadow-card">
|
|
66
|
+
<SwitchField id="sw-email" label="Email me when someone applies" hint="One email per applicant." defaultChecked />
|
|
67
|
+
<SwitchField id="sw-keys" label="Single-key shortcuts" hint="Turn off if you use a screen reader or voice control." defaultChecked />
|
|
68
|
+
<SwitchField id="sw-digest" label="Weekly summary" disabled />
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Related
|
|
75
|
+
|
|
76
|
+
- Checkbox
|
|
77
|
+
- Segmented control
|
|
78
|
+
- Keyboard shortcut hint
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Table settings
|
|
2
|
+
|
|
3
|
+
Settings for one table in the side panel: how duplicates are handled with the consequence written out, deleting empty rows, and sharing as a template with who can use it. Saved with Save; closing with changes asks first.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- For rules that apply to a whole table rather than one column.
|
|
10
|
+
|
|
11
|
+
## When not to use
|
|
12
|
+
|
|
13
|
+
- For column set-up. Use the column menu.
|
|
14
|
+
|
|
15
|
+
## Structure
|
|
16
|
+
|
|
17
|
+
1. Duplicate rows: match on, Keep the oldest, Keep the newest, Keep both, each with what happens
|
|
18
|
+
2. Empty rows: Delete empty rows automatically
|
|
19
|
+
3. Share as a template: checkbox, who can use it, link with Copy
|
|
20
|
+
4. Footer: Cancel, Save
|
|
21
|
+
|
|
22
|
+
## Guidelines
|
|
23
|
+
|
|
24
|
+
- Write out the consequence of each duplicate rule, including overwritten values and runs that cost credits again.
|
|
25
|
+
- Use checkboxes and radios in this form, saved with Save; a switch is only for changes that apply at once.
|
|
26
|
+
- Ask before discarding only when something changed, and warn before leaving the page while dirty.
|
|
27
|
+
- Say what a template shares and what it never shares.
|
|
28
|
+
|
|
29
|
+
## Content
|
|
30
|
+
|
|
31
|
+
- "Rows and looked-up values are never shared."
|
|
32
|
+
|
|
33
|
+
## Accessibility
|
|
34
|
+
|
|
35
|
+
- Radio groups are labelled by their heading; hints are linked to each option.
|
|
36
|
+
- The discard dialog puts focus on Keep editing.
|
|
37
|
+
|
|
38
|
+
## Keyboard
|
|
39
|
+
|
|
40
|
+
| Keys | Action |
|
|
41
|
+
|---|---|
|
|
42
|
+
| ⌘ Enter | Save |
|
|
43
|
+
| Esc | Close; asks first when there are changes |
|
|
44
|
+
|
|
45
|
+
## Examples
|
|
46
|
+
|
|
47
|
+
### settings
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Button, TableSettings, type TableSettingsValue } from "@twoux/ui";
|
|
51
|
+
import { Settings } from "lucide-react";
|
|
52
|
+
import { useState } from "react";
|
|
53
|
+
|
|
54
|
+
export const meta = { title: "Settings for the People table", description: "Change a setting and close the panel: it asks before discarding. Close without changes and it doesn't ask. Each duplicate rule says what happens to existing rows and credits.", order: 1 };
|
|
55
|
+
|
|
56
|
+
export default function Example() {
|
|
57
|
+
const [open, setOpen] = useState(false);
|
|
58
|
+
const [value, setValue] = useState<TableSettingsValue>({ duplicates: "keep-oldest", matchOn: "email", deleteEmptyRows: false, template: { shared: false, audience: "workspace" } });
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<Button variant="outline" onClick={() => setOpen(true)}><Settings />Table settings</Button>
|
|
62
|
+
<TableSettings
|
|
63
|
+
open={open}
|
|
64
|
+
onOpenChange={setOpen}
|
|
65
|
+
mode="overlay"
|
|
66
|
+
value={value}
|
|
67
|
+
onSave={async (v) => { await new Promise((r) => setTimeout(r, 400)); setValue(v); }}
|
|
68
|
+
columns={[{ id: "email", label: "Work email" }, { id: "name", label: "Name" }]}
|
|
69
|
+
tableName="People"
|
|
70
|
+
workspaceName="TwoUX"
|
|
71
|
+
templateLink="https://app.example.com/t/people"
|
|
72
|
+
/>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Related
|
|
79
|
+
|
|
80
|
+
- Side panel (drawer)
|
|
81
|
+
- Sheet grid
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Tabs (underline)
|
|
2
|
+
|
|
3
|
+
Splits one record or page into sections such as Overview, Candidates and Activity, one visible at a time.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- To split a record into separate sections people do not need side by side: Overview, Candidates, Activity.
|
|
10
|
+
- When each section is substantial and can have its own URL.
|
|
11
|
+
- With a count when the number helps people decide to open the tab: Candidates 42.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- To switch between views of the same data, such as All / Active. Use Segmented control.
|
|
16
|
+
- For steps in a flow. Use a stepper or separate pages.
|
|
17
|
+
- When people must compare content across sections. Show it on one page.
|
|
18
|
+
- For app navigation. Use the sidebar.
|
|
19
|
+
|
|
20
|
+
## Structure
|
|
21
|
+
|
|
22
|
+
1. Tab list with bottom rule
|
|
23
|
+
2. Tab label
|
|
24
|
+
3. Count (optional)
|
|
25
|
+
4. Active underline (2px primary)
|
|
26
|
+
5. Tab panel
|
|
27
|
+
|
|
28
|
+
## Guidelines
|
|
29
|
+
|
|
30
|
+
- Single row, 20px gap, 13px medium labels. Active tab gets primary text and a 2px primary underline: two cues, not one.
|
|
31
|
+
- Two to seven tabs. Order by use, most used first; the first tab is the default.
|
|
32
|
+
- No nested tabs and no disabled tabs; hide a tab that does not apply.
|
|
33
|
+
- If tabs overflow on narrow screens, the list scrolls sideways; it never wraps to a second row.
|
|
34
|
+
- Put shared headings and actions above the tab list, not inside one panel.
|
|
35
|
+
|
|
36
|
+
## Content
|
|
37
|
+
|
|
38
|
+
- One or two words, nouns, sentence case: Activity, not View all activity.
|
|
39
|
+
- Labels must not overlap in meaning.
|
|
40
|
+
- Counts need screen reader text: 42 candidates, not just 42.
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- role="tablist" with a label, role="tab" with aria-selected and aria-controls, role="tabpanel" labelled by its tab.
|
|
45
|
+
- Tab enters the list on the active tab; Left and Right arrows move between tabs; Home and End jump.
|
|
46
|
+
- Panels load instantly, so tabs activate on focus (automatic activation).
|
|
47
|
+
- The underline indicator has 3:1 against white.
|
|
48
|
+
- Tab hit area is the full label height (about 36px), above the 24px minimum.
|
|
49
|
+
|
|
50
|
+
## Keyboard
|
|
51
|
+
|
|
52
|
+
| Keys | Action |
|
|
53
|
+
|---|---|
|
|
54
|
+
| Left / Right | Previous or next tab |
|
|
55
|
+
| Home / End | First or last tab |
|
|
56
|
+
| Tab | Move from the tab list into the panel |
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
### underline
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@twoux/ui";
|
|
64
|
+
export const meta = {"title": "Sections of one record", "description": "Underline tabs split one page into sections. Counts help people decide where to go. Arrow keys switch tabs.", "order": 1};
|
|
65
|
+
|
|
66
|
+
export default function Example() {
|
|
67
|
+
return (
|
|
68
|
+
<Tabs defaultValue="overview" className="w-full max-w-lg">
|
|
69
|
+
<TabsList>
|
|
70
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
71
|
+
<TabsTrigger value="candidates" count={54}>Candidates</TabsTrigger>
|
|
72
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
73
|
+
</TabsList>
|
|
74
|
+
<TabsContent value="overview" className="text-ink-2">Views, applies and the funnel for this listing.</TabsContent>
|
|
75
|
+
<TabsContent value="candidates" className="text-ink-2">54 people applied. 16 are shortlisted.</TabsContent>
|
|
76
|
+
<TabsContent value="activity" className="text-ink-2">Edited by Rahul S. 2 days ago.</TabsContent>
|
|
77
|
+
</Tabs>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Related
|
|
83
|
+
|
|
84
|
+
- Segmented control
|
|
85
|
+
- Status badge
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Tag input
|
|
2
|
+
|
|
3
|
+
Enter several short values that don't come from a list, typed or pasted, and see at once which are wrong: email addresses to invite, allowed domains, keywords.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- For free values with no list to pick from: invite emails, allowed domains, search keywords.
|
|
10
|
+
- When people paste a list from a spreadsheet, an email client or another tool.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- When the values come from a list, such as skills or locations. Use Combobox (combobox) with multiple.
|
|
15
|
+
- For one value. Use a Text input (text-input).
|
|
16
|
+
- For labels on a record that people only read. Use Tag (tag).
|
|
17
|
+
- For many rows of values with several fields each. Use a Sheet grid (sheet-grid).
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Label
|
|
22
|
+
2. Field holding the tags, then the text input
|
|
23
|
+
3. Tag: text and remove button; an invalid tag adds an alert icon and its reason
|
|
24
|
+
4. Hint, or what just happened (added, already added, not added)
|
|
25
|
+
5. Count when there is a limit: 3 of 10
|
|
26
|
+
6. Error under the field
|
|
27
|
+
|
|
28
|
+
## Guidelines
|
|
29
|
+
|
|
30
|
+
- Enter, comma or leaving the field adds the typed text. Tab adds it only when there is text, so the field never traps focus.
|
|
31
|
+
- Pasted text is split on commas, semicolons, new lines and tabs, so a column from Excel or a list from Outlook works.
|
|
32
|
+
- A duplicate (ignoring case) isn't added again: the existing tag is highlighted and the hint line says it is already added.
|
|
33
|
+
- An invalid value stays as a marked tag with its reason; the error under the field counts them. Nothing is dropped without saying so.
|
|
34
|
+
- Values past the limit stay in the input, and the hint line says how many weren't added.
|
|
35
|
+
- Fix a value by pressing Enter on its tag, or double-clicking it: the text goes back into the input.
|
|
36
|
+
|
|
37
|
+
## Content
|
|
38
|
+
|
|
39
|
+
- The hint names the separators and the thing: Separate addresses with commas or new lines.
|
|
40
|
+
- Messages say what happened and what to do: "anna@acme.co.uk is already added.", "Not an email address.", "2 addresses aren't valid. Fix or remove them."
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- The tags are a list named by the field label. Each tag's name includes its reason when it isn't valid.
|
|
45
|
+
- Adding, removing and paste summaries ("12 added, 2 not valid") are announced politely.
|
|
46
|
+
- The field is one tab stop; arrow keys move between tags, so long lists don't flood the tab order.
|
|
47
|
+
- Error is shown with an icon and words as well as colour.
|
|
48
|
+
|
|
49
|
+
## Keyboard
|
|
50
|
+
|
|
51
|
+
| Keys | Action |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Enter or comma | Add the typed text |
|
|
54
|
+
| ← or Backspace at the start of the input | Move to the last tag |
|
|
55
|
+
| ← → | Move between tags; → from the last tag returns to the input |
|
|
56
|
+
| Backspace / Delete (on a tag) | Remove it; focus moves to the tag before or after |
|
|
57
|
+
| Enter (on a tag) | Put its text back in the input to fix it |
|
|
58
|
+
| Esc (on a tag) | Return to the input |
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### invite
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { TagInput } from "@twoux/ui";
|
|
66
|
+
import { useState } from "react";
|
|
67
|
+
|
|
68
|
+
export const meta = { title: "Invite by email", description: "Type or paste addresses. Duplicates are reported, invalid ones stay marked with the reason. Try pasting: ben@acme.co.uk; anna@acme.co.uk; chris@acme", order: 1 };
|
|
69
|
+
|
|
70
|
+
const isEmail = (v: string) => (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v) ? undefined : "Not an email address.");
|
|
71
|
+
|
|
72
|
+
export default function Example() {
|
|
73
|
+
const [value, setValue] = useState<string[]>(["anna@acme.co.uk"]);
|
|
74
|
+
return (
|
|
75
|
+
<div className="w-96 max-w-full">
|
|
76
|
+
<TagInput label="Email addresses" noun={{ one: "address", many: "addresses" }} value={value} onValueChange={setValue} validateValue={isEmail} max={10} name="invite" required />
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### states
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { TagInput } from "@twoux/ui";
|
|
86
|
+
|
|
87
|
+
export const meta = { title: "States", description: "Invalid values are counted under the field. Read-only tags stay focusable and readable.", order: 2 };
|
|
88
|
+
|
|
89
|
+
const noop = () => {};
|
|
90
|
+
const domain = (v: string) => (/^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(v) ? undefined : "Not a domain, such as acme.co.uk.");
|
|
91
|
+
|
|
92
|
+
export default function Example() {
|
|
93
|
+
return (
|
|
94
|
+
<div className="grid w-96 max-w-full gap-4">
|
|
95
|
+
<TagInput label="Allowed domains" noun={{ one: "domain", many: "domains" }} value={["acme.co.uk", "acme com", "acme.com"]} onValueChange={noop} validateValue={domain} />
|
|
96
|
+
<TagInput label="Keywords" noun={{ one: "keyword", many: "keywords" }} value={["Data engineer", "Analytics", "Remote"]} onValueChange={noop} readOnly />
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Related
|
|
103
|
+
|
|
104
|
+
- Tag
|
|
105
|
+
- Combobox
|
|
106
|
+
- Text input
|
|
107
|
+
- Sheet grid
|
|
108
|
+
- Form validation
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Tag
|
|
2
|
+
|
|
3
|
+
Labels a record with a category people chose, such as Design, Hybrid or Graduate scheme.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- For descriptive categories that classify or group records: team, contract type, work mode.
|
|
10
|
+
- For user-defined values that people add and remove.
|
|
11
|
+
- When one record belongs to several categories.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- For system state such as Live or Closed. Use Status badge.
|
|
16
|
+
- As a button. Use Button or a selectable control.
|
|
17
|
+
|
|
18
|
+
## Structure
|
|
19
|
+
|
|
20
|
+
1. Container (22px, 4px corners)
|
|
21
|
+
2. Colour dot (optional)
|
|
22
|
+
3. Text
|
|
23
|
+
4. Remove button (optional)
|
|
24
|
+
|
|
25
|
+
## Guidelines
|
|
26
|
+
|
|
27
|
+
- 22px high, 4px corners, white with a 1px grey border, 12px text, so it reads quieter than a status badge. Not fully round: in TwoUX a pill means a filter you can remove.
|
|
28
|
+
- Add a colour dot only when the colour means something, such as the team.
|
|
29
|
+
- Removable tags put a small x after the text.
|
|
30
|
+
- Show up to three in a table cell; summarise the rest as +N so the row stays scannable.
|
|
31
|
+
- Never wrap a tag over two lines. Truncate with a tooltip showing the full text.
|
|
32
|
+
|
|
33
|
+
## Content
|
|
34
|
+
|
|
35
|
+
- One or two words, ideally under 20 characters.
|
|
36
|
+
- Sentence case unless a proper noun: Graduate scheme, not Graduate Scheme.
|
|
37
|
+
- Use the same wording everywhere so filters match.
|
|
38
|
+
|
|
39
|
+
## Accessibility
|
|
40
|
+
|
|
41
|
+
- Text contrast 4.5:1 on white; the dot is decorative and hidden from assistive technology.
|
|
42
|
+
- The remove button is a real button named Remove Design.
|
|
43
|
+
- Remove buttons need a 24px target or enough spacing from neighbours.
|
|
44
|
+
- Colour never carries the only meaning; the text names the category.
|
|
45
|
+
|
|
46
|
+
## Keyboard
|
|
47
|
+
|
|
48
|
+
| Keys | Action |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Tab | Move to a removable tag's remove button |
|
|
51
|
+
| Enter / Space | Remove the tag |
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### tags
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Tag, TagList } from "@twoux/ui";
|
|
59
|
+
import { useState } from "react";
|
|
60
|
+
|
|
61
|
+
export const meta = { title: "Categories on a listing", description: "Tags are quieter than status badges and have 4px corners (pills mean filters). A dot only when the colour means something. In a table cell, three at most, then +N.", order: 1 };
|
|
62
|
+
|
|
63
|
+
export default function Example() {
|
|
64
|
+
const [tags, setTags] = useState(["Design", "Hybrid", "Graduate scheme"]);
|
|
65
|
+
return (
|
|
66
|
+
<div className="grid gap-4">
|
|
67
|
+
<div className="flex flex-wrap gap-1.5">{tags.map((t, i) => <Tag key={t} color={i === 0 ? "var(--color-c1)" : undefined} onRemove={() => setTags((x) => x.filter((y) => y !== t))}>{t}</Tag>)}</div>
|
|
68
|
+
<div className="flex items-center gap-3 text-base text-ink-2">In a table cell: <TagList tags={["Engineering", "Remote", "Senior", "Visa sponsorship", "Urgent"]} /></div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Related
|
|
75
|
+
|
|
76
|
+
- Status badge
|
|
77
|
+
- Avatar and avatar stack
|
|
78
|
+
- Tag input
|
|
79
|
+
- Combobox
|