division-ds 0.1.1 → 0.2.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/AGENTS.md +16 -2
- package/README.md +11 -0
- package/division-ds-0.2.0.tgz +0 -0
- package/package.json +2 -2
- package/public/docs/components/accordion.md +205 -0
- package/public/docs/components/ai-label.md +88 -0
- package/public/docs/components/alert-dialog.md +290 -0
- package/public/docs/components/alert.md +173 -0
- package/public/docs/components/approval-request.md +146 -0
- package/public/docs/components/auth-form.md +130 -0
- package/public/docs/components/avatar-group.md +150 -0
- package/public/docs/components/avatar.md +177 -0
- package/public/docs/components/badge.md +176 -0
- package/public/docs/components/breadcrumbs.md +110 -0
- package/public/docs/components/button.md +202 -0
- package/public/docs/components/card.md +255 -0
- package/public/docs/components/carousel.md +128 -0
- package/public/docs/components/cart.md +170 -0
- package/public/docs/components/chart-panel.md +356 -0
- package/public/docs/components/chat-message.md +154 -0
- package/public/docs/components/chat-thread.md +242 -0
- package/public/docs/components/checkbox.md +147 -0
- package/public/docs/components/checkout-form.md +126 -0
- package/public/docs/components/command-palette.md +145 -0
- package/public/docs/components/data-table.md +399 -0
- package/public/docs/components/description-list.md +101 -0
- package/public/docs/components/dialog.md +195 -0
- package/public/docs/components/divider.md +95 -0
- package/public/docs/components/dropdown-menu.md +220 -0
- package/public/docs/components/empty-state.md +143 -0
- package/public/docs/components/footer.md +395 -0
- package/public/docs/components/form-field.md +213 -0
- package/public/docs/components/icon-button.md +125 -0
- package/public/docs/components/input.md +198 -0
- package/public/docs/components/kanban-board.md +298 -0
- package/public/docs/components/kbd.md +70 -0
- package/public/docs/components/link.md +96 -0
- package/public/docs/components/list-item.md +238 -0
- package/public/docs/components/loading-animation.md +70 -0
- package/public/docs/components/multi-select.md +200 -0
- package/public/docs/components/navbar.md +142 -0
- package/public/docs/components/newsletter-form.md +110 -0
- package/public/docs/components/page-header.md +149 -0
- package/public/docs/components/pagination.md +210 -0
- package/public/docs/components/product-card.md +169 -0
- package/public/docs/components/progress.md +99 -0
- package/public/docs/components/prompt-input.md +149 -0
- package/public/docs/components/quantity-stepper.md +138 -0
- package/public/docs/components/radio.md +140 -0
- package/public/docs/components/rating.md +104 -0
- package/public/docs/components/score-ring.md +93 -0
- package/public/docs/components/search.md +114 -0
- package/public/docs/components/section-heading.md +111 -0
- package/public/docs/components/segmented-control.md +124 -0
- package/public/docs/components/select.md +222 -0
- package/public/docs/components/sheet.md +143 -0
- package/public/docs/components/sidebar.md +191 -0
- package/public/docs/components/skeleton.md +133 -0
- package/public/docs/components/slider.md +112 -0
- package/public/docs/components/sparkline.md +126 -0
- package/public/docs/components/spinner.md +96 -0
- package/public/docs/components/stat.md +104 -0
- package/public/docs/components/status-tracker.md +143 -0
- package/public/docs/components/stepper.md +145 -0
- package/public/docs/components/switch.md +151 -0
- package/public/docs/components/tabs.md +178 -0
- package/public/docs/components/tag.md +103 -0
- package/public/docs/components/textarea.md +146 -0
- package/public/docs/components/timeline.md +150 -0
- package/public/docs/components/toast.md +192 -0
- package/public/docs/components/tool-call.md +105 -0
- package/public/docs/components/tooltip.md +106 -0
- package/public/manifest/components.json +189 -7
- package/public/manifest/tokens.json +39 -0
- package/scripts/division-ds.mjs +76 -37
- package/scripts/lib/agent-docs.mjs +59 -2
- package/scripts/lib/ai-copy.mjs +106 -0
- package/scripts/lib/button-spacing.mjs +5 -5
- package/scripts/lib/check-text.mjs +44 -0
- package/scripts/lib/contrast.mjs +111 -0
- package/scripts/lib/host-inline.mjs +59 -0
- package/scripts/lib/mcp-server.mjs +115 -0
- package/scripts/lib/mcp-tools.mjs +241 -0
- package/scripts/lib/props-validator.mjs +1 -1
- package/scripts/lib/rules-installer.mjs +42 -4
- 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 +7 -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 +19 -0
- package/skills/division-ds/topics/layout.md +34 -0
- package/skills/division-ds/topics/motion.md +10 -0
- package/skills/division-ds/topics/overlays.md +9 -0
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
## Props
|
|
17
|
+
|
|
18
|
+
| Prop | Type | Default | Description |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `name` | `string` | | Required. The tool's own name, shown in a monospaced font. |
|
|
21
|
+
| `title` | `string` | | A plain-language description of the call, shown above the name. |
|
|
22
|
+
| `status` | `'running' \| 'done' \| 'failed'` | | Required. Shown as a word, a symbol and a colour. |
|
|
23
|
+
| `duration` | `string` | | How long it took, for example "1.2 s". |
|
|
24
|
+
| `input` | `ReactNode` | | What the assistant sent the tool. Folded away by default. |
|
|
25
|
+
| `output` | `ReactNode` | | What came back. Folded away by default. |
|
|
26
|
+
| `defaultOpen` | `boolean` | `false` | Starts with the details showing. |
|
|
27
|
+
| `className` | `string` | | Added to the panel. |
|
|
28
|
+
|
|
29
|
+
## Types and helpers
|
|
30
|
+
|
|
31
|
+
Exported beside the component, from the same file. Import a type with `import type { ... }`.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export type ToolCallStatus = 'running' | 'done' | 'failed'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
40
|
+
|
|
41
|
+
### Running, done and failed
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<div className="flex w-full max-w-xl flex-col gap-3">
|
|
45
|
+
<ToolCall name="query_stock_levels" title="Read stock levels at Depot 4" status="running" />
|
|
46
|
+
<ToolCall
|
|
47
|
+
name="query_stock_levels"
|
|
48
|
+
title="Read stock levels at Depot 4"
|
|
49
|
+
status="done"
|
|
50
|
+
duration="1.2 s"
|
|
51
|
+
input={'{ "depot": 4, "below_reorder_point": true }'}
|
|
52
|
+
output={'[\n { "sku": "K-41", "on_hand": 6, "reorder_at": 10 },\n { "sku": "TP-08", "on_hand": 3, "reorder_at": 8 }\n]'}
|
|
53
|
+
/>
|
|
54
|
+
<ToolCall
|
|
55
|
+
name="fetch_supplier_lead_times"
|
|
56
|
+
title="Check supplier lead times"
|
|
57
|
+
status="failed"
|
|
58
|
+
duration="4.0 s"
|
|
59
|
+
output="Timed out after 4 s. The supplier feed did not answer."
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Without a title, details open
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<div className="flex w-full max-w-xl flex-col gap-3">
|
|
68
|
+
<ToolCall
|
|
69
|
+
name="count_pallets"
|
|
70
|
+
status="done"
|
|
71
|
+
duration="0.3 s"
|
|
72
|
+
input={'{ "depot": 4, "bay": 2 }'}
|
|
73
|
+
output="14 pallets"
|
|
74
|
+
defaultOpen
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Keyboard
|
|
80
|
+
|
|
81
|
+
| Keys | Result |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `Tab` | Moves to "Show details". |
|
|
84
|
+
| `Enter` `Space` | Shows or hides the input and output. |
|
|
85
|
+
|
|
86
|
+
## Do
|
|
87
|
+
|
|
88
|
+
- Give each call a title a person understands: "Read stock levels at Depot 4", not just the function name.
|
|
89
|
+
- Show the call even when it's boring. Seeing the work is how a reader learns to trust it, or to catch a mistake.
|
|
90
|
+
- Mention a failure in the reply text too, along with what the assistant did instead.
|
|
91
|
+
|
|
92
|
+
## Don't
|
|
93
|
+
|
|
94
|
+
- 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.
|
|
95
|
+
- Don't open the details by default for long payloads. The reply is what matters, and the payload is the evidence.
|
|
96
|
+
- Don't use a ToolCall for something with a side effect the person hasn't agreed to. Ask with ApprovalRequest first.
|
|
97
|
+
|
|
98
|
+
## Anatomy
|
|
99
|
+
|
|
100
|
+
- **Panel**: A thin frame on surface.raised. It's marked as a separate block, so the message's screen reader announcement skips it.
|
|
101
|
+
- **Status glyph**: A Spinner while running, a check when done, an alert circle when failed. Decoration only.
|
|
102
|
+
- **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.
|
|
103
|
+
- **Status badge**: Running, Done or Failed as a word in the tone's colour. Colour is never the only signal.
|
|
104
|
+
- **Duration**: In a monospaced font with equal-width digits, for example "1.2 s".
|
|
105
|
+
- **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,106 @@
|
|
|
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
|
+
## Requires
|
|
17
|
+
|
|
18
|
+
- One <TooltipProvider> near the root of the app. AppShell and the demo layout each mount one, so a screen doesn't add its own.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `content` | `string` | | Required. The extra text. |
|
|
25
|
+
| `children` | `ReactElement` | | A single element that can take focus. |
|
|
26
|
+
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Which side of the trigger the content opens on. |
|
|
27
|
+
| `className` | `string` | | Merged with the content’s classes. Later Tailwind utilities (the small styling classes this system uses) win. |
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
32
|
+
|
|
33
|
+
### On a button
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Tooltip content="Sends the manifest to the depot; can't be undone once confirmed.">
|
|
37
|
+
<Button>Dispatch</Button>
|
|
38
|
+
</Tooltip>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### On an icon button
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Tooltip content="Uses the last known GPS fix, not live signal.">
|
|
45
|
+
<IconButton label="Recalculate route">
|
|
46
|
+
<Compass aria-hidden="true" className="w-4 h-4" />
|
|
47
|
+
</IconButton>
|
|
48
|
+
</Tooltip>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Sides
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Tooltip content="Opens above the trigger." side="top">
|
|
55
|
+
<Button variant="secondary">Top</Button>
|
|
56
|
+
</Tooltip>
|
|
57
|
+
<Tooltip content="Opens right of the trigger." side="right">
|
|
58
|
+
<Button variant="secondary">Right</Button>
|
|
59
|
+
</Tooltip>
|
|
60
|
+
<Tooltip content="Opens below the trigger." side="bottom">
|
|
61
|
+
<Button variant="secondary">Bottom</Button>
|
|
62
|
+
</Tooltip>
|
|
63
|
+
<Tooltip content="Opens left of the trigger." side="left">
|
|
64
|
+
<Button variant="secondary">Left</Button>
|
|
65
|
+
</Tooltip>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Keyboard
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<p className="text-sm text-[var(--color-text-secondary)] max-w-md">
|
|
72
|
+
Press Tab to move to the trigger and the tooltip appears. Press Escape and it goes away,
|
|
73
|
+
without you moving the pointer. That's a rule in WCAG 2.2 (the Web Content Accessibility
|
|
74
|
+
Guidelines, the public rulebook for making websites usable by people with disabilities).
|
|
75
|
+
Its success criterion (SC) 1.4.13 says this kind of content has to stay dismissible (you
|
|
76
|
+
can close it), hoverable (you can move the pointer onto it) and persistent (it stays
|
|
77
|
+
until you do).
|
|
78
|
+
</p>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Keyboard
|
|
82
|
+
|
|
83
|
+
| Keys | Result |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `Tab` | Moves focus to the trigger and shows the tooltip. Tabbing on to the next element hides it again. |
|
|
86
|
+
| `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. |
|
|
87
|
+
|
|
88
|
+
## Do
|
|
89
|
+
|
|
90
|
+
- Use it for the extra detail, never the essential one.
|
|
91
|
+
- Put it on an element that can take focus. A tooltip on a <div> doesn't reach anyone using a keyboard.
|
|
92
|
+
- Keep it to one sentence.
|
|
93
|
+
- Pair it with an IconButton whose label already names the action. Then the tooltip adds detail instead of repeating the name.
|
|
94
|
+
|
|
95
|
+
## Don't
|
|
96
|
+
|
|
97
|
+
- Don't put a tooltip on a disabled control. It can't take focus, so nobody can reach the tooltip.
|
|
98
|
+
- Don't put interactive content inside one. A tooltip is text only and never takes focus.
|
|
99
|
+
- Don't use it as the only label for an icon button. label does that job.
|
|
100
|
+
- Don't make it the only place an error is explained.
|
|
101
|
+
|
|
102
|
+
## Anatomy
|
|
103
|
+
|
|
104
|
+
- **Provider**: Mounted once in AppShell. It sets the 300ms (millisecond) delay, and the 150ms skip window for moving between triggers.
|
|
105
|
+
- **Trigger**: Uses asChild, so no wrapper element is added and the focus order stays as it was.
|
|
106
|
+
- **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.
|
|
@@ -1,6 +1,125 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generatedBy": "scripts/build-agent-docs.mjs",
|
|
3
3
|
"components": [
|
|
4
|
+
{
|
|
5
|
+
"name": "AiLabel",
|
|
6
|
+
"title": "AI label",
|
|
7
|
+
"tier": "atoms",
|
|
8
|
+
"slug": "ai-label",
|
|
9
|
+
"summary": "Marks content as made by AI, and on request says why. Given an explanation, the mark is a button that opens a short note right under it, with an optional list of sources. It is a disclosure (a button that shows and hides a region), not a tooltip or a popover: a tooltip can't hold links and never shows on a touch screen, and the system has no popover.",
|
|
10
|
+
"useWhen": "Marking a block of text, a value or a field that an AI wrote, so the person knows to check it.",
|
|
11
|
+
"notFor": "A status or a category: use Badge. A field the AI filled: give the field aiValue, which marks it itself.",
|
|
12
|
+
"source": "src/components/atoms/ai-label/AiLabel.tsx",
|
|
13
|
+
"docs": "/docs/components/ai-label.md",
|
|
14
|
+
"imports": [
|
|
15
|
+
{
|
|
16
|
+
"names": [
|
|
17
|
+
"AiLabel"
|
|
18
|
+
],
|
|
19
|
+
"from": "src/components/atoms/ai-label/AiLabel"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"props": [
|
|
23
|
+
{
|
|
24
|
+
"name": "label",
|
|
25
|
+
"type": "string",
|
|
26
|
+
"default": "'Generated by AI'",
|
|
27
|
+
"description": "The mark's words, and the button's name. Say what the AI made: 'AI summary', 'AI draft'."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "explanation",
|
|
31
|
+
"type": "ReactNode",
|
|
32
|
+
"description": "Why this is AI content and what to do about it, in a sentence or two: what it was made from, and that it needs checking. Without it the mark is static text and nothing opens."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "sources",
|
|
36
|
+
"type": "AiLabelSource[]",
|
|
37
|
+
"description": "{ label, href } for each thing the content was made from. Listed under the explanation; ignored without one."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "renderLink",
|
|
41
|
+
"type": "(props: LinkRenderProps) => ReactNode",
|
|
42
|
+
"default": "a plain anchor",
|
|
43
|
+
"description": "How each source link is drawn. Pass your router's link component for sources inside the app."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "className",
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Merged with the root's classes. When Tailwind utility classes clash, the later one wins."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "ref",
|
|
52
|
+
"type": "Ref<HTMLDivElement>",
|
|
53
|
+
"description": "Passed through to the root div."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "...rest",
|
|
57
|
+
"type": "HTMLAttributes<HTMLDivElement>",
|
|
58
|
+
"description": "Every native attribute, onto the root div."
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"api": [
|
|
62
|
+
{
|
|
63
|
+
"name": "AiLabelSource",
|
|
64
|
+
"kind": "type",
|
|
65
|
+
"code": "export type AiLabelSource = { label: string; href: string }",
|
|
66
|
+
"from": "src/components/atoms/ai-label/AiLabel"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"keyboard": [
|
|
70
|
+
{
|
|
71
|
+
"keys": [
|
|
72
|
+
"Tab"
|
|
73
|
+
],
|
|
74
|
+
"does": "Moves focus to the mark when it has an explanation, then into any source links once open."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"keys": [
|
|
78
|
+
"Enter",
|
|
79
|
+
"Space"
|
|
80
|
+
],
|
|
81
|
+
"does": "Opens or closes the explanation. Focus stays on the button."
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"dos": [
|
|
85
|
+
"Put it right above or beside the AI content it marks, not in a page header far away.",
|
|
86
|
+
"Say in the explanation what the AI used and that the person should check it, the way GitLab's 'Verify before use' does.",
|
|
87
|
+
"Name the sources when there are any. Showing where it came from is how a reader decides whether to trust it."
|
|
88
|
+
],
|
|
89
|
+
"donts": [
|
|
90
|
+
"Don't put it inside a <label>. A button inside a label is read into the field's name; put it beside the label.",
|
|
91
|
+
"Don't mark everything. A label on every line is a label on nothing.",
|
|
92
|
+
"Don't rely on the colour. The words say AI; ai.text only helps you find them."
|
|
93
|
+
],
|
|
94
|
+
"anatomy": [
|
|
95
|
+
{
|
|
96
|
+
"part": "Mark",
|
|
97
|
+
"description": "A sparkle and the label in uppercase mono, in ai.text. Static text when there is no explanation."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"part": "Disclosure button",
|
|
101
|
+
"description": "With an explanation, the mark is a button with aria-expanded and a chevron that turns when open. Its name is the label."
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"part": "Explanation",
|
|
105
|
+
"description": "A region right under the button, edged in ai.border, in text.secondary. Hidden until asked for, then next in the reading order."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"part": "Sources",
|
|
109
|
+
"description": "Optional. A list of links under the explanation, drawn through renderLink."
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"examples": [
|
|
113
|
+
{
|
|
114
|
+
"caption": "Static mark",
|
|
115
|
+
"source": "<AiLabel />"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"caption": "With an explanation and sources",
|
|
119
|
+
"source": "<div className=\"flex flex-col gap-2 max-w-md\">\n <AiLabel\n label=\"AI summary\"\n explanation=\"Summarised from the three most recent reviews of this replica. It can miss details or get them wrong: verify before you rely on it.\"\n sources={[\n { label: 'Review by Ilse Varga', href: '#review-1' },\n { label: 'Review by Tomas Brandt', href: '#review-2' },\n ]}\n />\n <p className=\"text-sm text-[var(--color-text-primary)]\">\n Reviewers find the gearbox quiet and the hop-up easy to tune; two mention a loose stock.\n </p>\n</div>"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
4
123
|
{
|
|
5
124
|
"name": "Avatar",
|
|
6
125
|
"title": "Avatar",
|
|
@@ -952,6 +1071,11 @@
|
|
|
952
1071
|
"default": "'Clear'",
|
|
953
1072
|
"description": "The clear button’s name. If the page has more than one, say what it clears: ‘Clear call sign’."
|
|
954
1073
|
},
|
|
1074
|
+
{
|
|
1075
|
+
"name": "aiValue",
|
|
1076
|
+
"type": "string",
|
|
1077
|
+
"description": "The value an AI proposed. While the field holds exactly this value it shows the AI-filled state: an ai.border edge, a faint ai.wash tint and a sparkle mark that describes the field as Filled by AI. Once the person changes it, the field looks normal again and a Revert to suggestion button puts the value back through onChange (no button on a disabled or read-only field). An invalid field keeps its invalid border."
|
|
1078
|
+
},
|
|
955
1079
|
{
|
|
956
1080
|
"name": "wrapperClassName",
|
|
957
1081
|
"type": "string",
|
|
@@ -986,7 +1110,7 @@
|
|
|
986
1110
|
"keys": [
|
|
987
1111
|
"Tab"
|
|
988
1112
|
],
|
|
989
|
-
"does": "Moves focus to the input, then to its clear
|
|
1113
|
+
"does": "Moves focus to the input, then to its revert, clear and reveal buttons if they are there."
|
|
990
1114
|
},
|
|
991
1115
|
{
|
|
992
1116
|
"keys": [
|
|
@@ -1015,7 +1139,8 @@
|
|
|
1015
1139
|
"Don't set invalid without saying what's wrong.",
|
|
1016
1140
|
"Don't disable a field to make it read-only. Use readOnly, which you can still focus and copy from.",
|
|
1017
1141
|
"Don't add clearable to a search field. Search already has its own clear button and the Escape key.",
|
|
1018
|
-
"Don't build your own show-password button next to a revealable field. You'll end up with two."
|
|
1142
|
+
"Don't build your own show-password button next to a revealable field. You'll end up with two.",
|
|
1143
|
+
"Don't set aiValue on a field the person filled in themselves. The AI look says the machine wrote it."
|
|
1019
1144
|
],
|
|
1020
1145
|
"anatomy": [
|
|
1021
1146
|
{
|
|
@@ -1032,7 +1157,7 @@
|
|
|
1032
1157
|
},
|
|
1033
1158
|
{
|
|
1034
1159
|
"part": "Adornments",
|
|
1035
|
-
"description": "Optional extras inside the right edge, in this order: the count in mono (a fixed-width typeface), the clear button and the reveal toggle. Turning on any of them wraps the input in a relative box (wrapperClassName styles it) and sizes the right padding to fit them, so text never runs underneath. With none on, the input is bare, with no wrapper."
|
|
1160
|
+
"description": "Optional extras inside the right edge, in this order: the AI mark or its revert button, the count in mono (a fixed-width typeface), the clear button and the reveal toggle. Turning on any of them wraps the input in a relative box (wrapperClassName styles it) and sizes the right padding to fit them, so text never runs underneath. With none on, the input is bare, with no wrapper."
|
|
1036
1161
|
},
|
|
1037
1162
|
{
|
|
1038
1163
|
"part": "Placeholder",
|
|
@@ -1064,6 +1189,10 @@
|
|
|
1064
1189
|
"caption": "Clear button",
|
|
1065
1190
|
"source": "<div className=\"w-full max-w-xs\">\n <FormField label=\"Call sign\">\n <Input clearable clearLabel=\"Clear call sign\" defaultValue=\"Anvil-3\" />\n </FormField>\n</div>"
|
|
1066
1191
|
},
|
|
1192
|
+
{
|
|
1193
|
+
"caption": "Filled by AI, with a revert",
|
|
1194
|
+
"source": "<div className=\"w-full max-w-xs\">\n <FormField label=\"Call sign\" help=\"Suggested from your last order. Edit it and the revert button appears.\">\n <Input aiValue=\"Anvil-3\" defaultValue=\"Anvil-3\" />\n </FormField>\n</div>"
|
|
1195
|
+
},
|
|
1067
1196
|
{
|
|
1068
1197
|
"caption": "Labelled the manual way",
|
|
1069
1198
|
"source": "<div className=\"flex flex-col gap-1 text-sm max-w-xs\">\n <label htmlFor=\"input-docs-callsign\">Call sign</label>\n <Input id=\"input-docs-callsign\" aria-describedby=\"input-docs-callsign-help\" />\n <p id=\"input-docs-callsign-help\" className=\"text-xs text-[var(--color-text-secondary)]\">\n Letters, numbers and a hyphen. FormField will set this pattern up for you.\n </p>\n</div>"
|
|
@@ -2886,6 +3015,11 @@
|
|
|
2886
3015
|
"default": "4",
|
|
2887
3016
|
"description": "The browser's own rows attribute. Size it to the answer you expect."
|
|
2888
3017
|
},
|
|
3018
|
+
{
|
|
3019
|
+
"name": "aiValue",
|
|
3020
|
+
"type": "string",
|
|
3021
|
+
"description": "The value an AI proposed, with the same contract as Input. The mark and the revert button sit in the top right corner. While the field holds exactly this value it shows the AI-filled state: an ai.border edge, a faint ai.wash tint and a sparkle mark that describes the field as Filled by AI. Once the person changes it, the field looks normal again and a Revert to suggestion button puts the value back through onChange (no button on a disabled or read-only field). An invalid field keeps its invalid border."
|
|
3022
|
+
},
|
|
2889
3023
|
{
|
|
2890
3024
|
"name": "className",
|
|
2891
3025
|
"type": "string",
|
|
@@ -2962,6 +3096,10 @@
|
|
|
2962
3096
|
"caption": "States",
|
|
2963
3097
|
"source": "<label className=\"flex flex-col gap-1 text-sm w-full max-w-xs\">\n Invalid\n <Textarea invalid defaultValue=\"Site coordinates unclear\" />\n</label>\n<label className=\"flex flex-col gap-1 text-sm w-full max-w-xs\">\n Disabled\n <Textarea disabled defaultValue=\"Locked until review closes\" />\n</label>\n{/* read-only is a different state from disabled and looks different — the text stays\n selectable and the control stays in the tab order, so `read-only:text-[...]` dims the\n text without the disabled fill. It was styled but never demonstrated. */}\n<label className=\"flex flex-col gap-1 text-sm w-full max-w-xs\">\n Read-only\n <Textarea readOnly defaultValue=\"Submitted 14:02, no further edits\" />\n</label>"
|
|
2964
3098
|
},
|
|
3099
|
+
{
|
|
3100
|
+
"caption": "Filled by AI, with a revert",
|
|
3101
|
+
"source": "const AI_NOTES = 'Ships with two midcaps and a sling. Ask for the 1.2 J spring if you play indoors.'\n\n<div className=\"flex flex-col gap-2 w-full max-w-sm\">\n <label htmlFor=\"ai-notes\" className=\"text-sm\">Order notes</label>\n <AiLabel explanation=\"Drafted from the replica's listing. Check the details before you send it.\" />\n <Textarea id=\"ai-notes\" aiValue={AI_NOTES} defaultValue={AI_NOTES} />\n</div>"
|
|
3102
|
+
},
|
|
2965
3103
|
{
|
|
2966
3104
|
"caption": "With a counter",
|
|
2967
3105
|
"source": "function CounterExample() {\n const max = 140\n const [value, setValue] = useState('')\n return (\n <label className=\"flex flex-col gap-1 text-sm w-full max-w-xs\">\n Field day summary\n <Textarea\n value={value}\n onChange={e => setValue(e.target.value)}\n maxLength={max}\n aria-describedby=\"textarea-docs-counter\"\n />\n <span id=\"textarea-docs-counter\" className=\"text-xs text-[var(--color-text-secondary)]\">\n {value.length} / {max}\n </span>\n </label>\n )\n}\n\n<CounterExample />"
|
|
@@ -4045,6 +4183,12 @@
|
|
|
4045
4183
|
"ChatMessage"
|
|
4046
4184
|
],
|
|
4047
4185
|
"from": "src/components/molecules/chat-message/ChatMessage"
|
|
4186
|
+
},
|
|
4187
|
+
{
|
|
4188
|
+
"names": [
|
|
4189
|
+
"ResponseFeedback"
|
|
4190
|
+
],
|
|
4191
|
+
"from": "src/components/molecules/chat-message/ResponseFeedback"
|
|
4048
4192
|
}
|
|
4049
4193
|
],
|
|
4050
4194
|
"requires": [
|
|
@@ -4086,6 +4230,21 @@
|
|
|
4086
4230
|
"name": "className",
|
|
4087
4231
|
"type": "string",
|
|
4088
4232
|
"description": "Goes on the article."
|
|
4233
|
+
},
|
|
4234
|
+
{
|
|
4235
|
+
"name": "onSubmit (ResponseFeedback)",
|
|
4236
|
+
"type": "(vote: 'helpful' | 'not-helpful') => void | Promise<void>",
|
|
4237
|
+
"description": "Required. Runs on a thumbs up or down. Return a promise to show \"Submitting feedback\" until it settles; a rejection puts the buttons back."
|
|
4238
|
+
},
|
|
4239
|
+
{
|
|
4240
|
+
"name": "onDetail (ResponseFeedback)",
|
|
4241
|
+
"type": "(text: string) => void",
|
|
4242
|
+
"description": "Offers an optional, dismissable \"what was wrong\" box after a Not helpful vote. Leave it out for no such step."
|
|
4243
|
+
},
|
|
4244
|
+
{
|
|
4245
|
+
"name": "className (ResponseFeedback)",
|
|
4246
|
+
"type": "string",
|
|
4247
|
+
"description": "Goes on the group."
|
|
4089
4248
|
}
|
|
4090
4249
|
],
|
|
4091
4250
|
"api": [
|
|
@@ -4094,6 +4253,12 @@
|
|
|
4094
4253
|
"kind": "type",
|
|
4095
4254
|
"code": "export type ChatMessageRole = 'user' | 'assistant' | 'system'",
|
|
4096
4255
|
"from": "src/components/molecules/chat-message/ChatMessage"
|
|
4256
|
+
},
|
|
4257
|
+
{
|
|
4258
|
+
"name": "FeedbackVote",
|
|
4259
|
+
"kind": "type",
|
|
4260
|
+
"code": "export type FeedbackVote = 'helpful' | 'not-helpful'",
|
|
4261
|
+
"from": "src/components/molecules/chat-message/ResponseFeedback"
|
|
4097
4262
|
}
|
|
4098
4263
|
],
|
|
4099
4264
|
"keyboard": [
|
|
@@ -4108,7 +4273,8 @@
|
|
|
4108
4273
|
"Keep streaming true until the last piece of text has arrived, then set it to false once.",
|
|
4109
4274
|
"Put tool calls and approvals in the assistant message where they happened, in order with its text.",
|
|
4110
4275
|
"Use a system line for events in the conversation itself: stopped, retried, context cleared.",
|
|
4111
|
-
"Give every message an author, including a system line (\"Status\"). It names the article."
|
|
4276
|
+
"Give every message an author, including a system line (\"Status\"). It names the article.",
|
|
4277
|
+
"Put ResponseFeedback in the actions of a generated reply once it has finished streaming, so a vote rates the whole reply."
|
|
4112
4278
|
],
|
|
4113
4279
|
"donts": [
|
|
4114
4280
|
"Don't animate the text in yourself. useStreamedText or your stream supplies it, and the thread handles announcing it.",
|
|
@@ -4145,7 +4311,7 @@
|
|
|
4145
4311
|
},
|
|
4146
4312
|
{
|
|
4147
4313
|
"part": "Actions",
|
|
4148
|
-
"description": "Small buttons under an assistant message: Copy, Retry."
|
|
4314
|
+
"description": "Small buttons under an assistant message: Copy, Retry, and ResponseFeedback (thumbs up and down)."
|
|
4149
4315
|
},
|
|
4150
4316
|
{
|
|
4151
4317
|
"part": "System line",
|
|
@@ -4160,6 +4326,10 @@
|
|
|
4160
4326
|
{
|
|
4161
4327
|
"caption": "While the reply is still streaming in: aria-busy, an ARIA (Accessible Rich Internet Applications) attribute that tells screen readers the message isn't finished, and a cursor that doesn't blink",
|
|
4162
4328
|
"source": "<div className=\"flex w-full max-w-xl flex-col gap-6\">\n <ChatMessage role=\"assistant\" author=\"Assistant\" streaming>\n Depot 4 holds 1,240 replicas across 38 lines. The busiest\n </ChatMessage>\n</div>"
|
|
4329
|
+
},
|
|
4330
|
+
{
|
|
4331
|
+
"caption": "Feedback on a reply: thumbs up or down, one vote, and an optional detail step after Not helpful",
|
|
4332
|
+
"source": "const pretendSend = () => new Promise<void>(resolve => setTimeout(resolve, 800))\n\n<div className=\"flex w-full max-w-xl flex-col gap-6\">\n <ChatMessage\n role=\"assistant\"\n author=\"Assistant\"\n actions={<ResponseFeedback onSubmit={pretendSend} onDetail={() => {}} />}\n >\n Depot 4 holds 1,240 replicas. Eleven are below their reorder point.\n </ChatMessage>\n</div>"
|
|
4163
4333
|
}
|
|
4164
4334
|
]
|
|
4165
4335
|
},
|
|
@@ -5655,6 +5825,12 @@
|
|
|
5655
5825
|
"type": "readonly string[]",
|
|
5656
5826
|
"description": "Ready-made prompts shown as chips. Clicking one submits it."
|
|
5657
5827
|
},
|
|
5828
|
+
{
|
|
5829
|
+
"name": "editableSuggestions",
|
|
5830
|
+
"type": "boolean",
|
|
5831
|
+
"default": "false",
|
|
5832
|
+
"description": "Clicking a suggestion puts its text in the box to edit instead of sending it. Use it when a person will likely change the prompt first. The chips then stay usable while busy."
|
|
5833
|
+
},
|
|
5658
5834
|
{
|
|
5659
5835
|
"name": "placeholder",
|
|
5660
5836
|
"type": "string",
|
|
@@ -5723,7 +5899,7 @@
|
|
|
5723
5899
|
},
|
|
5724
5900
|
{
|
|
5725
5901
|
"part": "Suggestions",
|
|
5726
|
-
"description": "A group of chips above the box. Clicking one submits its text
|
|
5902
|
+
"description": "A group of chips above the box. Clicking one submits its text (disabled while busy), or with editableSuggestions puts it in the box to edit."
|
|
5727
5903
|
},
|
|
5728
5904
|
{
|
|
5729
5905
|
"part": "Box",
|
|
@@ -7306,6 +7482,11 @@
|
|
|
7306
7482
|
"type": "{ label: string; altText: string; onAction: () => void }",
|
|
7307
7483
|
"description": "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."
|
|
7308
7484
|
},
|
|
7485
|
+
{
|
|
7486
|
+
"name": "replace (ToastOptions)",
|
|
7487
|
+
"type": "string",
|
|
7488
|
+
"description": "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."
|
|
7489
|
+
},
|
|
7309
7490
|
{
|
|
7310
7491
|
"name": "useToast()",
|
|
7311
7492
|
"type": "{ toast: (options: ToastOptions) => string; dismiss: (id: string) => void }",
|
|
@@ -7316,7 +7497,7 @@
|
|
|
7316
7497
|
{
|
|
7317
7498
|
"name": "ToastOptions",
|
|
7318
7499
|
"kind": "type",
|
|
7319
|
-
"code": "export type ToastOptions = {\n title: string\n description?: string\n tone?: Tone\n /** ms before it leaves on its own. Default 5000; 8000 for hostile. */\n duration?: number\n /** Radix requires altText: the way to do the same thing for someone who cannot reach the toast\n * before it leaves. */\n action?: { label: string; altText: string; onAction: () => void }\n}",
|
|
7500
|
+
"code": "export type ToastOptions = {\n title: string\n description?: string\n tone?: Tone\n /** ms before it leaves on its own. Default 5000; 8000 for hostile. */\n duration?: number\n /** Radix requires altText: the way to do the same thing for someone who cannot reach the toast\n * before it leaves. */\n action?: { label: string; altText: string; onAction: () => void }\n /** Any open toast raised with the same string closes as this one opens, so a repeated action\n * (\"Alert acknowledged\" three times) shows one toast, not a stack over the page. */\n replace?: string\n}",
|
|
7320
7501
|
"from": "src/components/molecules/toast/Toast"
|
|
7321
7502
|
},
|
|
7322
7503
|
{
|
|
@@ -7350,6 +7531,7 @@
|
|
|
7350
7531
|
"Use a toast to confirm something the person just did.",
|
|
7351
7532
|
"Give every action an altText that says where else it can be done.",
|
|
7352
7533
|
"Keep the title under about six words.",
|
|
7534
|
+
"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.",
|
|
7353
7535
|
"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)."
|
|
7354
7536
|
],
|
|
7355
7537
|
"donts": [
|
|
@@ -740,6 +740,45 @@
|
|
|
740
740
|
"light": "The dimmed leading-zero pad. 3.82:1 on surface.base, 22% of text.primary's 17.55:1 there, against the dark theme's 31%. Light theme needs a LIGHTER step than text.muted for the same role: muted aliases neutral.500 and measures 11.44:1 here, 65% of ink, which does not read as dimmed and out-shouts the signal.fill numeral it is padding (3.14:1 on this surface). neutral.400 would land at 6.58:1 and 38% of ink, closer to dark's share but more than twice the contrast of the digits beside it, so neutral.300 is the step that keeps the pad quieter than what it pads."
|
|
741
741
|
}
|
|
742
742
|
},
|
|
743
|
+
{
|
|
744
|
+
"name": "--color-ai-text",
|
|
745
|
+
"layer": "semantic",
|
|
746
|
+
"value": {
|
|
747
|
+
"dark": "#78b7ff",
|
|
748
|
+
"light": "#004593"
|
|
749
|
+
},
|
|
750
|
+
"use": "Text or an icon that marks something as made by AI: the AiLabel mark, the AI glyph in a field an AI filled. Always beside a word, never colour alone.",
|
|
751
|
+
"measured": {
|
|
752
|
+
"dark": "Measured by the gate: 9.55:1 on surface.base, 9.08:1 on surface.sunken, 8.32:1 on surface.raised. One step lighter than accent.info.text (info.500) so AI marks and status text do not read as the same thing.",
|
|
753
|
+
"light": "Measured by the gate: 8.39:1 on surface.base, 5.75:1 on surface.sunken, 8.92:1 on surface.raised. info.700 reached only 3.49:1 on surface.sunken, so the light theme takes info.800."
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
"name": "--color-ai-border",
|
|
758
|
+
"layer": "semantic",
|
|
759
|
+
"value": {
|
|
760
|
+
"dark": "#78b7ff",
|
|
761
|
+
"light": "#004593"
|
|
762
|
+
},
|
|
763
|
+
"use": "The boundary of a form field whose value an AI filled and the person has not changed. Input and Textarea paint it from aiValue; do not paint it by hand.",
|
|
764
|
+
"measured": {
|
|
765
|
+
"dark": "The edge of a field an AI filled. 7.91:1 on the AI-filled field (ai.wash over the control fill), far above the 3:1 SC 1.4.11 floor. Same stop as ai.text; a separate token because a boundary and text carry different floors.",
|
|
766
|
+
"light": "The edge of a field an AI filled. 5.24:1 on the AI-filled field (ai.wash over surface.sunken). Same stop as ai.text; a separate token because a boundary and text carry different floors."
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
"name": "--color-ai-wash",
|
|
771
|
+
"layer": "semantic",
|
|
772
|
+
"value": {
|
|
773
|
+
"dark": "rgba(90, 167, 255, 0.1)",
|
|
774
|
+
"light": "rgba(0, 99, 213, 0.08)"
|
|
775
|
+
},
|
|
776
|
+
"use": "The faint tint inside a field an AI filled, laid over the control fill. Input and Textarea paint it; nothing else should.",
|
|
777
|
+
"measured": {
|
|
778
|
+
"dark": "Tint laid over the control fill of an AI-filled field. Text on it is text.primary and gated (composited over surface.sunken). Never a fill on its own, never under coloured text other than ai.text.",
|
|
779
|
+
"light": "Tint laid over the control fill of an AI-filled field. Text on it is text.primary and gated (composited over surface.sunken)."
|
|
780
|
+
}
|
|
781
|
+
},
|
|
743
782
|
{
|
|
744
783
|
"name": "--color-focus-ring",
|
|
745
784
|
"layer": "semantic",
|