@teamblind-chorus/ui 1.1.0 → 2.0.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/README.md +3 -3
- package/agents/AGENTS.md +6 -6
- package/agents/DESIGN.md +245 -244
- package/agents/LOVABLE.md +40 -11
- package/agents/catalog.md +10 -8
- package/agents/components/avatar-rail/avatar-rail.md +2 -4
- package/agents/components/avatar-rail/avatar-rail.spec.json +27 -12
- package/agents/components/badge/role.md +7 -9
- package/agents/components/badge/role.spec.json +6 -6
- package/agents/components/badge/update.md +6 -8
- package/agents/components/badge/update.spec.json +5 -5
- package/agents/components/banner/banner.family.json +3 -1
- package/agents/components/banner/banner.md +66 -15
- package/agents/components/banner/banner.spec.json +37 -14
- package/agents/components/bottom-sheet/bottom-sheet.md +4 -6
- package/agents/components/bottom-sheet/bottom-sheet.spec.json +5 -5
- package/agents/components/bubble/bubble.md +8 -10
- package/agents/components/bubble/bubble.spec.json +11 -11
- package/agents/components/button/button.md +1 -1
- package/agents/components/button/check.md +9 -11
- package/agents/components/button/check.spec.json +25 -8
- package/agents/components/button/fab.md +7 -9
- package/agents/components/button/fab.spec.json +27 -10
- package/agents/components/button/group.spec.json +4 -4
- package/agents/components/button/icon.md +21 -23
- package/agents/components/button/icon.spec.json +29 -12
- package/agents/components/button/standard.md +40 -42
- package/agents/components/button/standard.spec.json +37 -20
- package/agents/components/button/text.md +21 -23
- package/agents/components/button/text.spec.json +30 -13
- package/agents/components/button/toggle.md +7 -9
- package/agents/components/button/toggle.spec.json +27 -10
- package/agents/components/button/toolbar.md +24 -26
- package/agents/components/button/toolbar.spec.json +10 -12
- package/agents/components/carousel/carousel.md +1 -1
- package/agents/components/carousel/post.md +15 -21
- package/agents/components/carousel/post.spec.json +17 -17
- package/agents/components/carousel/profile.md +9 -45
- package/agents/components/carousel/profile.spec.json +17 -17
- package/agents/components/chip/chip.md +1 -1
- package/agents/components/chip/filter.md +22 -24
- package/agents/components/chip/filter.spec.json +34 -11
- package/agents/components/chip/tag.md +22 -24
- package/agents/components/chip/tag.spec.json +36 -13
- package/agents/components/dialog/dialog.md +1 -3
- package/agents/components/dialog/dialog.spec.json +3 -3
- package/agents/components/directory-list/directory-list.md +1 -3
- package/agents/components/directory-list/directory-list.spec.json +2 -2
- package/agents/components/divider/divider.family.json +1 -1
- package/agents/components/divider/divider.md +12 -14
- package/agents/components/divider/divider.spec.json +8 -8
- package/agents/components/empty-state/empty-state.family.json +28 -0
- package/agents/components/empty-state/empty-state.md +69 -0
- package/agents/components/empty-state/empty-state.spec.json +87 -0
- package/agents/components/feed/ad.md +2 -4
- package/agents/components/feed/ad.spec.json +10 -10
- package/agents/components/feed/post.md +41 -43
- package/agents/components/feed/post.spec.json +35 -39
- package/agents/components/form-field/form-field.md +1 -1
- package/agents/components/form-field/input.md +32 -34
- package/agents/components/form-field/input.spec.json +39 -31
- package/agents/components/form-field/search.md +2 -4
- package/agents/components/form-field/search.spec.json +24 -16
- package/agents/components/form-field/select.md +18 -20
- package/agents/components/form-field/select.spec.json +36 -27
- package/agents/components/form-field/textarea.md +3 -5
- package/agents/components/form-field/textarea.spec.json +37 -29
- package/agents/components/header/main.md +4 -6
- package/agents/components/header/main.spec.json +3 -3
- package/agents/components/header/sub.md +6 -8
- package/agents/components/header/sub.spec.json +3 -3
- package/agents/components/list/accordion.md +34 -45
- package/agents/components/list/accordion.spec.json +26 -17
- package/agents/components/list/entry.md +59 -81
- package/agents/components/list/entry.spec.json +37 -21
- package/agents/components/list/list.md +2 -2
- package/agents/components/list/radio.md +13 -20
- package/agents/components/list/radio.spec.json +33 -18
- package/agents/components/list/standard.md +88 -64
- package/agents/components/list/standard.spec.json +52 -20
- package/agents/components/metadata/compact.md +4 -6
- package/agents/components/metadata/compact.spec.json +6 -6
- package/agents/components/metadata/metadata.md +1 -1
- package/agents/components/metadata/standard.md +12 -14
- package/agents/components/metadata/standard.spec.json +10 -10
- package/agents/components/nav-card/nav-card.md +25 -27
- package/agents/components/nav-card/nav-card.spec.json +25 -16
- package/agents/components/nav-list/nav-list.md +2 -8
- package/agents/components/nav-list/nav-list.spec.json +3 -3
- package/agents/components/navigation-bar/main.md +9 -11
- package/agents/components/navigation-bar/main.spec.json +6 -6
- package/agents/components/navigation-bar/search.md +6 -8
- package/agents/components/navigation-bar/search.spec.json +9 -9
- package/agents/components/navigation-bar/sub.md +9 -11
- package/agents/components/navigation-bar/sub.spec.json +7 -7
- package/agents/components/page-shell/page-shell.family.json +1 -1
- package/agents/components/page-shell/page-shell.md +33 -0
- package/agents/components/page-shell/page-shell.spec.json +85 -0
- package/agents/components/pagination/pagination.family.json +1 -1
- package/agents/components/pagination/pagination.md +3 -3
- package/agents/components/pagination/pagination.spec.json +5 -5
- package/agents/components/profile-header/profile-header.md +9 -11
- package/agents/components/profile-header/profile-header.spec.json +9 -9
- package/agents/components/progress/progress.family.json +1 -1
- package/agents/components/progress/progress.md +5 -5
- package/agents/components/progress/progress.spec.json +8 -8
- package/agents/components/side-sheet/side-sheet.md +11 -13
- package/agents/components/side-sheet/side-sheet.spec.json +3 -3
- package/agents/components/skeleton/skeleton.md +7 -9
- package/agents/components/skeleton/skeleton.spec.json +5 -5
- package/agents/components/spinner/spinner.family.json +27 -0
- package/agents/components/spinner/spinner.md +96 -0
- package/agents/components/spinner/spinner.spec.json +82 -0
- package/agents/components/status-tag/status-tag.md +7 -9
- package/agents/components/status-tag/status-tag.spec.json +5 -5
- package/agents/components/suggestion-list/suggestion-list.md +3 -7
- package/agents/components/suggestion-list/suggestion-list.spec.json +8 -12
- package/agents/components/switch/switch.md +12 -14
- package/agents/components/switch/switch.spec.json +23 -15
- package/agents/components/tab-bar/tab-bar.md +9 -11
- package/agents/components/tab-bar/tab-bar.spec.json +37 -23
- package/agents/components/tabs/rounded.md +6 -8
- package/agents/components/tabs/rounded.spec.json +34 -13
- package/agents/components/tabs/segmented.md +4 -6
- package/agents/components/tabs/segmented.spec.json +4 -8
- package/agents/components/tabs/underline.md +9 -11
- package/agents/components/tabs/underline.spec.json +31 -14
- package/agents/components/thumbnail/thumbnail.md +5 -7
- package/agents/components/thumbnail/thumbnail.spec.json +8 -8
- package/agents/components/toast/toast.md +5 -7
- package/agents/components/toast/toast.spec.json +3 -3
- package/agents/components/tooltip/tooltip.md +6 -8
- package/agents/components/tooltip/tooltip.spec.json +4 -4
- package/agents/manifest.json +8 -6
- package/agents/tokens.usage.json +71 -226
- package/agents/usage.json +12 -0
- package/dist/index.cjs +531 -262
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -13
- package/dist/index.d.ts +57 -13
- package/dist/index.js +530 -263
- package/dist/index.js.map +1 -1
- package/dist/styles.css +560 -379
- package/eslint/rules.js +7 -7
- package/package.json +2 -3
- package/agents/anti-patterns.md +0 -533
- package/agents/compose.md +0 -240
- package/agents/images.md +0 -66
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"helper": {
|
|
40
40
|
"type": "node",
|
|
41
41
|
"optional": true,
|
|
42
|
-
"description": "Assistive text rendered below the field box, left-aligned. **Optional on every appearance** — omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.
|
|
42
|
+
"description": "Assistive text rendered below the field box, left-aligned. **Optional on every appearance** — omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.text.danger` so the message reads as the error caption; an error field may still be shown without a helper, in which case only the field box re-tones. Mutually exclusive with `maxLength` — if both are given, the character count is shown and `helper` is ignored."
|
|
43
43
|
},
|
|
44
44
|
"maxLength": {
|
|
45
45
|
"type": "number",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"label": {
|
|
61
61
|
"required": false,
|
|
62
|
-
"description": "Visible label above the box. `sys.typo.label.md`, `sys.color.
|
|
62
|
+
"description": "Visible label above the box. `sys.typo.label.md`, `sys.color.text.default`. Associated with the input via `htmlFor`.",
|
|
63
63
|
"accepts": [
|
|
64
64
|
"text"
|
|
65
65
|
]
|
|
@@ -83,14 +83,14 @@
|
|
|
83
83
|
},
|
|
84
84
|
"helper": {
|
|
85
85
|
"required": false,
|
|
86
|
-
"description": "Assistive text below the box, left-aligned. `sys.typo.body.sm`, `sys.color.
|
|
86
|
+
"description": "Assistive text below the box, left-aligned. `sys.typo.body.sm`, `sys.color.text.subtle`; on the `error` appearance the colour re-tones to `sys.color.text.danger` so the message reads as the error caption. Referenced by the input's `aria-describedby`. Not rendered when a `maxLength` count is present, and intentionally omittable on every appearance (including `error`) — pass nothing and the field renders without an assistive rung.",
|
|
87
87
|
"accepts": [
|
|
88
88
|
"text"
|
|
89
89
|
]
|
|
90
90
|
},
|
|
91
91
|
"count": {
|
|
92
92
|
"required": false,
|
|
93
|
-
"description": "`current/max` character count below the box, right-aligned, present when `maxLength` is set. `sys.typo.body.sm`, `sys.color.
|
|
93
|
+
"description": "`current/max` character count below the box, right-aligned, present when `maxLength` is set. `sys.typo.body.sm`, `sys.color.text.subtle`; the current-count number is `sys.typo.label.md` weight in `sys.color.text.default`. Referenced by the input's `aria-describedby`; updates `aria-live=\"polite\"`.",
|
|
94
94
|
"accepts": [
|
|
95
95
|
"text"
|
|
96
96
|
]
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"slotGap": "sys.layout.inline.md",
|
|
104
104
|
"radius": "sys.radius.md",
|
|
105
105
|
"borderWidth": "sys.borderWidth.hairline",
|
|
106
|
-
"activeStrokeWeight": "sys.borderWidth.
|
|
106
|
+
"activeStrokeWeight": "sys.borderWidth.hairline",
|
|
107
107
|
"groupGap": "sys.layout.stack.xs",
|
|
108
108
|
"labelTypo": "sys.typo.label.md",
|
|
109
109
|
"helperTypo": "sys.typo.body.sm",
|
|
@@ -113,28 +113,28 @@
|
|
|
113
113
|
"iconSize": "sys.icon.md"
|
|
114
114
|
},
|
|
115
115
|
"groupColors": {
|
|
116
|
-
"label": "sys.color.
|
|
117
|
-
"helper": "sys.color.
|
|
118
|
-
"helperError": "sys.color.
|
|
119
|
-
"count": "sys.color.
|
|
120
|
-
"countCurrent": "sys.color.
|
|
116
|
+
"label": "sys.color.text.default",
|
|
117
|
+
"helper": "sys.color.text.subtle",
|
|
118
|
+
"helperError": "sys.color.text.danger",
|
|
119
|
+
"count": "sys.color.text.subtle",
|
|
120
|
+
"countCurrent": "sys.color.text.default"
|
|
121
121
|
},
|
|
122
122
|
"appearances": {
|
|
123
123
|
"default": {
|
|
124
124
|
"background": "transparent",
|
|
125
|
-
"text": "sys.color.
|
|
126
|
-
"placeholder": "sys.color.
|
|
127
|
-
"borderRest": "sys.color.
|
|
128
|
-
"borderHover": "sys.color.
|
|
129
|
-
"borderActive": "sys.color.
|
|
125
|
+
"text": "sys.color.text.default",
|
|
126
|
+
"placeholder": "sys.color.border.boldest",
|
|
127
|
+
"borderRest": "sys.color.border.default",
|
|
128
|
+
"borderHover": "sys.color.border.boldest",
|
|
129
|
+
"borderActive": "sys.color.border.focused"
|
|
130
130
|
},
|
|
131
131
|
"error": {
|
|
132
|
-
"background": "sys.color.
|
|
133
|
-
"text": "sys.color.
|
|
134
|
-
"placeholder": "sys.color.
|
|
135
|
-
"borderRest": "sys.color.
|
|
136
|
-
"borderHover": "sys.color.
|
|
137
|
-
"borderActive": "sys.color.
|
|
132
|
+
"background": "sys.color.background.danger",
|
|
133
|
+
"text": "sys.color.text.danger",
|
|
134
|
+
"placeholder": "sys.color.text.danger",
|
|
135
|
+
"borderRest": "sys.color.border.danger",
|
|
136
|
+
"borderHover": "sys.color.border.danger",
|
|
137
|
+
"borderActive": "sys.color.border.danger"
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
"states": {
|
|
@@ -155,35 +155,43 @@
|
|
|
155
155
|
"nestedActionScope": "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button — that '×' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
|
|
156
156
|
},
|
|
157
157
|
"active": {
|
|
158
|
+
"isFocusState": true,
|
|
158
159
|
"overlay": null,
|
|
159
160
|
"border": "borderActive",
|
|
160
161
|
"strokeWeight": "activeStrokeWeight",
|
|
161
162
|
"caret": "visible",
|
|
162
163
|
"showsClearWhenValue": true,
|
|
163
|
-
"
|
|
164
|
+
"focusRing": {
|
|
165
|
+
"composition": "outward",
|
|
166
|
+
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
167
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
168
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
169
|
+
},
|
|
170
|
+
"note": "This IS the field's keyboard/input-focus state — `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke stays at `hairline` (1px) and re-tones to `borderActive` on active (no thickening) — but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
|
|
164
171
|
},
|
|
165
172
|
"disabled": {
|
|
166
173
|
"overlay": null,
|
|
167
|
-
"background": "sys.color.
|
|
168
|
-
"
|
|
174
|
+
"background": "sys.color.background.disabled",
|
|
175
|
+
"text": "sys.color.text.disabled",
|
|
176
|
+
"placeholder": "sys.color.text.disabled",
|
|
177
|
+
"border": "sys.color.border.bold",
|
|
169
178
|
"suppressClear": true,
|
|
170
179
|
"suppressFocusRing": true,
|
|
171
|
-
"cursor": "not-allowed"
|
|
180
|
+
"cursor": "not-allowed",
|
|
181
|
+
"note": "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
|
|
172
182
|
}
|
|
173
183
|
},
|
|
174
184
|
"focusIndicator": {
|
|
175
185
|
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the field is in (most commonly `active` since focus implies the caret is in the box). The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
|
|
176
186
|
"composition": "outward",
|
|
177
|
-
"compositionReason": "Action affordance with breathing room around it; the
|
|
187
|
+
"compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
|
|
178
188
|
"overlay": {
|
|
179
189
|
"color": "label",
|
|
180
190
|
"opacity": "sys.state.focus"
|
|
181
191
|
},
|
|
182
192
|
"ring": {
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
186
|
-
"insetColor": "sys.color.focusInset"
|
|
193
|
+
"width": "sys.borderWidth.hairline",
|
|
194
|
+
"color": "sys.color.border.focused"
|
|
187
195
|
},
|
|
188
196
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
189
197
|
},
|
|
@@ -196,7 +204,7 @@
|
|
|
196
204
|
},
|
|
197
205
|
"forbidden": [
|
|
198
206
|
"raw <input> styled with Tailwind / inline color — the input is wrapped in the chorus-field chrome that owns the stroke",
|
|
199
|
-
"active-state stroke painted with sys.color.primary / a container tier — the active stroke is sys.color.
|
|
207
|
+
"active-state stroke painted with sys.color.background.primary / a container tier — the active stroke is sys.color.text.default (default appearance) or sys.color.text.danger (error appearance), never primary or a container tier",
|
|
200
208
|
"stroke painted via `border:` — stroke is an inset box-shadow on the field",
|
|
201
209
|
"helper text rendered outside the helperText slot"
|
|
202
210
|
]
|
|
@@ -10,7 +10,7 @@ Search-shaped single-line field — sibling of [Input](./input.md) with a leadin
|
|
|
10
10
|
|
|
11
11
|
## Default
|
|
12
12
|
|
|
13
|
-
Neutral at-rest search bar — transparent fill, hairline `
|
|
13
|
+
Neutral at-rest search bar — transparent fill, hairline `border.default` stroke, `SearchIcon` left, placeholder in faint `outline` colour. Type into the specimen: placeholder → full-strength `onSurface` text, stroke steps to `active`, trailing clear ("×") appears at the right edge.
|
|
14
14
|
|
|
15
15
|
```preview
|
|
16
16
|
form-field/search/default
|
|
@@ -20,9 +20,7 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
20
20
|
<FormField variant="search" placeholder="Search" />
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### Focus indicator
|
|
23
|
+
## Focused state
|
|
26
24
|
|
|
27
25
|
Focus ring layered on top of the `active` pill border. Same composition as [Input → Focus indicator](./input.md#focus-indicator).
|
|
28
26
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"leading": {
|
|
43
43
|
"required": true,
|
|
44
|
-
"description": "The leading `SearchIcon` glyph pinned at the box's inner-left edge. Inherits the field's text colour (`sys.color.
|
|
44
|
+
"description": "The leading `SearchIcon` glyph pinned at the box's inner-left edge. Inherits the field's text colour (`sys.color.text.default`); decorative — not a real button, has `aria-hidden`. 16px (`sys.icon.md`), matching the clear button's footprint so the two affixes balance.",
|
|
45
45
|
"intrinsic": true
|
|
46
46
|
},
|
|
47
47
|
"input": {
|
|
@@ -64,18 +64,18 @@
|
|
|
64
64
|
"slotGap": "sys.layout.inline.md",
|
|
65
65
|
"radius": "sys.radius.full",
|
|
66
66
|
"borderWidth": "sys.borderWidth.hairline",
|
|
67
|
-
"activeStrokeWeight": "sys.borderWidth.
|
|
67
|
+
"activeStrokeWeight": "sys.borderWidth.hairline",
|
|
68
68
|
"textTypo": "sys.typo.body.md",
|
|
69
69
|
"iconSize": "sys.icon.md"
|
|
70
70
|
},
|
|
71
71
|
"appearances": {
|
|
72
72
|
"default": {
|
|
73
73
|
"background": "transparent",
|
|
74
|
-
"text": "sys.color.
|
|
75
|
-
"placeholder": "sys.color.
|
|
76
|
-
"borderRest": "sys.color.
|
|
77
|
-
"borderHover": "sys.color.
|
|
78
|
-
"borderActive": "sys.color.
|
|
74
|
+
"text": "sys.color.text.default",
|
|
75
|
+
"placeholder": "sys.color.border.boldest",
|
|
76
|
+
"borderRest": "sys.color.border.default",
|
|
77
|
+
"borderHover": "sys.color.border.boldest",
|
|
78
|
+
"borderActive": "sys.color.border.focused"
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
81
|
"states": {
|
|
@@ -96,35 +96,43 @@
|
|
|
96
96
|
"nestedActionScope": "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button — that '×' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
|
|
97
97
|
},
|
|
98
98
|
"active": {
|
|
99
|
+
"isFocusState": true,
|
|
99
100
|
"overlay": null,
|
|
100
101
|
"border": "borderActive",
|
|
101
102
|
"strokeWeight": "activeStrokeWeight",
|
|
102
103
|
"caret": "visible",
|
|
103
104
|
"showsClearWhenValue": true,
|
|
104
|
-
"
|
|
105
|
+
"focusRing": {
|
|
106
|
+
"composition": "outward",
|
|
107
|
+
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
108
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
109
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
110
|
+
},
|
|
111
|
+
"note": "This IS the field's keyboard/input-focus state — `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke stays at `hairline` (1px) and re-tones to `borderActive` on active (no thickening) — but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
|
|
105
112
|
},
|
|
106
113
|
"disabled": {
|
|
107
114
|
"overlay": null,
|
|
108
|
-
"background": "sys.color.
|
|
109
|
-
"
|
|
115
|
+
"background": "sys.color.background.disabled",
|
|
116
|
+
"text": "sys.color.text.disabled",
|
|
117
|
+
"placeholder": "sys.color.text.disabled",
|
|
118
|
+
"border": "sys.color.border.bold",
|
|
110
119
|
"suppressClear": true,
|
|
111
120
|
"suppressFocusRing": true,
|
|
112
|
-
"cursor": "not-allowed"
|
|
121
|
+
"cursor": "not-allowed",
|
|
122
|
+
"note": "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
|
|
113
123
|
}
|
|
114
124
|
},
|
|
115
125
|
"focusIndicator": {
|
|
116
126
|
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the field is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
|
|
117
127
|
"composition": "outward",
|
|
118
|
-
"compositionReason": "Action affordance with breathing room around it; the
|
|
128
|
+
"compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
|
|
119
129
|
"overlay": {
|
|
120
130
|
"color": "label",
|
|
121
131
|
"opacity": "sys.state.focus"
|
|
122
132
|
},
|
|
123
133
|
"ring": {
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
127
|
-
"insetColor": "sys.color.focusInset"
|
|
134
|
+
"width": "sys.borderWidth.hairline",
|
|
135
|
+
"color": "sys.color.border.focused"
|
|
128
136
|
},
|
|
129
137
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
130
138
|
},
|
|
@@ -10,7 +10,7 @@ Input-shaped picker — same box, label, helper, and error re-tone as [Input](./
|
|
|
10
10
|
|
|
11
11
|
## Default
|
|
12
12
|
|
|
13
|
-
Neutral at-rest field — transparent fill, hairline `
|
|
13
|
+
Neutral at-rest field — transparent fill, hairline `border.default` stroke, placeholder in faint `outline` colour. The trailing 16px chevron signals that the field opens a sheet rather than a caret.
|
|
14
14
|
|
|
15
15
|
```preview
|
|
16
16
|
form-field/select/default
|
|
@@ -20,9 +20,7 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
20
20
|
<FormField variant="select" placeholder="Select an option" onOpen={() => {}} />
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### With leading icon
|
|
23
|
+
## Leading icon
|
|
26
24
|
|
|
27
25
|
Optional `leadingIcon` (16px / `sys.icon.md`) pins inner-left — same affordance as Search bar's glyph. Available on `input` and `select`.
|
|
28
26
|
|
|
@@ -40,22 +38,7 @@ import { GlobeIcon } from '@teamblind-chorus/ui/icons';
|
|
|
40
38
|
/>
|
|
41
39
|
```
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Pair a Select with an Input on one row via `<FormFieldGroup direction="horizontal">`. The group owns one shared label above and helper below; children render as bare boxes joined at `sys.layout.inline.md` gap. Typical pattern: leading Select (country dial code, currency, unit) + trailing real Input.
|
|
46
|
-
|
|
47
|
-
```preview
|
|
48
|
-
form-field/select/group
|
|
49
|
-
---
|
|
50
|
-
import { FormField, FormFieldGroup } from '@teamblind-chorus/ui';
|
|
51
|
-
|
|
52
|
-
<FormFieldGroup direction="horizontal" label="Phone number" helper="We'll text a one-time code">
|
|
53
|
-
<FormField variant="select" value="+82" onOpen={() => {}} style={{ flex: '0 0 96px' }} />
|
|
54
|
-
<FormField variant="input" placeholder="010-0000-0000" />
|
|
55
|
-
</FormFieldGroup>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Focus indicator
|
|
41
|
+
## Focused state
|
|
59
42
|
|
|
60
43
|
Same as [Input → Focus indicator](./input.md#focus-indicator) — layered on top of the `active` border re-tone.
|
|
61
44
|
|
|
@@ -72,6 +55,21 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
72
55
|
/>
|
|
73
56
|
```
|
|
74
57
|
|
|
58
|
+
## Group
|
|
59
|
+
|
|
60
|
+
Pair a Select with an Input on one row via `<FormFieldGroup direction="horizontal">`. The group owns one shared label above and helper below; children render as bare boxes joined at `sys.layout.inline.md` gap. Typical pattern: leading Select (country dial code, currency, unit) + trailing real Input.
|
|
61
|
+
|
|
62
|
+
```preview
|
|
63
|
+
form-field/select/group
|
|
64
|
+
---
|
|
65
|
+
import { FormField, FormFieldGroup } from '@teamblind-chorus/ui';
|
|
66
|
+
|
|
67
|
+
<FormFieldGroup direction="horizontal" label="Phone number" helper="We'll text a one-time code">
|
|
68
|
+
<FormField variant="select" value="+82" onOpen={() => {}} style={{ flex: '0 0 96px' }} />
|
|
69
|
+
<FormField variant="input" placeholder="010-0000-0000" />
|
|
70
|
+
</FormFieldGroup>
|
|
71
|
+
```
|
|
72
|
+
|
|
75
73
|
## Appearance
|
|
76
74
|
|
|
77
75
|
Same two-appearance axis as [Input → Appearance](./input.md#appearance) — `default` and `error`. The error re-tone covers box, chevron, label and helper rung; placeholder text steps to `onErrorContainer`.
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"leadingIcon": {
|
|
43
43
|
"type": "node",
|
|
44
44
|
"optional": true,
|
|
45
|
-
"description": "Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.
|
|
45
|
+
"description": "Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.text.default` on the default appearance, `sys.color.text.danger` on `error`) so the glyph reads as part of the typed content."
|
|
46
46
|
},
|
|
47
47
|
"onOpen": {
|
|
48
48
|
"type": "function",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"slotGap": "sys.layout.inline.md",
|
|
100
100
|
"radius": "sys.radius.md",
|
|
101
101
|
"borderWidth": "sys.borderWidth.hairline",
|
|
102
|
-
"activeStrokeWeight": "sys.borderWidth.
|
|
102
|
+
"activeStrokeWeight": "sys.borderWidth.hairline",
|
|
103
103
|
"groupGap": "sys.layout.stack.xs",
|
|
104
104
|
"labelTypo": "sys.typo.label.md",
|
|
105
105
|
"helperTypo": "sys.typo.body.sm",
|
|
@@ -109,28 +109,28 @@
|
|
|
109
109
|
"iconSize": "sys.icon.md"
|
|
110
110
|
},
|
|
111
111
|
"groupColors": {
|
|
112
|
-
"label": "sys.color.
|
|
113
|
-
"helper": "sys.color.
|
|
114
|
-
"helperError": "sys.color.
|
|
115
|
-
"count": "sys.color.
|
|
116
|
-
"countCurrent": "sys.color.
|
|
112
|
+
"label": "sys.color.text.default",
|
|
113
|
+
"helper": "sys.color.text.subtle",
|
|
114
|
+
"helperError": "sys.color.text.danger",
|
|
115
|
+
"count": "sys.color.text.subtle",
|
|
116
|
+
"countCurrent": "sys.color.text.default"
|
|
117
117
|
},
|
|
118
118
|
"appearances": {
|
|
119
119
|
"default": {
|
|
120
120
|
"background": "transparent",
|
|
121
|
-
"text": "sys.color.
|
|
122
|
-
"placeholder": "sys.color.
|
|
123
|
-
"borderRest": "sys.color.
|
|
124
|
-
"borderHover": "sys.color.
|
|
125
|
-
"borderActive": "sys.color.
|
|
121
|
+
"text": "sys.color.text.default",
|
|
122
|
+
"placeholder": "sys.color.border.boldest",
|
|
123
|
+
"borderRest": "sys.color.border.default",
|
|
124
|
+
"borderHover": "sys.color.border.boldest",
|
|
125
|
+
"borderActive": "sys.color.border.focused"
|
|
126
126
|
},
|
|
127
127
|
"error": {
|
|
128
|
-
"background": "sys.color.
|
|
129
|
-
"text": "sys.color.
|
|
130
|
-
"placeholder": "sys.color.
|
|
131
|
-
"borderRest": "sys.color.
|
|
132
|
-
"borderHover": "sys.color.
|
|
133
|
-
"borderActive": "sys.color.
|
|
128
|
+
"background": "sys.color.background.danger",
|
|
129
|
+
"text": "sys.color.text.danger",
|
|
130
|
+
"placeholder": "sys.color.text.danger",
|
|
131
|
+
"borderRest": "sys.color.border.danger",
|
|
132
|
+
"borderHover": "sys.color.border.danger",
|
|
133
|
+
"borderActive": "sys.color.border.danger"
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
"states": {
|
|
@@ -150,20 +150,31 @@
|
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
152
|
"active": {
|
|
153
|
+
"isFocusState": true,
|
|
153
154
|
"overlay": null,
|
|
154
155
|
"border": "borderActive",
|
|
155
|
-
"strokeWeight": "activeStrokeWeight"
|
|
156
|
+
"strokeWeight": "activeStrokeWeight",
|
|
157
|
+
"focusRing": {
|
|
158
|
+
"composition": "outward",
|
|
159
|
+
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
160
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
161
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
162
|
+
},
|
|
163
|
+
"note": "This IS the trigger's keyboard-focus / open state — `:focus-visible` and the engaged (open) state coincide for the select trigger, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke re-tones to `borderActive` at `activeStrokeWeight` (1px, = rest) as an inset box-shadow, pixel-stable (no reflow)."
|
|
156
164
|
},
|
|
157
165
|
"disabled": {
|
|
158
166
|
"overlay": null,
|
|
159
|
-
"background": "sys.color.
|
|
160
|
-
"
|
|
167
|
+
"background": "sys.color.background.disabled",
|
|
168
|
+
"text": "sys.color.text.disabled",
|
|
169
|
+
"placeholder": "sys.color.text.disabled",
|
|
170
|
+
"border": "sys.color.border.bold",
|
|
161
171
|
"suppressFocusRing": true,
|
|
162
|
-
"cursor": "not-allowed"
|
|
172
|
+
"cursor": "not-allowed",
|
|
173
|
+
"note": "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
|
|
163
174
|
}
|
|
164
175
|
},
|
|
165
176
|
"focusIndicator": {
|
|
166
|
-
"description": "Same keyboard-focus indicator as Input — outward
|
|
177
|
+
"description": "Same keyboard-focus indicator as Input — outward single ring composed over the active stroke.",
|
|
167
178
|
"composition": "outward",
|
|
168
179
|
"compositionReason": "Action affordance with breathing room around it.",
|
|
169
180
|
"overlay": {
|
|
@@ -171,10 +182,8 @@
|
|
|
171
182
|
"opacity": "sys.state.focus"
|
|
172
183
|
},
|
|
173
184
|
"ring": {
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
177
|
-
"insetColor": "sys.color.focusInset"
|
|
185
|
+
"width": "sys.borderWidth.hairline",
|
|
186
|
+
"color": "sys.color.border.focused"
|
|
178
187
|
},
|
|
179
188
|
"trigger": ":focus-visible"
|
|
180
189
|
},
|
|
@@ -25,11 +25,9 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
25
25
|
/>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## Error
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`appearance="error"` re-tones container to `errorContainer` and stroke to `error`. The optional `helper` paints in `sys.color.error` as the error caption.
|
|
30
|
+
`appearance="error"` re-tones container to `errorContainer` and stroke to `error`. The optional `helper` paints in `sys.color.text.danger` as the error caption.
|
|
33
31
|
|
|
34
32
|
```preview
|
|
35
33
|
form-field/textarea-error
|
|
@@ -45,7 +43,7 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
45
43
|
/>
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
## Character count
|
|
49
47
|
|
|
50
48
|
`maxLength` caps value length and renders a `current/max` count below the box (right-aligned). Mutually exclusive with `helper` — count wins.
|
|
51
49
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"helper": {
|
|
37
37
|
"type": "node",
|
|
38
38
|
"optional": true,
|
|
39
|
-
"description": "Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.
|
|
39
|
+
"description": "Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.text.danger` on the error appearance."
|
|
40
40
|
},
|
|
41
41
|
"maxLength": {
|
|
42
42
|
"type": "number",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"label": {
|
|
63
63
|
"required": false,
|
|
64
|
-
"description": "Visible label above the box. `sys.typo.label.md`, `sys.color.
|
|
64
|
+
"description": "Visible label above the box. `sys.typo.label.md`, `sys.color.text.default`. Associated with the textarea via `htmlFor`.",
|
|
65
65
|
"accepts": ["text"]
|
|
66
66
|
},
|
|
67
67
|
"container": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"slotGap": "sys.layout.inline.md",
|
|
93
93
|
"radius": "sys.radius.md",
|
|
94
94
|
"borderWidth": "sys.borderWidth.hairline",
|
|
95
|
-
"activeStrokeWeight": "sys.borderWidth.
|
|
95
|
+
"activeStrokeWeight": "sys.borderWidth.hairline",
|
|
96
96
|
"groupGap": "sys.layout.stack.xs",
|
|
97
97
|
"labelTypo": "sys.typo.label.md",
|
|
98
98
|
"helperTypo": "sys.typo.body.sm",
|
|
@@ -104,28 +104,28 @@
|
|
|
104
104
|
"resize": "vertical"
|
|
105
105
|
},
|
|
106
106
|
"groupColors": {
|
|
107
|
-
"label": "sys.color.
|
|
108
|
-
"helper": "sys.color.
|
|
109
|
-
"helperError": "sys.color.
|
|
110
|
-
"count": "sys.color.
|
|
111
|
-
"countCurrent": "sys.color.
|
|
107
|
+
"label": "sys.color.text.default",
|
|
108
|
+
"helper": "sys.color.text.subtle",
|
|
109
|
+
"helperError": "sys.color.text.danger",
|
|
110
|
+
"count": "sys.color.text.subtle",
|
|
111
|
+
"countCurrent": "sys.color.text.default"
|
|
112
112
|
},
|
|
113
113
|
"appearances": {
|
|
114
114
|
"default": {
|
|
115
115
|
"background": "transparent",
|
|
116
|
-
"text": "sys.color.
|
|
117
|
-
"placeholder": "sys.color.
|
|
118
|
-
"borderRest": "sys.color.
|
|
119
|
-
"borderHover": "sys.color.
|
|
120
|
-
"borderActive": "sys.color.
|
|
116
|
+
"text": "sys.color.text.default",
|
|
117
|
+
"placeholder": "sys.color.border.boldest",
|
|
118
|
+
"borderRest": "sys.color.border.default",
|
|
119
|
+
"borderHover": "sys.color.border.boldest",
|
|
120
|
+
"borderActive": "sys.color.border.focused"
|
|
121
121
|
},
|
|
122
122
|
"error": {
|
|
123
|
-
"background": "sys.color.
|
|
124
|
-
"text": "sys.color.
|
|
125
|
-
"placeholder": "sys.color.
|
|
126
|
-
"borderRest": "sys.color.
|
|
127
|
-
"borderHover": "sys.color.
|
|
128
|
-
"borderActive": "sys.color.
|
|
123
|
+
"background": "sys.color.background.danger",
|
|
124
|
+
"text": "sys.color.text.danger",
|
|
125
|
+
"placeholder": "sys.color.text.danger",
|
|
126
|
+
"borderRest": "sys.color.border.danger",
|
|
127
|
+
"borderHover": "sys.color.border.danger",
|
|
128
|
+
"borderActive": "sys.color.border.danger"
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
131
|
"states": {
|
|
@@ -137,33 +137,41 @@
|
|
|
137
137
|
"nestedActionScope": "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button — that '×' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
|
|
138
138
|
},
|
|
139
139
|
"active": {
|
|
140
|
+
"isFocusState": true,
|
|
140
141
|
"overlay": null,
|
|
141
142
|
"border": "borderActive",
|
|
142
143
|
"strokeWeight": "activeStrokeWeight",
|
|
143
144
|
"caret": "visible",
|
|
144
|
-
"
|
|
145
|
+
"focusRing": {
|
|
146
|
+
"composition": "outward",
|
|
147
|
+
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
148
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
149
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
150
|
+
},
|
|
151
|
+
"note": "This IS the field's keyboard/input-focus state — `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. Stroke stays at `hairline` (1px), re-toning to `borderActive` as an inset box-shadow (no thickening) — same pixel-stable contract as input."
|
|
145
152
|
},
|
|
146
153
|
"disabled": {
|
|
147
154
|
"overlay": null,
|
|
148
|
-
"background": "sys.color.
|
|
149
|
-
"
|
|
155
|
+
"background": "sys.color.background.disabled",
|
|
156
|
+
"text": "sys.color.text.disabled",
|
|
157
|
+
"placeholder": "sys.color.text.disabled",
|
|
158
|
+
"border": "sys.color.border.bold",
|
|
150
159
|
"suppressFocusRing": true,
|
|
151
|
-
"cursor": "not-allowed"
|
|
160
|
+
"cursor": "not-allowed",
|
|
161
|
+
"note": "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
|
|
152
162
|
}
|
|
153
163
|
},
|
|
154
164
|
"focusIndicator": {
|
|
155
|
-
"description": "Same outward
|
|
165
|
+
"description": "Same outward single ring as input.focusIndicator.",
|
|
156
166
|
"composition": "outward",
|
|
157
|
-
"compositionReason": "Action affordance with breathing room around it; the
|
|
167
|
+
"compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
|
|
158
168
|
"overlay": {
|
|
159
169
|
"color": "label",
|
|
160
170
|
"opacity": "sys.state.focus"
|
|
161
171
|
},
|
|
162
172
|
"ring": {
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
166
|
-
"insetColor": "sys.color.focusInset"
|
|
173
|
+
"width": "sys.borderWidth.hairline",
|
|
174
|
+
"color": "sys.color.border.focused"
|
|
167
175
|
},
|
|
168
176
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
169
177
|
},
|
|
@@ -23,9 +23,7 @@ import { Header } from '@teamblind-chorus/ui';
|
|
|
23
23
|
/>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
### With drill-in chevron
|
|
26
|
+
## Drill-in chevron
|
|
29
27
|
|
|
30
28
|
`trailingIcon` mode — the chevron is a [Button `variant="icon"`](../button/icon.md) at `size="medium"` (32 × 32 capsule, 16-glyph) that owns its own tap target. The chevron paints in `onSurfaceVariant` and rotates -90° to read as chevron-right. The surrounding `<header>` element stays non-interactive — clicks land on the Icon Button, not on the row chrome. Use when the trailing affordance is "open this surface", not "commit a labelled action". `headerAction` and `trailingIcon` are mutually exclusive.
|
|
31
29
|
|
|
@@ -42,7 +40,7 @@ import { Header } from '@teamblind-chorus/ui';
|
|
|
42
40
|
/>
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
## Dropdown
|
|
46
44
|
|
|
47
45
|
`headerDropdown` mode — the trailing affordance is a [Text Button dropdown](../button/text.md#dropdown) (`size="xsmall"`, default appearance) whose **label is the current value** ("Top", "Newest", "All time") and whose trailing chevron flips with `open` as a state signal. Use when the heading row needs an inline sort / filter / range disclosure — "Recommended channels [Top ▾]", "Posts [Last 7 days ▾]". Consumer owns the menu surface (Menu, popover, ListBox) and the `open` state; Header only renders the trigger.
|
|
48
46
|
|
|
@@ -69,7 +67,7 @@ function Example() {
|
|
|
69
67
|
}
|
|
70
68
|
```
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
## Label only
|
|
73
71
|
|
|
74
72
|
Set only `label` (omit `headerAction`, `trailingIcon`, and `headerDropdown`) for a labelled region that needs a heading without a trailing affordance. The row collapses to a single heading at the requested size — no spacer, no empty container.
|
|
75
73
|
|
|
@@ -84,7 +82,7 @@ import { Header } from '@teamblind-chorus/ui';
|
|
|
84
82
|
## Slots
|
|
85
83
|
|
|
86
84
|
- **container** — outer row. Flex with `space-between`; label leads, action / icon / dropdown trails, 8px (`layout.inline.md`) gap. Full-bleed — transparent background and its own padding: `container.md` (16) inline (the shared content rail), `stack.lg` (24) block-start, `stack.md` (16) block-end — so the label lands on the same rail as the rows beneath it; bundling hosts add no inline padding of their own and absorb only the block padding. Stays a non-interactive `<header>` / `<div>` — the trailing affordance owns its own hit target.
|
|
87
|
-
- **label** *(optional)* — heading text. `<h3>` by default; override the wrapper with `as="div"` when the surrounding host already owns the heading semantics. Color `sys.color.
|
|
85
|
+
- **label** *(optional)* — heading text. `<h3>` by default; override the wrapper with `as="div"` when the surrounding host already owns the heading semantics. Color `sys.color.text.default`. Typo per size.
|
|
88
86
|
- **action** *(optional, headerAction mode)* — trailing Text Button. Fixed at `size="xsmall"`, `appearance="accent"`.
|
|
89
87
|
- **icon** *(optional, trailingIcon mode)* — trailing [Icon Button](../button/icon.md) (`variant="icon"` `size="medium"`) hosting a 16px glyph (canonical: chevron-right). Its own tap target — clicks land on the Icon Button, not the surrounding header. Supply `aria-label` for the icon-only button (defaults to `"Open <label>"` when `label` is a string).
|
|
90
88
|
- **dropdown** *(optional, headerDropdown mode)* — trailing [Text Button dropdown](../button/text.md#dropdown) (`size="xsmall"`, default appearance). Label is the current value; trailing chevron flips between `ChevronDownIcon` (closed) and `ChevronUpIcon` (open) tied to `headerDropdown.open`. Owns its own tap target — clicks land on the dropdown trigger, not the surrounding header.
|