@teamblind-chorus/ui 1.2.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 +4 -4
- package/agents/components/avatar-rail/avatar-rail.md +2 -4
- package/agents/components/avatar-rail/avatar-rail.spec.json +10 -14
- 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.md +16 -18
- package/agents/components/banner/banner.spec.json +14 -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 +8 -10
- package/agents/components/button/fab.md +7 -9
- package/agents/components/button/fab.spec.json +10 -12
- package/agents/components/button/group.spec.json +4 -4
- package/agents/components/button/icon.md +21 -23
- package/agents/components/button/icon.spec.json +12 -14
- package/agents/components/button/standard.md +40 -42
- package/agents/components/button/standard.spec.json +20 -22
- package/agents/components/button/text.md +21 -23
- package/agents/components/button/text.spec.json +13 -15
- package/agents/components/button/toggle.md +7 -9
- package/agents/components/button/toggle.spec.json +10 -12
- 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 +17 -13
- package/agents/components/chip/tag.md +22 -24
- package/agents/components/chip/tag.spec.json +19 -15
- 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.md +9 -9
- package/agents/components/empty-state/empty-state.spec.json +14 -14
- 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 +34 -33
- package/agents/components/form-field/search.md +2 -4
- package/agents/components/form-field/search.spec.json +19 -18
- package/agents/components/form-field/select.md +18 -20
- package/agents/components/form-field/select.spec.json +30 -29
- package/agents/components/form-field/textarea.md +3 -5
- package/agents/components/form-field/textarea.spec.json +32 -31
- 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 +20 -20
- package/agents/components/list/entry.md +59 -81
- package/agents/components/list/entry.spec.json +20 -23
- package/agents/components/list/list.md +2 -2
- package/agents/components/list/radio.md +13 -20
- package/agents/components/list/radio.spec.json +16 -20
- package/agents/components/list/standard.md +50 -72
- package/agents/components/list/standard.spec.json +18 -21
- 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 +19 -19
- 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/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 +1 -1
- package/agents/components/spinner/spinner.md +8 -10
- package/agents/components/spinner/spinner.spec.json +9 -9
- 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 +17 -18
- package/agents/components/tab-bar/tab-bar.md +9 -11
- package/agents/components/tab-bar/tab-bar.spec.json +25 -27
- package/agents/components/tabs/rounded.md +6 -8
- package/agents/components/tabs/rounded.spec.json +17 -15
- 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 +14 -16
- 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/tokens.usage.json +71 -226
- package/dist/index.cjs +212 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +212 -223
- package/dist/index.js.map +1 -1
- package/dist/styles.css +386 -387
- 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
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"offer": {
|
|
85
85
|
"type": "object",
|
|
86
86
|
"optional": true,
|
|
87
|
-
"description": "{ label, participants } — inline offer-evaluation banner. Same chrome as `poll` (surfaceVariant slab, leading glyph + label + divider + participant count), but the leading icon is `CompensationFillIcon` and the glyph + label paint in `sys.color.success` (resolves to `ref.palette.green.500`). Surfaces a 'compensation / offer evaluation' post — the author publishes their current salary or a competing offer and asks the community for better-option signals. `label` is constrained to the literal `\"Offer\"` (see `tagBanner.labelEnum`); `participants` is the participant count shown after the divider.",
|
|
87
|
+
"description": "{ label, participants } — inline offer-evaluation banner. Same chrome as `poll` (surfaceVariant slab, leading glyph + label + divider + participant count), but the leading icon is `CompensationFillIcon` and the glyph + label paint in `sys.color.text.success` (resolves to `ref.palette.green.500`). Surfaces a 'compensation / offer evaluation' post — the author publishes their current salary or a competing offer and asks the community for better-option signals. `label` is constrained to the literal `\"Offer\"` (see `tagBanner.labelEnum`); `participants` is the participant count shown after the divider.",
|
|
88
88
|
"fields": {
|
|
89
89
|
"label": {
|
|
90
90
|
"type": "enum",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
},
|
|
138
138
|
"channel": {
|
|
139
139
|
"required": true,
|
|
140
|
-
"description": "Channel / author name, rendered by the [Metadata](../metadata/metadata.md) cluster as an `<a>` link to the channel page. 12px / Semibold / onSurface. No underline at rest; hover underlines the link alone; focus paints a hairline `sys.color.
|
|
140
|
+
"description": "Channel / author name, rendered by the [Metadata](../metadata/metadata.md) cluster as an `<a>` link to the channel page. 12px / Semibold / onSurface. No underline at rest; hover underlines the link alone; focus paints a hairline `sys.color.border.focused` outline at `sys.radius.xs`.",
|
|
141
141
|
"accepts": [
|
|
142
142
|
"text"
|
|
143
143
|
]
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
"required": false,
|
|
183
183
|
"agentRequired": true,
|
|
184
184
|
"omittedBehavior": "collapse",
|
|
185
|
-
"fallbackOnMissingSrc": "sys.color.
|
|
185
|
+
"fallbackOnMissingSrc": "sys.color.surface.sunken",
|
|
186
186
|
"description": "80×80 square image at the trailing edge of the title/body block. radius.sm. `omittedBehavior: collapse` is a runtime safety net — when the prop is undefined the slot drops out of the layout (no reserved whitespace). `agentRequired: true` overrides this for scaffold/generation time: agents MUST always pass a `thumbnail` prop, falling back to `src: \"/placeholder.png\"` when no real subject is implied. `fallbackOnMissingSrc` is the dim-tone fill the component paints when `thumbnail` is present but `src` fails to load (network error or empty string). When the post carries 2+ images, overlays a `SquareStackIcon` badge at the top-right corner (16px / white / 4 inset on top + right).",
|
|
187
187
|
"accepts": [
|
|
188
188
|
"thumbnail"
|
|
@@ -190,11 +190,11 @@
|
|
|
190
190
|
},
|
|
191
191
|
"poll": {
|
|
192
192
|
"required": false,
|
|
193
|
-
"description": "Inline banner naming an attached poll. surfaceVariant fill, 48px min-height. Leading glyph `PollFillIcon` + label, both painted in `sys.color.brand`; label is constrained to the literal `Poll`."
|
|
193
|
+
"description": "Inline banner naming an attached poll. surfaceVariant fill, 48px min-height. Leading glyph `PollFillIcon` + label, both painted in `sys.color.text.brand`; label is constrained to the literal `Poll`."
|
|
194
194
|
},
|
|
195
195
|
"offer": {
|
|
196
196
|
"required": false,
|
|
197
|
-
"description": "Inline banner naming an attached offer-evaluation post. Identical chrome to `poll` — surfaceVariant fill, 48px min-height — with the leading glyph swapped to `CompensationFillIcon` and the glyph + label painted in `sys.color.success` (resolves to `ref.palette.green.500`). Label is constrained to the literal `Offer`."
|
|
197
|
+
"description": "Inline banner naming an attached offer-evaluation post. Identical chrome to `poll` — surfaceVariant fill, 48px min-height — with the leading glyph swapped to `CompensationFillIcon` and the glyph + label painted in `sys.color.text.success` (resolves to `ref.palette.green.500`). Label is constrained to the literal `Offer`."
|
|
198
198
|
},
|
|
199
199
|
"citation": {
|
|
200
200
|
"required": false,
|
|
@@ -209,39 +209,39 @@
|
|
|
209
209
|
},
|
|
210
210
|
"engagement": {
|
|
211
211
|
"required": false,
|
|
212
|
-
"description": "Footer row of three counters laid out as an `xsmall` Text Button group. Likes + Comments are real Text Buttons (`text` / `xsmall` / `secondary`); Views renders as a non-interactive `<span>` matching the same 16-glyph / 12-label rhythm. Row gap follows the xsmall-rung group rule (`sys.layout.inline.sm` = 4px). Like is a toggle: active state retones the label to `sys.color.brand` (via `--button-text-label` override) and swaps `HeartIcon` → `HeartFillIcon`, with the count incrementing in lockstep. Never wraps."
|
|
212
|
+
"description": "Footer row of three counters laid out as an `xsmall` Text Button group. Likes + Comments are real Text Buttons (`text` / `xsmall` / `secondary`); Views renders as a non-interactive `<span>` matching the same 16-glyph / 12-label rhythm. Row gap follows the xsmall-rung group rule (`sys.layout.inline.sm` = 4px). Like is a toggle: active state retones the label to `sys.color.text.brand` (via `--button-text-label` override) and swaps `HeartIcon` → `HeartFillIcon`, with the count incrementing in lockstep. Never wraps."
|
|
213
213
|
}
|
|
214
214
|
},
|
|
215
215
|
"sizing": {
|
|
216
|
-
"containerFill": "sys.color.surface",
|
|
216
|
+
"containerFill": "sys.color.surface.default",
|
|
217
217
|
"containerPaddingBlock": "sys.layout.container.lg",
|
|
218
218
|
"containerPaddingInline": "sys.layout.container.md",
|
|
219
219
|
"interBlockGap": "sys.layout.stack.md",
|
|
220
220
|
"flagTypo": "sys.typo.label.sm",
|
|
221
|
-
"flagColor": "sys.color.brand",
|
|
221
|
+
"flagColor": "sys.color.text.brand",
|
|
222
222
|
"avatarSize": 32,
|
|
223
223
|
"channelTypo": "sys.typo.label.sm",
|
|
224
|
-
"channelColor": "sys.color.
|
|
224
|
+
"channelColor": "sys.color.text.default",
|
|
225
225
|
"timestampTypo": "sys.typo.label.sm",
|
|
226
|
-
"timestampColor": "sys.color.
|
|
226
|
+
"timestampColor": "sys.color.border.boldest",
|
|
227
227
|
"followActionTypo": "sys.typo.label.sm",
|
|
228
|
-
"followActionColorInactive": "sys.color.
|
|
229
|
-
"followActionColorActive": "sys.color.
|
|
228
|
+
"followActionColorInactive": "sys.color.text.link",
|
|
229
|
+
"followActionColorActive": "sys.color.text.subtle",
|
|
230
230
|
"channelRowGap": "sys.layout.inline.md",
|
|
231
231
|
"metaTypo": "sys.typo.label.sm",
|
|
232
|
-
"metaColor": "sys.color.
|
|
232
|
+
"metaColor": "sys.color.text.subtle",
|
|
233
233
|
"metaSeparatorGap": "sys.layout.inline.sm",
|
|
234
234
|
"titleBodyGap": "ref.space.100",
|
|
235
235
|
"titleTypo": "sys.typo.heading.sm",
|
|
236
|
-
"titleColor": "sys.color.
|
|
236
|
+
"titleColor": "sys.color.text.default",
|
|
237
237
|
"containerBottomDividerWidth": "sys.borderWidth.hairline",
|
|
238
|
-
"containerBottomDividerColor": "sys.color.
|
|
238
|
+
"containerBottomDividerColor": "sys.color.border.default",
|
|
239
239
|
"bodyTypo": "sys.typo.body.sm",
|
|
240
|
-
"bodyColor": "sys.color.
|
|
240
|
+
"bodyColor": "sys.color.text.subtle",
|
|
241
241
|
"bodyLineClamp": 2,
|
|
242
242
|
"thumbnailSize": "80 × 80",
|
|
243
243
|
"thumbnailRadius": "sys.radius.sm",
|
|
244
|
-
"thumbnailFallbackFill": "sys.color.
|
|
244
|
+
"thumbnailFallbackFill": "sys.color.surface.sunken",
|
|
245
245
|
"thumbnailMultipleBadge": {
|
|
246
246
|
"trigger": "thumbnail.stacked === true (2+ images attached to the post)",
|
|
247
247
|
"icon": "SquareStackIcon",
|
|
@@ -254,49 +254,49 @@
|
|
|
254
254
|
},
|
|
255
255
|
"engagementCounterShape": "xsmall Text Button (Likes / Comments) or matching static span (Views)",
|
|
256
256
|
"engagementLabelTypo": "sys.typo.label.sm",
|
|
257
|
-
"engagementLabelColor": "sys.color.
|
|
257
|
+
"engagementLabelColor": "sys.color.text.subtle",
|
|
258
258
|
"engagementIconSize": "sys.icon.md",
|
|
259
259
|
"engagementSlotGap": "sys.layout.inline.sm",
|
|
260
260
|
"engagementRowGap": "sys.layout.inline.lg",
|
|
261
|
-
"likeActiveColor": "sys.color.brand",
|
|
261
|
+
"likeActiveColor": "sys.color.text.brand",
|
|
262
262
|
"likeActiveIcon": "HeartFillIcon",
|
|
263
263
|
"likeRestIcon": "HeartIcon",
|
|
264
264
|
"likeLeadingOffset": "Inherited from Text Button's optical-alignment default — no per-call offset; the heart glyph sits at the card's content rail automatically."
|
|
265
265
|
},
|
|
266
266
|
"poll": {
|
|
267
|
-
"fill": "sys.color.
|
|
267
|
+
"fill": "sys.color.surface.sunken",
|
|
268
268
|
"radius": "sys.radius.md",
|
|
269
269
|
"paddingBlock": "ref.space.150",
|
|
270
270
|
"paddingInline": "ref.space.200",
|
|
271
271
|
"minHeight": "ref.space.600",
|
|
272
272
|
"bodyTypo": "14",
|
|
273
273
|
"glyph": "PollFillIcon",
|
|
274
|
-
"glyphColor": "sys.color.brand",
|
|
275
|
-
"labelColor": "sys.color.brand",
|
|
274
|
+
"glyphColor": "sys.color.text.brand",
|
|
275
|
+
"labelColor": "sys.color.text.brand",
|
|
276
276
|
"labelLiteral": "Poll",
|
|
277
|
-
"anatomy": "Leading 'poll-title' sub-container groups glyph (brand tone) + label at 4px gap; 12px flex gap to vertical
|
|
277
|
+
"anatomy": "Leading 'poll-title' sub-container groups glyph (brand tone) + label at 4px gap; 12px flex gap to vertical border.default divider; 12px gap to participant count."
|
|
278
278
|
},
|
|
279
279
|
"offer": {
|
|
280
|
-
"fill": "sys.color.
|
|
280
|
+
"fill": "sys.color.surface.sunken",
|
|
281
281
|
"radius": "sys.radius.md",
|
|
282
282
|
"paddingBlock": "ref.space.150",
|
|
283
283
|
"paddingInline": "ref.space.200",
|
|
284
284
|
"minHeight": "ref.space.600",
|
|
285
285
|
"bodyTypo": "14",
|
|
286
286
|
"glyph": "CompensationFillIcon",
|
|
287
|
-
"glyphColor": "sys.color.success",
|
|
287
|
+
"glyphColor": "sys.color.icon.success",
|
|
288
288
|
"glyphColorResolved": "ref.palette.green.500",
|
|
289
|
-
"labelColor": "sys.color.success",
|
|
289
|
+
"labelColor": "sys.color.text.success",
|
|
290
290
|
"labelLiteral": "Offer",
|
|
291
291
|
"anatomy": "Identical to `poll` — same surfaceVariant slab, same 48 min-height, same leading sub-container + divider + participant count. Differs only in the glyph (CompensationFillIcon) and the editorial tone (success / green-500 instead of brand)."
|
|
292
292
|
},
|
|
293
293
|
"tagBanner": {
|
|
294
294
|
"shared": "`poll` and `offer` are siblings of the same banner family — same chrome, same anatomy. They differ only on the glyph + editorial tone.",
|
|
295
|
-
"labelEnum": "The leading-icon label is constrained to exactly two literals across both modules combined: `\"Poll\"` (for the poll banner) and `\"Offer\"` (for the offer-evaluation banner). Consumers
|
|
295
|
+
"labelEnum": "The leading-icon label is constrained to exactly two literals across both modules combined: `\"Poll\"` (for the poll banner) and `\"Offer\"` (for the offer-evaluation banner). Consumers MUST pass one of these two values, or omit `label` to use the default for the chosen banner kind. No other label string is valid.",
|
|
296
296
|
"renderOrder": "When both modules are present on the same post, `poll` renders above `offer`. Either may stand alone."
|
|
297
297
|
},
|
|
298
298
|
"citation": {
|
|
299
|
-
"textColumnFill": "sys.color.
|
|
299
|
+
"textColumnFill": "sys.color.surface.sunken",
|
|
300
300
|
"outerRadius": "sys.radius.md",
|
|
301
301
|
"heroWidth": "120px",
|
|
302
302
|
"heroBleed": "Flush against the card's leading edge — no container padding, no inter-column gap.",
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
},
|
|
312
312
|
"mention": {
|
|
313
313
|
"typo": "sys.typo.body.sm",
|
|
314
|
-
"color": "sys.color.
|
|
314
|
+
"color": "sys.color.text.link",
|
|
315
315
|
"fontStyle": "italic"
|
|
316
316
|
},
|
|
317
317
|
"states": {
|
|
@@ -326,25 +326,21 @@
|
|
|
326
326
|
"opacity": "sys.state.focus"
|
|
327
327
|
},
|
|
328
328
|
"ring": {
|
|
329
|
-
"
|
|
330
|
-
"
|
|
331
|
-
"outerLayerPosition": "depth 0..2px from the card edge (the outer stroke)",
|
|
332
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
333
|
-
"insetColor": "sys.color.focusInset",
|
|
334
|
-
"insetLayerPosition": "depth 2..3px from the card edge (the counter-ring just inside the outer stroke)",
|
|
329
|
+
"width": "sys.borderWidth.hairline",
|
|
330
|
+
"color": "sys.color.border.focused",
|
|
335
331
|
"implementation": "inset box-shadow constrained strictly inside the card's footprint."
|
|
336
332
|
},
|
|
337
333
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
338
334
|
},
|
|
339
335
|
"behavior": {
|
|
340
336
|
"slotOmissionCollapses": "Runtime safety net — when these optional blocks (flag, followAction, thumbnail, poll, offer, citation, mention) are absent the layout reflows cleanly with no reserved whitespace. This is a graceful-degradation contract for downstream consumers, NOT a license for agents to omit slots at scaffold time. `thumbnail` in particular is `agentRequired` (see props.thumbnail) — agents must always pass it with `/placeholder.png` when no subject photo is implied.",
|
|
341
|
-
"containerBottomDivider": "Each Post card carries a hairline bottom divider (`sys.borderWidth.hairline` × `sys.color.
|
|
337
|
+
"containerBottomDivider": "Each Post card carries a hairline bottom divider (`sys.borderWidth.hairline` × `sys.color.border.default`) so consecutive cards in a stream read with a deliberate inter-card seam. The divider is `border-bottom` on the card itself — layout-safe under `box-sizing: border-box`, no shadow tricks needed. Inside a `<FeedGroup>` (the 3-up bundle case), the inner posts keep their dividers and the group's last card carries the same outer-bottom divider as a standalone Post — the rhythm is identical, the wrapper is purely semantic.",
|
|
342
338
|
"groupCase": "Three (or more) Post cards bundled vertically inside a `<FeedGroup>` semantic wrapper. Used for thread-grouped or topic-bundled feeds where consecutive posts belong together. The group adds no extra surface chrome — each Post keeps its own `surface` fill, `container.lg/md` padding, and bottom divider; the wrapper only contributes a `role=\"region\"` + optional `aria-label` for the bundle's intent. There is no fixed item count contract; 3 is the canonical demo case.",
|
|
343
339
|
"tagBannerLabelConstrained": "The poll and offer banners share one closed enum for their leading label: `\"Poll\"` or `\"Offer\"`. Each module defaults to its own literal when `label` is omitted; passing any other string is a contract violation.",
|
|
344
340
|
"truncationNotWrap": "meta and title truncate; body clamps to two lines. The card never grows to fit a long title.",
|
|
345
341
|
"thumbnailFlexSibling": "Title and body share their inline space with the thumbnail when both are present; thumbnail is a flex sibling, not floated, so the body's two-line clamp computes against the reduced inline width.",
|
|
346
342
|
"engagementNoReflow": "Footer row stays single-line; tiny screens scroll the row rather than wrapping.",
|
|
347
|
-
"likeToggle": "Tapping Likes increments the count and swaps the rest-state HeartIcon / secondary appearance for the active-state HeartFillIcon with the label re-toned to `sys.color.brand` (via a `--button-text-label` override — not the Text Button `primary` appearance — because Feed's active-like colour is the editorial brand tone, not the interactive primary). Glyph re-colours via `currentColor`. Tapping again decrements and reverts. Controlled via `liked` + `onLikeChange`, or left uncontrolled for in-demo behaviour.",
|
|
343
|
+
"likeToggle": "Tapping Likes increments the count and swaps the rest-state HeartIcon / secondary appearance for the active-state HeartFillIcon with the label re-toned to `sys.color.text.brand` (via a `--button-text-label` override — not the Text Button `primary` appearance — because Feed's active-like colour is the editorial brand tone, not the interactive primary). Glyph re-colours via `currentColor`. Tapping again decrements and reverts. Controlled via `liked` + `onLikeChange`, or left uncontrolled for in-demo behaviour.",
|
|
348
344
|
"likeLeadingNudge": "The Like glyph aligns flush with the card's content rail via Text Button's optical-alignment default — the chrome bleeds outward by its own padding on every side, so the visible heart sits at the rail without any per-call offset. The 4px row gap to Comments stays unaffected.",
|
|
349
345
|
"viewsNonInteractive": "Views renders as a non-interactive `<span>` matching the xsmall Text Button visual rhythm — no hover / pressed / focus, no `cursor: pointer`. It is a metric display, not an action that has been turned off, so `aria-disabled` is the wrong shape.",
|
|
350
346
|
"channelAndMetaAreLinks": "Channel name and every `meta` item are independent links. The middot separator between meta items is decorative (`aria-hidden`) and sits outside each link's hit area, so the hover underline never spans the separator."
|
|
@@ -353,8 +349,8 @@
|
|
|
353
349
|
"thumbnail slot omitted at scaffold / agent time — every feed post MUST pass a `thumbnail` prop. Fill `src` with a real subject photo when implied, the bundled `/placeholder.png` otherwise. The runtime `slotOmissionCollapses` reflow is a safety net for downstream consumers, NOT permission for agents to skip the slot",
|
|
354
350
|
"author meta collapsed into a single line — the two meta rows (channel · time / workplace · role · username) are anatomy invariants",
|
|
355
351
|
"engagement footer rendered with raw <button> — like / comments / views are button/text + non-interactive span (views) per the spec",
|
|
356
|
-
"active-like color painted with sys.color.primary — active-like uses sys.color.brand via the --button-text-label plumbing var",
|
|
357
|
-
"raw <a> for hashtag / mention — inline link tone uses sys.color.primary via the body markup contract, not raw anchor styling",
|
|
352
|
+
"active-like color painted with sys.color.background.primary — active-like uses sys.color.text.brand via the --button-text-label plumbing var",
|
|
353
|
+
"raw <a> for hashtag / mention — inline link tone uses sys.color.background.primary via the body markup contract, not raw anchor styling",
|
|
358
354
|
"Feed wrapped in a padding-inline div / className=\"px-*\" / style={{ padding }} — Feed is layoutInset=\"full-bleed\" and pays its own page rail once; an outer padding wrapper double-pays the gutter and misaligns the card with NavigationBar / TabBar (the runtime useFullBleedGuard warns on this)",
|
|
359
355
|
"Feed wrapped in an external <Link>/<a> to make the card navigable — pass the `onClick` prop instead (the card surface becomes the target, interior affordances route independently). An outer anchor re-pays the page rail as a gutter AND nests the card's own anchors (channel link, citation, mention), which breaks DOM parsing"
|
|
360
356
|
]
|
|
@@ -8,6 +8,6 @@ The text-entry primitives — controls the user types into or picks a value from
|
|
|
8
8
|
|
|
9
9
|
## Sub-components
|
|
10
10
|
|
|
11
|
-
- **[Input](./input.md)** — Single-line text input. Surface-toned box, hairline `
|
|
11
|
+
- **[Input](./input.md)** — Single-line text input. Surface-toned box, hairline `border.default` rest stroke, `onSurface` border while active, optional trailing clear ("×"). Corner radius `sys.radius.md`. `error` appearance re-tones to error family. Compose multiple via the **Group** use case.
|
|
12
12
|
- **[Search bar](./search.md)** — Same anatomy and state model as Input with three deltas: leading `SearchIcon` glyph, corner radius stepped to `sys.radius.full` (pill), bare-box-only (no `label`, `helper`, or `maxLength`).
|
|
13
13
|
- **[Select](./select.md)** — Input-shaped picker. Read-only field with a trailing `ArrowDownIcon` (16px) chevron; clicking opens a `BottomSheet` with the option list. Supports the same optional leading icon as Input, plus a horizontal **Group** use case (country code + number, currency + amount).
|
|
@@ -10,7 +10,7 @@ Single-line text field — a bordered, transparent-fill box for short values. Op
|
|
|
10
10
|
|
|
11
11
|
## Default
|
|
12
12
|
|
|
13
|
-
Transparent fill, hairline `
|
|
13
|
+
Transparent fill, hairline `border.default` stroke, placeholder in faint `outline`. Type to see the lifecycle: placeholder → value, stroke steps up, trailing clear (*×*) appears.
|
|
14
14
|
|
|
15
15
|
```preview
|
|
16
16
|
form-field/input/default
|
|
@@ -20,11 +20,9 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
20
20
|
<FormField variant="input" placeholder="Place holder" />
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Error
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`errorContainer` wash, full-strength `error` stroke, `onErrorContainer` text. Optional helper rung re-tones to `sys.color.error`.
|
|
25
|
+
`errorContainer` wash, full-strength `error` stroke, `onErrorContainer` text. Optional helper rung re-tones to `sys.color.text.danger`.
|
|
28
26
|
|
|
29
27
|
```preview
|
|
30
28
|
form-field/input/error
|
|
@@ -55,7 +53,7 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
55
53
|
/>
|
|
56
54
|
```
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
## Label, helper & count
|
|
59
57
|
|
|
60
58
|
When any of `label` / `helper` / `maxLength` is set, the box wraps in a `.chorus-field-group` flex column at `sys.layout.stack.xs` between rungs — label above, helper left or count right below. Helper and count are mutually exclusive; pass both and count wins.
|
|
61
59
|
|
|
@@ -88,7 +86,7 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
88
86
|
/>
|
|
89
87
|
```
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
## Leading icon
|
|
92
90
|
|
|
93
91
|
Optional `leadingIcon` (16px / `sys.icon.md`) pinned inner-left. Decorative (`aria-hidden`); tracks the field's active text colour. Also available on [`select`](./select.md).
|
|
94
92
|
|
|
@@ -107,23 +105,7 @@ import { LocationIcon } from '@teamblind-chorus/ui/icons';
|
|
|
107
105
|
/>
|
|
108
106
|
```
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Compose multiple Inputs into a column via `<FormFieldGroup>`. Each rung keeps its own label / helper / count; group inserts `sys.layout.stack.md` (16px) between rungs — sign-up and profile forms.
|
|
113
|
-
|
|
114
|
-
```preview
|
|
115
|
-
form-field/input/group
|
|
116
|
-
---
|
|
117
|
-
import { FormField, FormFieldGroup } from '@teamblind-chorus/ui';
|
|
118
|
-
|
|
119
|
-
<FormFieldGroup direction="vertical">
|
|
120
|
-
<FormField variant="input" label="Name" placeholder="Your name" />
|
|
121
|
-
<FormField variant="input" label="Email" placeholder="you@example.com" helper="We'll send a confirmation" />
|
|
122
|
-
<FormField variant="input" label="Bio" placeholder="One sentence about you" />
|
|
123
|
-
</FormFieldGroup>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Focus indicator
|
|
108
|
+
## Focused state
|
|
127
109
|
|
|
128
110
|
Focus ring layered on top of the `active` border re-tone.
|
|
129
111
|
|
|
@@ -139,22 +121,38 @@ import { FormField } from '@teamblind-chorus/ui';
|
|
|
139
121
|
/>
|
|
140
122
|
```
|
|
141
123
|
|
|
124
|
+
## Group
|
|
125
|
+
|
|
126
|
+
Compose multiple Inputs into a column via `<FormFieldGroup>`. Each rung keeps its own label / helper / count; group inserts `sys.layout.stack.md` (16px) between rungs — sign-up and profile forms.
|
|
127
|
+
|
|
128
|
+
```preview
|
|
129
|
+
form-field/input/group
|
|
130
|
+
---
|
|
131
|
+
import { FormField, FormFieldGroup } from '@teamblind-chorus/ui';
|
|
132
|
+
|
|
133
|
+
<FormFieldGroup direction="vertical">
|
|
134
|
+
<FormField variant="input" label="Name" placeholder="Your name" />
|
|
135
|
+
<FormField variant="input" label="Email" placeholder="you@example.com" helper="We'll send a confirmation" />
|
|
136
|
+
<FormField variant="input" label="Bio" placeholder="One sentence about you" />
|
|
137
|
+
</FormFieldGroup>
|
|
138
|
+
```
|
|
139
|
+
|
|
142
140
|
## Slots
|
|
143
141
|
|
|
144
142
|
- **container** — the box. Owns transparent fill, the inset-`box-shadow` stroke, radius, padding, and focus ring.
|
|
145
143
|
- **input** — editable text. Single line; value-driven placeholder swap.
|
|
146
144
|
- **clear** — trailing *×* button (`XCircleFillIcon`). Shown only while the box is active and holds a non-empty value.
|
|
147
145
|
- **group** *(optional)* — wrapper holding label / box / helper / count when any is supplied.
|
|
148
|
-
- **label** *(optional)* — visible label above the box. `sys.typo.label.md` / `sys.color.
|
|
149
|
-
- **helper** *(optional)* — assistive text below the box, left-aligned. `sys.typo.body.sm` / `sys.color.
|
|
150
|
-
- **count** *(optional)* — `current/max` count below the box, right-aligned. `sys.typo.body.sm` / `sys.color.
|
|
146
|
+
- **label** *(optional)* — visible label above the box. `sys.typo.label.md` / `sys.color.text.default`, bound via `htmlFor`.
|
|
147
|
+
- **helper** *(optional)* — assistive text below the box, left-aligned. `sys.typo.body.sm` / `sys.color.text.subtle`, referenced by `aria-describedby`; re-tones to `error` on the error appearance. Mutually exclusive with `count`.
|
|
148
|
+
- **count** *(optional)* — `current/max` count below the box, right-aligned. `sys.typo.body.sm` / `sys.color.text.subtle`; live digit steps to `label.md` weight. `aria-live="polite"`. Mutually exclusive with `helper`.
|
|
151
149
|
|
|
152
150
|
## Appearance
|
|
153
151
|
|
|
154
152
|
| Appearance | Background | Border (rest) | Text | Placeholder |
|
|
155
153
|
|------------|-------------------------------|-----------------------------------------------------|-------------------------------|-------------------------------|
|
|
156
|
-
| `default` | `transparent` | `sys.color.
|
|
157
|
-
| `error` | `sys.color.
|
|
154
|
+
| `default` | `transparent` | `sys.color.border.default` (`borderWidth.hairline`) | `sys.color.text.default` | `sys.color.border.boldest` |
|
|
155
|
+
| `error` | `sys.color.background.danger` | `sys.color.text.danger` (`borderWidth.hairline`) | `sys.color.text.danger` | `sys.color.text.danger` |
|
|
158
156
|
|
|
159
157
|
Placeholder vs. value is value-driven, not focus-driven.
|
|
160
158
|
|
|
@@ -179,14 +177,14 @@ A single fixed footprint.
|
|
|
179
177
|
|
|
180
178
|
## States
|
|
181
179
|
|
|
182
|
-
Four interactive states. Load-bearing: `active` — the field has the caret; stroke re-tones to `sys.color.
|
|
180
|
+
Four interactive states. Load-bearing: `active` — the field has the caret; stroke re-tones to `sys.color.text.default` at 2px.
|
|
183
181
|
|
|
184
182
|
| State | Stroke (inset box-shadow) | Additional |
|
|
185
183
|
|------------|---------------------------------------------------------------------------|------------|
|
|
186
|
-
| `default` | 1px, `borderRest` (`
|
|
187
|
-
| `hovered` | 1px, `sys.color.
|
|
188
|
-
| `pressed` | 1px, `sys.color.
|
|
189
|
-
| `active` | 2px, `sys.color.
|
|
184
|
+
| `default` | 1px, `borderRest` (`border.default` / `error`) | Caret hidden. |
|
|
185
|
+
| `hovered` | 1px, `sys.color.border.boldest` (error: stays `error`) | `:hover`. |
|
|
186
|
+
| `pressed` | 1px, `sys.color.border.boldest` + `text` overlay at `sys.state.pressed` | `:active`. |
|
|
187
|
+
| `active` | 2px, `sys.color.text.default` (error: `error`) | Caret visible per the [system caret rule](../../DESIGN.md#caret). |
|
|
190
188
|
| `disabled` | 1px, `borderRest`; container at `sys.state.disabled` opacity | Fill steps to `surfaceContainerLow`; overlays / clear suppressed; `cursor: not-allowed`. |
|
|
191
189
|
|
|
192
190
|
## Focus indicator
|
|
@@ -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": {
|
|
@@ -164,33 +164,34 @@
|
|
|
164
164
|
"focusRing": {
|
|
165
165
|
"composition": "outward",
|
|
166
166
|
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
167
|
-
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.
|
|
168
|
-
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.
|
|
167
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
168
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
169
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
|
|
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)."
|
|
171
171
|
},
|
|
172
172
|
"disabled": {
|
|
173
173
|
"overlay": null,
|
|
174
|
-
"background": "sys.color.
|
|
175
|
-
"
|
|
174
|
+
"background": "sys.color.background.disabled",
|
|
175
|
+
"text": "sys.color.text.disabled",
|
|
176
|
+
"placeholder": "sys.color.text.disabled",
|
|
177
|
+
"border": "sys.color.border.bold",
|
|
176
178
|
"suppressClear": true,
|
|
177
179
|
"suppressFocusRing": true,
|
|
178
|
-
"cursor": "not-allowed"
|
|
180
|
+
"cursor": "not-allowed",
|
|
181
|
+
"note": "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
|
|
179
182
|
}
|
|
180
183
|
},
|
|
181
184
|
"focusIndicator": {
|
|
182
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.",
|
|
183
186
|
"composition": "outward",
|
|
184
|
-
"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.",
|
|
185
188
|
"overlay": {
|
|
186
189
|
"color": "label",
|
|
187
190
|
"opacity": "sys.state.focus"
|
|
188
191
|
},
|
|
189
192
|
"ring": {
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
193
|
-
"insetColor": "sys.color.focusInset"
|
|
193
|
+
"width": "sys.borderWidth.hairline",
|
|
194
|
+
"color": "sys.color.border.focused"
|
|
194
195
|
},
|
|
195
196
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
196
197
|
},
|
|
@@ -203,7 +204,7 @@
|
|
|
203
204
|
},
|
|
204
205
|
"forbidden": [
|
|
205
206
|
"raw <input> styled with Tailwind / inline color — the input is wrapped in the chorus-field chrome that owns the stroke",
|
|
206
|
-
"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",
|
|
207
208
|
"stroke painted via `border:` — stroke is an inset box-shadow on the field",
|
|
208
209
|
"helper text rendered outside the helperText slot"
|
|
209
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": {
|
|
@@ -105,33 +105,34 @@
|
|
|
105
105
|
"focusRing": {
|
|
106
106
|
"composition": "outward",
|
|
107
107
|
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
108
|
-
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.
|
|
109
|
-
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.
|
|
108
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
109
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
110
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
|
|
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)."
|
|
112
112
|
},
|
|
113
113
|
"disabled": {
|
|
114
114
|
"overlay": null,
|
|
115
|
-
"background": "sys.color.
|
|
116
|
-
"
|
|
115
|
+
"background": "sys.color.background.disabled",
|
|
116
|
+
"text": "sys.color.text.disabled",
|
|
117
|
+
"placeholder": "sys.color.text.disabled",
|
|
118
|
+
"border": "sys.color.border.bold",
|
|
117
119
|
"suppressClear": true,
|
|
118
120
|
"suppressFocusRing": true,
|
|
119
|
-
"cursor": "not-allowed"
|
|
121
|
+
"cursor": "not-allowed",
|
|
122
|
+
"note": "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
|
|
120
123
|
}
|
|
121
124
|
},
|
|
122
125
|
"focusIndicator": {
|
|
123
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.",
|
|
124
127
|
"composition": "outward",
|
|
125
|
-
"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.",
|
|
126
129
|
"overlay": {
|
|
127
130
|
"color": "label",
|
|
128
131
|
"opacity": "sys.state.focus"
|
|
129
132
|
},
|
|
130
133
|
"ring": {
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
134
|
-
"insetColor": "sys.color.focusInset"
|
|
134
|
+
"width": "sys.borderWidth.hairline",
|
|
135
|
+
"color": "sys.color.border.focused"
|
|
135
136
|
},
|
|
136
137
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
137
138
|
},
|