division-ds 0.1.2 → 0.2.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/AGENTS.md +23 -3
- package/README.md +11 -0
- package/division-ds-0.2.1.tgz +0 -0
- package/package.json +2 -2
- package/public/docs/components/accordion.md +207 -0
- package/public/docs/components/ai-label.md +90 -0
- package/public/docs/components/alert-dialog.md +292 -0
- package/public/docs/components/alert.md +175 -0
- package/public/docs/components/approval-request.md +148 -0
- package/public/docs/components/auth-form.md +132 -0
- package/public/docs/components/avatar-group.md +152 -0
- package/public/docs/components/avatar.md +179 -0
- package/public/docs/components/badge.md +178 -0
- package/public/docs/components/breadcrumbs.md +112 -0
- package/public/docs/components/button.md +204 -0
- package/public/docs/components/card.md +257 -0
- package/public/docs/components/carousel.md +130 -0
- package/public/docs/components/cart.md +172 -0
- package/public/docs/components/chart-panel.md +358 -0
- package/public/docs/components/chat-message.md +157 -0
- package/public/docs/components/chat-thread.md +244 -0
- package/public/docs/components/checkbox.md +149 -0
- package/public/docs/components/checkout-form.md +128 -0
- package/public/docs/components/command-palette.md +147 -0
- package/public/docs/components/data-table.md +401 -0
- package/public/docs/components/description-list.md +103 -0
- package/public/docs/components/dialog.md +197 -0
- package/public/docs/components/divider.md +97 -0
- package/public/docs/components/dropdown-menu.md +222 -0
- package/public/docs/components/empty-state.md +145 -0
- package/public/docs/components/footer.md +397 -0
- package/public/docs/components/form-field.md +216 -0
- package/public/docs/components/icon-button.md +127 -0
- package/public/docs/components/input.md +200 -0
- package/public/docs/components/kanban-board.md +300 -0
- package/public/docs/components/kbd.md +72 -0
- package/public/docs/components/link.md +98 -0
- package/public/docs/components/list-item.md +241 -0
- package/public/docs/components/loading-animation.md +72 -0
- package/public/docs/components/multi-select.md +202 -0
- package/public/docs/components/navbar.md +144 -0
- package/public/docs/components/newsletter-form.md +112 -0
- package/public/docs/components/page-header.md +151 -0
- package/public/docs/components/pagination.md +212 -0
- package/public/docs/components/product-card.md +172 -0
- package/public/docs/components/progress.md +101 -0
- package/public/docs/components/prompt-input.md +151 -0
- package/public/docs/components/quantity-stepper.md +140 -0
- package/public/docs/components/radio.md +177 -0
- package/public/docs/components/rating.md +106 -0
- package/public/docs/components/score-ring.md +95 -0
- package/public/docs/components/search.md +116 -0
- package/public/docs/components/section-heading.md +113 -0
- package/public/docs/components/segmented-control.md +126 -0
- package/public/docs/components/select.md +224 -0
- package/public/docs/components/sheet.md +145 -0
- package/public/docs/components/sidebar.md +193 -0
- package/public/docs/components/skeleton.md +135 -0
- package/public/docs/components/slider.md +114 -0
- package/public/docs/components/sparkline.md +128 -0
- package/public/docs/components/spinner.md +98 -0
- package/public/docs/components/stat.md +106 -0
- package/public/docs/components/status-tracker.md +145 -0
- package/public/docs/components/stepper.md +147 -0
- package/public/docs/components/switch.md +153 -0
- package/public/docs/components/tabs.md +180 -0
- package/public/docs/components/tag.md +105 -0
- package/public/docs/components/textarea.md +148 -0
- package/public/docs/components/timeline.md +152 -0
- package/public/docs/components/toast.md +194 -0
- package/public/docs/components/tool-call.md +107 -0
- package/public/docs/components/tooltip.md +108 -0
- package/public/manifest/components.json +198 -15
- package/public/manifest/tokens.json +75 -5
- package/scripts/division-ds.mjs +78 -42
- package/scripts/lib/agent-docs.mjs +80 -6
- package/scripts/lib/ai-copy.mjs +120 -0
- package/scripts/lib/button-spacing.mjs +5 -5
- package/scripts/lib/check-text.mjs +57 -0
- package/scripts/lib/contrast.mjs +111 -0
- package/scripts/lib/host-inline.mjs +59 -0
- package/scripts/lib/mcp-server.mjs +116 -0
- package/scripts/lib/mcp-tools.mjs +266 -0
- package/scripts/lib/props-validator.mjs +3 -2
- package/scripts/lib/rules-installer.mjs +42 -4
- package/scripts/lib/token-discipline.mjs +10 -3
- package/skills/division-ds/SKILL.md +90 -0
- package/skills/division-ds/commands/add.md +25 -0
- package/skills/division-ds/commands/build.md +46 -0
- package/skills/division-ds/commands/fix.md +28 -0
- package/skills/division-ds/commands/review.md +25 -0
- package/skills/division-ds/topics/ai.md +8 -0
- package/skills/division-ds/topics/colour.md +11 -0
- package/skills/division-ds/topics/feedback.md +9 -0
- package/skills/division-ds/topics/forms.md +22 -0
- package/skills/division-ds/topics/layout.md +36 -0
- package/skills/division-ds/topics/motion.md +10 -0
- package/skills/division-ds/topics/overlays.md +9 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
> A short message that pops up, stays a few seconds and goes away. Hostile toasts (errors) interrupt and stay longer. Everything else waits its turn.
|
|
4
|
+
|
|
5
|
+
- **Best for:** Confirming something the person just did, for a few seconds.
|
|
6
|
+
- **Not for:** The only copy of important information: use Alert. Also not for a change the page already shows.
|
|
7
|
+
- Molecule. Source: `src/components/molecules/toast/Toast.tsx`
|
|
8
|
+
- Rendered docs page: `/components/toast`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Toaster, useToast } from 'src/components/molecules/toast/Toast'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
|
|
17
|
+
|
|
18
|
+
## Requires
|
|
19
|
+
|
|
20
|
+
- One <Toaster> near the root of the app, wrapped around everything that calls useToast(). AppShell and the demo layout each mount one. Outside it, useToast() throws an error.
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `title (ToastOptions)` | `string` | | Required. Screen readers hear the tone word before it. |
|
|
27
|
+
| `description (ToastOptions)` | `string` | | Optional body text. |
|
|
28
|
+
| `tone (ToastOptions)` | `'info' \| 'ok' \| 'warning' \| 'hostile'` | `'info'` | Which colour, icon and word the toast has. |
|
|
29
|
+
| `duration (ToastOptions)` | `number` | `5000` | How many milliseconds before it goes away on its own. 8000 for hostile. |
|
|
30
|
+
| `action (ToastOptions)` | `{ label: string; altText: string; onAction: () => void }` | | Optional. Radix (the library the toast is built on) requires altText: what to do somewhere else if someone can't reach the toast in time. |
|
|
31
|
+
| `replace (ToastOptions)` | `string` | | Optional. Closes any open toast raised with the same string as this one opens, so an action repeated quickly ("Alert acknowledged" three times) shows one toast instead of a stack. |
|
|
32
|
+
| `useToast()` | `{ toast: (options: ToastOptions) => string; dismiss: (id: string) => void }` | | Shows a toast (and returns its id) or closes one. Throws an error outside a Toaster. |
|
|
33
|
+
|
|
34
|
+
## Types and helpers
|
|
35
|
+
|
|
36
|
+
Exported beside the component, from the same file. Import a type with `import type { ... }`.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
export type ToastOptions = {
|
|
40
|
+
title: string
|
|
41
|
+
description?: string
|
|
42
|
+
tone?: Tone
|
|
43
|
+
/** ms before it leaves on its own. Default 5000; 8000 for hostile. */
|
|
44
|
+
duration?: number
|
|
45
|
+
/** Radix requires altText: the way to do the same thing for someone who cannot reach the toast
|
|
46
|
+
* before it leaves. */
|
|
47
|
+
action?: { label: string; altText: string; onAction: () => void }
|
|
48
|
+
/** Any open toast raised with the same string closes as this one opens, so a repeated action
|
|
49
|
+
* ("Alert acknowledged" three times) shows one toast, not a stack over the page. */
|
|
50
|
+
replace?: string
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
export function useToast(): ToastApi
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
61
|
+
|
|
62
|
+
### Tones
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
function TonesExample() {
|
|
66
|
+
const { toast } = useToast()
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="flex flex-wrap gap-3">
|
|
70
|
+
<Button variant="secondary" size="sm" onClick={() => toast({ title: 'Depot hours changed', tone: 'info' })}>
|
|
71
|
+
Info
|
|
72
|
+
</Button>
|
|
73
|
+
<Button variant="secondary" size="sm" onClick={() => toast({ title: 'Manifest saved', tone: 'ok' })}>
|
|
74
|
+
Ok
|
|
75
|
+
</Button>
|
|
76
|
+
<Button variant="secondary" size="sm" onClick={() => toast({ title: 'Depot closes early', tone: 'warning' })}>
|
|
77
|
+
Warning
|
|
78
|
+
</Button>
|
|
79
|
+
<Button variant="secondary" size="sm" onClick={() => toast({ title: 'Sync failed', tone: 'hostile' })}>
|
|
80
|
+
Hostile
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
<TonesExample />
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### With a description
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
function WithDescriptionExample() {
|
|
93
|
+
const { toast } = useToast()
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Button
|
|
97
|
+
variant="secondary"
|
|
98
|
+
size="sm"
|
|
99
|
+
onClick={() => toast({ title: 'Manifest saved', description: 'Twelve items, filed to Brasov north.', tone: 'ok' })}
|
|
100
|
+
>
|
|
101
|
+
Save manifest
|
|
102
|
+
</Button>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
<WithDescriptionExample />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### With an action
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
function WithActionExample() {
|
|
113
|
+
const { toast } = useToast()
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Button
|
|
117
|
+
variant="secondary"
|
|
118
|
+
size="sm"
|
|
119
|
+
onClick={() =>
|
|
120
|
+
toast({
|
|
121
|
+
title: 'Item removed',
|
|
122
|
+
action: {
|
|
123
|
+
label: 'Undo',
|
|
124
|
+
altText: 'Undo from the manifest history',
|
|
125
|
+
onAction: () => toast({ title: 'Item restored', tone: 'ok' }),
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
>
|
|
130
|
+
Remove item
|
|
131
|
+
</Button>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
<WithActionExample />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Keyboard
|
|
139
|
+
|
|
140
|
+
| Keys | Result |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `F8` | Moves focus into the notification region. |
|
|
143
|
+
| `Tab` | Moves through the toast's action and close button. A toast with focus pauses its timer. |
|
|
144
|
+
| `Escape` | Closes the toast that has focus. |
|
|
145
|
+
|
|
146
|
+
## Do
|
|
147
|
+
|
|
148
|
+
- Use a toast to confirm something the person just did.
|
|
149
|
+
- Give every action an altText that says where else it can be done.
|
|
150
|
+
- Keep the title under about six words.
|
|
151
|
+
- After an action runs, focus goes back to where it was before it entered the toast. If the action changes the page (Undo puts a row back), move focus to what changed yourself.
|
|
152
|
+
- A toast follows the innermost open overlay (a dialog or panel on top of the page). If it's raised while one is open, or is already showing when one opens, it shows inside that overlay, and it moves back to the page corner when the overlay closes. AlertDialog is the exception: its toasts always show in the page corner, above the scrim (the dimmed layer behind the dialog).
|
|
153
|
+
|
|
154
|
+
## Don't
|
|
155
|
+
|
|
156
|
+
- Don't put the only copy of important information in a toast. It goes away.
|
|
157
|
+
- Don't show a toast for a change the page already shows.
|
|
158
|
+
- Don't line up more than three at once.
|
|
159
|
+
|
|
160
|
+
## Common mistakes
|
|
161
|
+
|
|
162
|
+
Wrong:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
function SettingsForm() {
|
|
166
|
+
return (
|
|
167
|
+
<Toaster>
|
|
168
|
+
<Form />
|
|
169
|
+
</Toaster>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Right:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
// once, at the root
|
|
178
|
+
<Toaster>
|
|
179
|
+
<App />
|
|
180
|
+
</Toaster>
|
|
181
|
+
|
|
182
|
+
// anywhere below it
|
|
183
|
+
const { toast } = useToast()
|
|
184
|
+
toast({ title: 'Settings saved', tone: 'ok' })
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A screen that mounts its own Toaster gets a second viewport (the corner where toasts appear) and a second live region (the area screen readers listen to for announcements). Rely on the one at the root and call useToast().
|
|
188
|
+
|
|
189
|
+
## Anatomy
|
|
190
|
+
|
|
191
|
+
- **Toaster**: The provider and the viewport, mounted once in the app shell.
|
|
192
|
+
- **Viewport**: The area where toasts appear: bottom right, 360px (pixels) wide, full width on a phone. Pressing F8 moves focus into it.
|
|
193
|
+
- **Toast**: Built like Alert, on the sunken surface: icon, tone word, title, description, action and a close button. Hovering over it pauses its timer too.
|
|
194
|
+
- **Action**: Optional. Needs altText, which says how to do the same thing somewhere else.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# ToolCall
|
|
2
|
+
|
|
3
|
+
> A tool the assistant ran, shown right where it ran. It has the status as a word and a small symbol, a title, the tool name and how long it took, with the input and output folded away behind a button.
|
|
4
|
+
|
|
5
|
+
- **Best for:** An assistant reply that ran a tool, such as a query, a lookup or a calculation.
|
|
6
|
+
- **Not for:** An action with side effects that needs a yes first: use ApprovalRequest.
|
|
7
|
+
- Molecule. Source: `src/components/molecules/tool-call/ToolCall.tsx`
|
|
8
|
+
- Rendered docs page: `/components/tool-call`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { ToolCall } from 'src/components/molecules/tool-call/ToolCall'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
|
|
17
|
+
|
|
18
|
+
## Props
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Default | Description |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `name` | `string` | | Required. The tool's own name, shown in a monospaced font. |
|
|
23
|
+
| `title` | `string` | | A plain-language description of the call, shown above the name. |
|
|
24
|
+
| `status` | `'running' \| 'done' \| 'failed'` | | Required. Shown as a word, a symbol and a colour. |
|
|
25
|
+
| `duration` | `string` | | How long it took, for example "1.2 s". |
|
|
26
|
+
| `input` | `ReactNode` | | What the assistant sent the tool. Folded away by default. |
|
|
27
|
+
| `output` | `ReactNode` | | What came back. Folded away by default. |
|
|
28
|
+
| `defaultOpen` | `boolean` | `false` | Starts with the details showing. |
|
|
29
|
+
| `className` | `string` | | Added to the panel. |
|
|
30
|
+
|
|
31
|
+
## Types and helpers
|
|
32
|
+
|
|
33
|
+
Exported beside the component, from the same file. Import a type with `import type { ... }`.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
export type ToolCallStatus = 'running' | 'done' | 'failed'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
42
|
+
|
|
43
|
+
### Running, done and failed
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<div className="flex w-full max-w-xl flex-col gap-3">
|
|
47
|
+
<ToolCall name="query_stock_levels" title="Read stock levels at Depot 4" status="running" />
|
|
48
|
+
<ToolCall
|
|
49
|
+
name="query_stock_levels"
|
|
50
|
+
title="Read stock levels at Depot 4"
|
|
51
|
+
status="done"
|
|
52
|
+
duration="1.2 s"
|
|
53
|
+
input={'{ "depot": 4, "below_reorder_point": true }'}
|
|
54
|
+
output={'[\n { "sku": "K-41", "on_hand": 6, "reorder_at": 10 },\n { "sku": "TP-08", "on_hand": 3, "reorder_at": 8 }\n]'}
|
|
55
|
+
/>
|
|
56
|
+
<ToolCall
|
|
57
|
+
name="fetch_supplier_lead_times"
|
|
58
|
+
title="Check supplier lead times"
|
|
59
|
+
status="failed"
|
|
60
|
+
duration="4.0 s"
|
|
61
|
+
output="Timed out after 4 s. The supplier feed did not answer."
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Without a title, details open
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<div className="flex w-full max-w-xl flex-col gap-3">
|
|
70
|
+
<ToolCall
|
|
71
|
+
name="count_pallets"
|
|
72
|
+
status="done"
|
|
73
|
+
duration="0.3 s"
|
|
74
|
+
input={'{ "depot": 4, "bay": 2 }'}
|
|
75
|
+
output="14 pallets"
|
|
76
|
+
defaultOpen
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Keyboard
|
|
82
|
+
|
|
83
|
+
| Keys | Result |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `Tab` | Moves to "Show details". |
|
|
86
|
+
| `Enter` `Space` | Shows or hides the input and output. |
|
|
87
|
+
|
|
88
|
+
## Do
|
|
89
|
+
|
|
90
|
+
- Give each call a title a person understands: "Read stock levels at Depot 4", not just the function name.
|
|
91
|
+
- Show the call even when it's boring. Seeing the work is how a reader learns to trust it, or to catch a mistake.
|
|
92
|
+
- Mention a failure in the reply text too, along with what the assistant did instead.
|
|
93
|
+
|
|
94
|
+
## Don't
|
|
95
|
+
|
|
96
|
+
- Don't give a call its own live region (an area screen readers announce on change). The thread's busy status speaks for the whole turn.
|
|
97
|
+
- Don't open the details by default for long payloads. The reply is what matters, and the payload is the evidence.
|
|
98
|
+
- Don't use a ToolCall for something with a side effect the person hasn't agreed to. Ask with ApprovalRequest first.
|
|
99
|
+
|
|
100
|
+
## Anatomy
|
|
101
|
+
|
|
102
|
+
- **Panel**: A thin frame on surface.raised. It's marked as a separate block, so the message's screen reader announcement skips it.
|
|
103
|
+
- **Status glyph**: A Spinner while running, a check when done, an alert circle when failed. Decoration only.
|
|
104
|
+
- **Title and name**: The title written for people, then the tool's own name in a monospaced font. With no title, the name comes first.
|
|
105
|
+
- **Status badge**: Running, Done or Failed as a word in the tone's colour. Colour is never the only signal.
|
|
106
|
+
- **Duration**: In a monospaced font with equal-width digits, for example "1.2 s".
|
|
107
|
+
- **Details**: A "Show details" button (with aria-expanded, which tells screen readers whether it's open) above Input and Output. Those wrap onto new lines instead of scrolling sideways.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
> Extra text that shows on hover or focus. It never holds information people can only get there. It doesn't appear on touch screens, it goes away, and someone may never trigger it.
|
|
4
|
+
|
|
5
|
+
- **Best for:** Extra detail on hover or focus for an element that can take focus, such as an IconButton.
|
|
6
|
+
- **Not for:** The only label, or the only explanation of an error. Use the label prop or visible text.
|
|
7
|
+
- Atom. Source: `src/components/atoms/tooltip/Tooltip.tsx`
|
|
8
|
+
- Rendered docs page: `/components/tooltip`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Tooltip, TooltipProvider } from 'src/components/atoms/tooltip/Tooltip'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
|
|
17
|
+
|
|
18
|
+
## Requires
|
|
19
|
+
|
|
20
|
+
- One <TooltipProvider> near the root of the app. AppShell and the demo layout each mount one, so a screen doesn't add its own.
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `content` | `string` | | Required. The extra text. |
|
|
27
|
+
| `children` | `ReactElement` | | A single element that can take focus. |
|
|
28
|
+
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Which side of the trigger the content opens on. |
|
|
29
|
+
| `className` | `string` | | Merged with the content’s classes. Later Tailwind utilities (the small styling classes this system uses) win. |
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
34
|
+
|
|
35
|
+
### On a button
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Tooltip content="Sends the manifest to the depot; can't be undone once confirmed.">
|
|
39
|
+
<Button>Dispatch</Button>
|
|
40
|
+
</Tooltip>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### On an icon button
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<Tooltip content="Uses the last known GPS fix, not live signal.">
|
|
47
|
+
<IconButton label="Recalculate route">
|
|
48
|
+
<Compass aria-hidden="true" className="w-4 h-4" />
|
|
49
|
+
</IconButton>
|
|
50
|
+
</Tooltip>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Sides
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Tooltip content="Opens above the trigger." side="top">
|
|
57
|
+
<Button variant="secondary">Top</Button>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
<Tooltip content="Opens right of the trigger." side="right">
|
|
60
|
+
<Button variant="secondary">Right</Button>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
<Tooltip content="Opens below the trigger." side="bottom">
|
|
63
|
+
<Button variant="secondary">Bottom</Button>
|
|
64
|
+
</Tooltip>
|
|
65
|
+
<Tooltip content="Opens left of the trigger." side="left">
|
|
66
|
+
<Button variant="secondary">Left</Button>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Keyboard
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<p className="text-sm text-[var(--color-text-secondary)] max-w-md">
|
|
74
|
+
Press Tab to move to the trigger and the tooltip appears. Press Escape and it goes away,
|
|
75
|
+
without you moving the pointer. That's a rule in WCAG 2.2 (the Web Content Accessibility
|
|
76
|
+
Guidelines, the public rulebook for making websites usable by people with disabilities).
|
|
77
|
+
Its success criterion (SC) 1.4.13 says this kind of content has to stay dismissible (you
|
|
78
|
+
can close it), hoverable (you can move the pointer onto it) and persistent (it stays
|
|
79
|
+
until you do).
|
|
80
|
+
</p>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Keyboard
|
|
84
|
+
|
|
85
|
+
| Keys | Result |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `Tab` | Moves focus to the trigger and shows the tooltip. Tabbing on to the next element hides it again. |
|
|
88
|
+
| `Esc` | Dismisses it without moving the pointer. That's required by SC 1.4.13, a success criterion (one numbered rule) in WCAG, the Web Content Accessibility Guidelines. |
|
|
89
|
+
|
|
90
|
+
## Do
|
|
91
|
+
|
|
92
|
+
- Use it for the extra detail, never the essential one.
|
|
93
|
+
- Put it on an element that can take focus. A tooltip on a <div> doesn't reach anyone using a keyboard.
|
|
94
|
+
- Keep it to one sentence.
|
|
95
|
+
- Pair it with an IconButton whose label already names the action. Then the tooltip adds detail instead of repeating the name.
|
|
96
|
+
|
|
97
|
+
## Don't
|
|
98
|
+
|
|
99
|
+
- Don't put a tooltip on a disabled control. It can't take focus, so nobody can reach the tooltip.
|
|
100
|
+
- Don't put interactive content inside one. A tooltip is text only and never takes focus.
|
|
101
|
+
- Don't use it as the only label for an icon button. label does that job.
|
|
102
|
+
- Don't make it the only place an error is explained.
|
|
103
|
+
|
|
104
|
+
## Anatomy
|
|
105
|
+
|
|
106
|
+
- **Provider**: Mounted once in AppShell. It sets the 300ms (millisecond) delay, and the 150ms skip window for moving between triggers.
|
|
107
|
+
- **Trigger**: Uses asChild, so no wrapper element is added and the focus order stays as it was.
|
|
108
|
+
- **Content**: Portalled, meaning it's rendered in a separate layer of the page, outside its trigger. It enters with motion token M1, is capped at 260px (pixels) wide and has the cut bottom-right corner. It's a single object with its own fill and holds nothing that takes focus, so the cut doesn't need an unclipped wrapper. The trigger, not the panel, is what takes focus.
|