@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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "List",
|
|
4
4
|
"family": "list",
|
|
5
5
|
"subcomponent": "standard",
|
|
6
|
-
"description": "The default List variant — display or navigation rows over the shared List anatomy. The whole row is the click target; no selection model. A row is text-only by default (no leading slot); it opts into a **leading image** (the image type) by passing a `thumbnail`, which renders a 40px [Thumbnail](../thumbnail/thumbnail.md) at the leading edge with a 12px (`sys.layout.inline.lg`) gap to the text group, or into a **leading icon** (the icon type) by passing an `icon`, which renders a 24px (`sys.icon.lg`) glyph with an 8px (`sys.layout.inline.md`) gap. A row opts into a trailing drill-in chevron with `nav: true` (the drill-in case — the row routes the user to another surface); a per-item `trailingIcon` overrides the auto chevron. A row opts into an inline **count badge** to the right of the label via `count` — separated from the label by `sys.layout.inline.sm` (4), the unread / status-count case — and it composes with the chevron / trailing slot on the same row. For the richer directory shape (selectable 32/48/56 avatar + stacked `secondary` identity line) reach for [list/entry](./entry.md); Standard's leading image is single-density at the 40 rung.",
|
|
6
|
+
"description": "The default List variant — display or navigation rows over the shared List anatomy. The whole row is the click target; no selection model. A row is text-only by default (no leading slot); it opts into a **leading image** (the image type) by passing a `thumbnail`, which renders a 40px [Thumbnail](../thumbnail/thumbnail.md) at the leading edge with a 12px (`sys.layout.inline.lg`) gap to the text group, or into a **leading icon** (the icon type) by passing an `icon`, which renders a 24px (`sys.icon.lg`) glyph with an 8px (`sys.layout.inline.md`) gap. A row opts into a trailing drill-in chevron with `nav: true` (the drill-in case — the row routes the user to another surface); a per-item `trailingIcon` overrides the auto chevron. A row opts into an inline **count badge** to the right of the label via `count` — separated from the label by `sys.layout.inline.sm` (4), the unread / status-count case — and it composes with the chevron / trailing slot on the same row. For the richer directory shape (selectable 32/48/56 avatar + stacked `secondary` identity line) reach for [list/entry](./entry.md); Standard's leading image is single-density at the 40 rung. A row opts into an embedded **`banner`** — a [Banner](../banner/banner.md) painted below the row's text group at the row's full content width, `sys.layout.stack.xs` (8) below it — for an in-row call-out tied to that row's subject.",
|
|
7
7
|
"element": "ul",
|
|
8
8
|
"props": {
|
|
9
9
|
"embedded": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"slots": {
|
|
21
21
|
"container": {
|
|
22
22
|
"required": true,
|
|
23
|
-
"description": "Outer scroll surface. Vertical stack with a transparent fill (inherits parent container tone); rows separated by a 1px
|
|
23
|
+
"description": "Outer scroll surface. Vertical stack with a transparent fill (inherits parent container tone); rows separated by a 1px border.default divider, not a gap."
|
|
24
24
|
},
|
|
25
25
|
"row": {
|
|
26
26
|
"required": true,
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"leading": {
|
|
30
30
|
"required": false,
|
|
31
|
-
"fallbackOnMissingSrc": "sys.color.
|
|
31
|
+
"fallbackOnMissingSrc": "sys.color.surface.sunken",
|
|
32
32
|
"description": "The leading slot — omitted by default (no reserved leading whitespace), hosting one of two mutually exclusive types. **Image type** (`thumbnail`): a [Thumbnail](../thumbnail/thumbnail.md) at the 40 rung, vertically centred against the label column, with a 12px (`sys.layout.inline.lg`) gap to the text group; `thumbnail` props (`src`, `alt`, `updateDot`, `logoBadge`) forward verbatim, and `fallbackOnMissingSrc` is the dim-tone fill it paints when `src` is empty / fails to load (at scaffold time, agents fill `src` with `/placeholder.png` rather than relying on the fallback). **Icon type** (`icon`): a 24px (`sys.icon.lg`) decorative glyph in `onSurfaceVariant`, vertically centred, with an 8px (`sys.layout.inline.md`) gap to the text group — the category-mark shape for settings / menu rows. The slot enforces the 24 rung regardless of the glyph's own `size`. A row passes `thumbnail` or `icon`, never both.",
|
|
33
33
|
"accepts": [
|
|
34
34
|
"thumbnail",
|
|
@@ -67,6 +67,13 @@
|
|
|
67
67
|
"navChevron": {
|
|
68
68
|
"required": false,
|
|
69
69
|
"description": "Auto-rendered 16px right-pointing chevron at the trailing edge, painted when the row sets `nav: true` — the drill-in affordance signalling the row routes to another surface. `onSurfaceVariant` tone, decorative (`aria-hidden`); never a separate hit target — the whole row is the click target. A per-item `trailingIcon` replaces it."
|
|
70
|
+
},
|
|
71
|
+
"banner": {
|
|
72
|
+
"required": false,
|
|
73
|
+
"description": "Optional embedded [Banner](../banner/banner.md) painted **below** the row's text group, separated by `sys.layout.stack.xs` (8). The row flips from a single horizontal line to a vertical stack — the normal leading + label + trailing line on top, the Banner spanning the row's **full content width** (aligned to the same 16px inline inset as the label above it) underneath. Reach for it when a list row needs an in-row call-out tied to that row's subject — a follow-up prompt, a capability nudge, a single-line CTA — rather than a separate full-width Banner detached from the row. The Banner keeps its own fill / radius / padding and its full prop surface (use `appearance=\"accent\"` + `neutralBody` for the quiet-tint call-out shape, a leading `icon`, a `trailingAction` Text Button). It is a **nested-action region**: clicks inside it stop propagating before they reach the row's `onClick`, and the row's hover / pressed overlay is suppressed while the pointer sits on it — same contract as `trailingIcon`.",
|
|
74
|
+
"accepts": [
|
|
75
|
+
"banner"
|
|
76
|
+
]
|
|
70
77
|
}
|
|
71
78
|
},
|
|
72
79
|
"rowProps": {
|
|
@@ -102,10 +109,15 @@
|
|
|
102
109
|
"optional": true,
|
|
103
110
|
"description": "Trailing-edge node. Wrapped in a slot that stops click propagation so the slot is its own hit target separate from the row's `onClick`."
|
|
104
111
|
},
|
|
112
|
+
"banner": {
|
|
113
|
+
"type": "node",
|
|
114
|
+
"optional": true,
|
|
115
|
+
"description": "Embedded `<Banner>` rendered below the row's text group at the row's full content width, `sys.layout.stack.xs` (8) below it. A nested-action region: its own controls (e.g. a `trailingAction` Text Button) never commit the row, and the row's hover / pressed overlay is suppressed over it. Canonical fill: `<Banner appearance=\"accent\" neutralBody icon={…} trailingAction={…}>…</Banner>`."
|
|
116
|
+
},
|
|
105
117
|
"divider": {
|
|
106
118
|
"type": "boolean",
|
|
107
119
|
"default": true,
|
|
108
|
-
"description": "Per-row bottom-divider opt-out. Pass `divider: false` to suppress the hairline `
|
|
120
|
+
"description": "Per-row bottom-divider opt-out. Pass `divider: false` to suppress the hairline `border.default` rule beneath the row; the row's footprint and inline padding stay unchanged. Reach for it when a visual group ends mid-stack and the divider would visually fence off the next group from its label. The last row already omits its divider via `:not(:last-child)`."
|
|
109
121
|
},
|
|
110
122
|
"nav": {
|
|
111
123
|
"type": "boolean",
|
|
@@ -147,21 +159,23 @@
|
|
|
147
159
|
"leadingIconGapNote": "8px (`sys.layout.inline.md`) between a leading 24px (`sys.icon.lg`) icon (the icon type) and the text group — the icon-leading rung of the family's role-based row spacing (the base leading gap), narrower than the 12px image rung. Applies only to rows that carry an `icon`.",
|
|
148
160
|
"trailingActionGap": "sys.layout.inline.md",
|
|
149
161
|
"trailingActionGapNote": "Fixed 8px (`sys.layout.inline.md`) between the text group and a trailing icon / nav chevron — the family-wide trailing gap, identical across every List variant.",
|
|
162
|
+
"bannerGap": "sys.layout.stack.xs",
|
|
163
|
+
"bannerGapNote": "8px (`sys.layout.stack.xs`) vertical gap between the row's text group and an embedded `banner` below it. The Banner spans the row's full content width (the 16px inline inset is the row's own padding, not paid again by the Banner — see banner.md § Layout inset).",
|
|
150
164
|
"labelToCountGap": "sys.layout.inline.sm",
|
|
151
165
|
"labelToCountGapNote": "4px (`sys.layout.inline.sm`) between the label and an inline `count` badge — they tile flush on the primary line as one label+count block, narrower than the 8px trailing gap. Mirrors list/entry's identity-group spacing.",
|
|
152
166
|
"dividerWidth": "sys.borderWidth.hairline",
|
|
153
|
-
"dividerColor": "sys.color.
|
|
167
|
+
"dividerColor": "sys.color.border.default",
|
|
154
168
|
"dividerPerRowOptOut": "Pass `divider: false` on a row to suppress its bottom divider.",
|
|
155
169
|
"leadingThumbnailSize": 40,
|
|
156
170
|
"labelTypo": "sys.typo.body.md",
|
|
157
|
-
"labelColor": "sys.color.
|
|
171
|
+
"labelColor": "sys.color.text.default",
|
|
158
172
|
"supportingTypo": "sys.typo.body.sm",
|
|
159
|
-
"supportingColor": "sys.color.
|
|
173
|
+
"supportingColor": "sys.color.text.subtle",
|
|
160
174
|
"supportingOffset": "0",
|
|
161
175
|
"trailingIconSize": "16 × 16",
|
|
162
|
-
"trailingIconColor": "sys.color.
|
|
176
|
+
"trailingIconColor": "sys.color.text.subtle",
|
|
163
177
|
"navChevronSize": "16 × 16",
|
|
164
|
-
"navChevronColor": "sys.color.
|
|
178
|
+
"navChevronColor": "sys.color.text.subtle"
|
|
165
179
|
},
|
|
166
180
|
"states": {
|
|
167
181
|
"default": {
|
|
@@ -179,28 +193,44 @@
|
|
|
179
193
|
"opacity": "sys.state.pressed"
|
|
180
194
|
}
|
|
181
195
|
},
|
|
196
|
+
"focused": {
|
|
197
|
+
"overlay": {
|
|
198
|
+
"color": "label",
|
|
199
|
+
"opacity": "sys.state.focus"
|
|
200
|
+
},
|
|
201
|
+
"focusRing": {
|
|
202
|
+
"composition": "inward",
|
|
203
|
+
"layer": "::after/::before overlay — position:absolute, inset:0, inset box-shadow, no reflow (DESIGN.md Focus ring composition)",
|
|
204
|
+
"innerCounterRing": {
|
|
205
|
+
"width": "sys.borderWidth.hairline",
|
|
206
|
+
"color": "sys.color.border.focused"
|
|
207
|
+
},
|
|
208
|
+
"outerRing": {
|
|
209
|
+
"width": "sys.borderWidth.thin",
|
|
210
|
+
"color": "sys.color.border.focused"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"note": "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the row is in; never via plain mouse click."
|
|
214
|
+
},
|
|
182
215
|
"nestedActionScope": "The hover / pressed overlay is suppressed while the pointer sits on an independent trailing action (a `trailingIcon` button — favorite / mute / Follow). The small control owns the state; the large row does NOT also read as hovered / pressed. The decorative nav chevron is exempt — it is the row's own drill-in affordance, so hovering it still lights the whole row. The visual-state boundary matches the event boundary (the trailing action already stops propagation).",
|
|
183
216
|
"disabled": {
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"pointerEvents": "none"
|
|
217
|
+
"text": "sys.color.text.disabled",
|
|
218
|
+
"icon": "sys.color.icon.disabled",
|
|
219
|
+
"pointerEvents": "none",
|
|
220
|
+
"note": "Explicit disabled (no opacity): row text to text.disabled, icons to icon.disabled. The inter-row divider and focus overlay are unaffected (they were never part of the content tone)."
|
|
187
221
|
}
|
|
188
222
|
},
|
|
189
223
|
"focusIndicator": {
|
|
190
224
|
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the row is in.",
|
|
191
225
|
"composition": "inward",
|
|
192
|
-
"compositionReason": "Rows tile the column flush with only a hairline `
|
|
226
|
+
"compositionReason": "Rows tile the column flush with only a hairline `border.default` divider between them; an outward ring would overlap the divider and the neighbouring row.",
|
|
193
227
|
"overlay": {
|
|
194
228
|
"color": "label",
|
|
195
229
|
"opacity": "sys.state.focus"
|
|
196
230
|
},
|
|
197
231
|
"ring": {
|
|
198
|
-
"
|
|
199
|
-
"
|
|
200
|
-
"outerLayerPosition": "depth 0..2px from the row edge (the outer stroke)",
|
|
201
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
202
|
-
"insetColor": "sys.color.focusInset",
|
|
203
|
-
"insetLayerPosition": "depth 2..3px from the row edge (the counter-ring just inside the outer stroke)",
|
|
232
|
+
"width": "sys.borderWidth.hairline",
|
|
233
|
+
"color": "sys.color.border.focused",
|
|
204
234
|
"implementation": "inset box-shadow on the row's `::before` overlay (the `::after` carries the inter-row divider). Constrained strictly inside the row's footprint and never exceeds it."
|
|
205
235
|
},
|
|
206
236
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
@@ -217,6 +247,8 @@
|
|
|
217
247
|
"nav chevron as a separate hit target — the drill-in chevron is decorative; the whole row is the click target",
|
|
218
248
|
"leading thumbnail at a size other than the row's intrinsic 40 rung",
|
|
219
249
|
"compact directory rows (selectable 32/48/56 avatar + stacked secondary line + optional toggle) built as a leading-image Standard row — that directory anatomy is [list/entry](./entry.md); Standard's image type is single-density at the 40 rung. (Standard does carry an inline `count` badge next to the label for unread / status counts, but not the avatar-rung directory shape.)",
|
|
220
|
-
"raw `border:` on the row — list seam is the family's bottom divider via
|
|
250
|
+
"raw `border:` on the row — list seam is the family's bottom divider via border.default",
|
|
251
|
+
"embedded `banner` painted with a per-child `margin-block` / `padding-block` wrapper to fake the 8px gap — the text-group↔Banner gap is the row stack's `gap: sys.layout.stack.xs`, and the Banner's horizontal inset is the row's own 16px padding; Banner ships no outer margin",
|
|
252
|
+
"embedded `banner` as a separate `<List>` row beneath the header — the call-out belongs to its row's subject, so it nests inside that row via `item.banner`, not as a sibling row that the divider would fence off"
|
|
221
253
|
]
|
|
222
254
|
}
|
|
@@ -25,9 +25,7 @@ import { Metadata } from '@teamblind-chorus/ui';
|
|
|
25
25
|
/>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
##
|
|
29
|
-
|
|
30
|
-
### With role badge
|
|
28
|
+
## Role badge
|
|
31
29
|
|
|
32
30
|
An object identity item carries `badge` — a single presentational mark rendered after the nickname's link, outside the `<a>`, at the middots' 4px gap. At most one badge rides the nickname, never a stack.
|
|
33
31
|
|
|
@@ -59,9 +57,9 @@ import { Metadata, Badge } from '@teamblind-chorus/ui';
|
|
|
59
57
|
| Slot | Token bindings |
|
|
60
58
|
|---------------|----------------|
|
|
61
59
|
| avatar | [Thumbnail](../thumbnail/thumbnail.md) `size={32}` |
|
|
62
|
-
| meta | `sys.typo.label.sm` / `sys.color.
|
|
63
|
-
| timestamp | `sys.typo.label.sm` / `sys.color.
|
|
64
|
-
| dot separator | `·` glyph, `color: sys.color.
|
|
60
|
+
| meta | `sys.typo.label.sm` / `sys.color.text.subtle`, links inherit; underline on hover |
|
|
61
|
+
| timestamp | `sys.typo.label.sm` / `sys.color.border.boldest` |
|
|
62
|
+
| dot separator | `·` glyph, `color: sys.color.border.boldest`, **`line-height: 1`** so its line-box equals its font-size — never inflates the text line |
|
|
65
63
|
|
|
66
64
|
## States
|
|
67
65
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Metadata",
|
|
4
4
|
"family": "metadata",
|
|
5
5
|
"subcomponent": "compact",
|
|
6
|
-
"description": "One-line channel-detail attribution — the slimmed [Standard](standard.md) head that keeps the leading 32-rung [Thumbnail](../thumbnail/thumbnail.md) but drops the primary name line, follow toggle, and subtitle (the info that is unnecessary once the channel context is already established). A leading avatar plus the identity meta-link row, with the posting time relocated to the line's trailing edge: avatar · company name · nickname (bare, no @ prefix, optional single role badge) · timestamp. The identity items keep the standard meta-row grammar (independent `<a>` links, middot separators, badge outside the link); the timestamp is plain text in `sys.color.
|
|
6
|
+
"description": "One-line channel-detail attribution — the slimmed [Standard](standard.md) head that keeps the leading 32-rung [Thumbnail](../thumbnail/thumbnail.md) but drops the primary name line, follow toggle, and subtitle (the info that is unnecessary once the channel context is already established). A leading avatar plus the identity meta-link row, with the posting time relocated to the line's trailing edge: avatar · company name · nickname (bare, no @ prefix, optional single role badge) · timestamp. The identity items keep the standard meta-row grammar (independent `<a>` links, middot separators, badge outside the link); the timestamp is plain text in `sys.color.border.boldest` so the line reads identity-first, time-last. `layoutInset: inline` — atom-shaped, pays no padding of its own; the host row owns the gutter / divider / click target.",
|
|
7
7
|
"element": "div",
|
|
8
8
|
"props": {
|
|
9
9
|
"variant": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"timestamp": {
|
|
24
24
|
"type": "string",
|
|
25
25
|
"required": true,
|
|
26
|
-
"description": "Posting time at the line's trailing edge — plain text (never a link), in `label.sm` / `sys.color.
|
|
26
|
+
"description": "Posting time at the line's trailing edge — plain text (never a link), in `label.sm` / `sys.color.border.boldest` so it recedes behind the identity links. NOT preceded by a middot: the time is separated from the identity cluster by an `inline.md` (8) gap so it reads as a distinct trailing element rather than another identity item (mirrors the Standard head's name↔time treatment). Required: the timestamp is what distinguishes a compact attribution from a bare identity row."
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"slots": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"timestamp": {
|
|
49
49
|
"required": true,
|
|
50
|
-
"description": "Trailing plain-text posting time after the identity cluster — no leading middot, separated by an `inline.md` (8) gap. `sys.typo.label.sm` / `sys.color.
|
|
50
|
+
"description": "Trailing plain-text posting time after the identity cluster — no leading middot, separated by an `inline.md` (8) gap. `sys.typo.label.sm` / `sys.color.border.boldest` — one tonal step further than the identity links.",
|
|
51
51
|
"accepts": [
|
|
52
52
|
"text"
|
|
53
53
|
]
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
},
|
|
56
56
|
"sizing": {
|
|
57
57
|
"metaTypo": "sys.typo.label.sm",
|
|
58
|
-
"metaColor": "sys.color.
|
|
58
|
+
"metaColor": "sys.color.text.subtle",
|
|
59
59
|
"timestampTypo": "sys.typo.label.sm",
|
|
60
|
-
"timestampColor": "sys.color.
|
|
61
|
-
"dotColor": "sys.color.
|
|
60
|
+
"timestampColor": "sys.color.border.boldest",
|
|
61
|
+
"dotColor": "sys.color.border.boldest",
|
|
62
62
|
"dotLineHeight": "1",
|
|
63
63
|
"dotLineHeightNote": "Same family-wide rule as the standard sub: the middot separator inherits the surrounding text's font-size but uses `line-height: 1` so its line-box never exceeds the glyph's font-size — the dot never inflates the single text line.",
|
|
64
64
|
"metaSeparatorInset": "sys.layout.inline.sm"
|
|
@@ -12,7 +12,7 @@ Three rules hold across every family member.
|
|
|
12
12
|
|
|
13
13
|
### Dot height
|
|
14
14
|
|
|
15
|
-
Every middot separator inherits the surrounding text's font-size but uses `line-height: 1`, so its line-box never exceeds the glyph's font-size — the U+00B7 glyph's natural vertical extent never inflates the host text line. The dot paints in `sys.color.
|
|
15
|
+
Every middot separator inherits the surrounding text's font-size but uses `line-height: 1`, so its line-box never exceeds the glyph's font-size — the U+00B7 glyph's natural vertical extent never inflates the host text line. The dot paints in `sys.color.border.boldest` and is always inline, never a block element.
|
|
16
16
|
|
|
17
17
|
### Bare nickname, single badge
|
|
18
18
|
|
|
@@ -26,11 +26,9 @@ import { Metadata } from '@teamblind-chorus/ui';
|
|
|
26
26
|
/>
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Follow action
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
The inline follow toggle. A middot separates it from the timestamp; the toggle paints in `sys.color.primary` at rest and `sys.color.onSurfaceVariant` when active. The dot's line-box stays inside its font-size so the row's text-line stays tight.
|
|
31
|
+
The inline follow toggle. A middot separates it from the timestamp; the toggle paints in `sys.color.background.primary` at rest and `sys.color.text.subtle` when active. The dot's line-box stays inside its font-size so the row's text-line stays tight.
|
|
34
32
|
|
|
35
33
|
```preview
|
|
36
34
|
metadata/standard/follow
|
|
@@ -47,7 +45,7 @@ import { Metadata } from '@teamblind-chorus/ui';
|
|
|
47
45
|
/>
|
|
48
46
|
```
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
## Sponsored
|
|
51
49
|
|
|
52
50
|
Feed Ad shape — `subtitle="Sponsored"` paints a plain caption-tone line under the brand name (no link affordance, no meta row). Pair with `trailing` to host the dismiss × button.
|
|
53
51
|
|
|
@@ -69,7 +67,7 @@ import { XIcon } from '@teamblind-chorus/ui/icons';
|
|
|
69
67
|
/>
|
|
70
68
|
```
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
## Role badge
|
|
73
71
|
|
|
74
72
|
A meta item in object form carries `badge` — a single presentational mark rendered after the item's link, outside the `<a>`, at the middots' 4px gap. Canonical fill: a role [Badge](../badge/badge.md) on the trailing nickname. At most one badge rides the nickname, never a stack.
|
|
75
73
|
|
|
@@ -96,8 +94,8 @@ import { Metadata, Badge } from '@teamblind-chorus/ui';
|
|
|
96
94
|
- **container** — outer flex row. `align-items: center`, `sys.layout.inline.md` (8px) gap between avatar, text column, and trailing slot.
|
|
97
95
|
- **avatar** *(optional)* — leading [Thumbnail](../thumbnail/thumbnail.md) at `size={32}`. Forwards every Thumbnail prop verbatim.
|
|
98
96
|
- **text** — two-line text column. Primary line on top, optional secondary line below. `flex: 1 1 auto`, `min-width: 0` so both lines truncate.
|
|
99
|
-
- **name** — entity name. `<a>` when `nameHref` is set, `<span>` otherwise. `sys.typo.label.sm` / `sys.color.
|
|
100
|
-
- **timestamp** *(optional)* — inline timestamp after the name. `sys.typo.label.sm` / `sys.color.
|
|
97
|
+
- **name** — entity name. `<a>` when `nameHref` is set, `<span>` otherwise. `sys.typo.label.sm` / `sys.color.text.default`. Single line; truncates.
|
|
98
|
+
- **timestamp** *(optional)* — inline timestamp after the name. `sys.typo.label.sm` / `sys.color.border.boldest`.
|
|
101
99
|
- **followAction** *(optional)* — bare-text follow toggle at the primary line's trailing edge. Preceded by a middot.
|
|
102
100
|
- **subtitle** *(optional, ad)* — plain caption-tone secondary line. Mutually exclusive with `meta`.
|
|
103
101
|
- **meta** *(optional, post)* — secondary line meta-link row. Each item is its own `<a>`; siblings separate by middot. The last item is canonically the user's nickname, displayed bare (no @ prefix). An object item may carry `badge` — a single presentational mark node rendered after the link, outside the `<a>` (canonical fill: [badge/role](../badge/badge.md) on the nickname — at most one badge rides the nickname, never a stack).
|
|
@@ -109,12 +107,12 @@ import { Metadata, Badge } from '@teamblind-chorus/ui';
|
|
|
109
107
|
|---------------|----------------|
|
|
110
108
|
| container | Flex row, `sys.layout.inline.md` (8) gap, `align-items: center` |
|
|
111
109
|
| avatar | [Thumbnail](../thumbnail/thumbnail.md) `size={32}` |
|
|
112
|
-
| name | `sys.typo.label.sm` / Semibold / `sys.color.
|
|
113
|
-
| timestamp | `sys.typo.label.sm` / `sys.color.
|
|
114
|
-
| dot separator | `·` glyph, `color: sys.color.
|
|
115
|
-
| follow | `sys.typo.label.sm` / Semibold / `sys.color.primary` (active → `sys.color.
|
|
116
|
-
| subtitle | `sys.typo.label.sm` / `sys.color.
|
|
117
|
-
| meta | `sys.typo.label.sm` / `sys.color.
|
|
110
|
+
| name | `sys.typo.label.sm` / Semibold / `sys.color.text.default`, single-line ellipsis |
|
|
111
|
+
| timestamp | `sys.typo.label.sm` / `sys.color.border.boldest` |
|
|
112
|
+
| dot separator | `·` glyph, `color: sys.color.border.boldest`, **`line-height: 1`** so its line-box equals its font-size — never inflates the text line |
|
|
113
|
+
| follow | `sys.typo.label.sm` / Semibold / `sys.color.background.primary` (active → `sys.color.text.subtle`) |
|
|
114
|
+
| subtitle | `sys.typo.label.sm` / `sys.color.text.subtle` |
|
|
115
|
+
| meta | `sys.typo.label.sm` / `sys.color.text.subtle`, links inherit; underline on hover |
|
|
118
116
|
|
|
119
117
|
## States
|
|
120
118
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"timestamp": {
|
|
25
25
|
"type": "string",
|
|
26
26
|
"optional": true,
|
|
27
|
-
"description": "Inline timestamp painted after the name on the primary line, in `label.sm` / `sys.color.
|
|
27
|
+
"description": "Inline timestamp painted after the name on the primary line, in `label.sm` / `sys.color.border.boldest` — one tonal step further than the name. Reach for it on Feed Post; omit on Feed Ad."
|
|
28
28
|
},
|
|
29
29
|
"followAction": {
|
|
30
30
|
"type": "boolean",
|
|
@@ -78,26 +78,26 @@
|
|
|
78
78
|
},
|
|
79
79
|
"name": {
|
|
80
80
|
"required": true,
|
|
81
|
-
"description": "Entity name. `sys.typo.label.sm` (12 / Semibold) / `sys.color.
|
|
81
|
+
"description": "Entity name. `sys.typo.label.sm` (12 / Semibold) / `sys.color.text.default`. Renders as `<a>` when `nameHref` is set, `<span>` otherwise. Single line; truncates with ellipsis.",
|
|
82
82
|
"accepts": [
|
|
83
83
|
"text"
|
|
84
84
|
]
|
|
85
85
|
},
|
|
86
86
|
"timestamp": {
|
|
87
87
|
"required": false,
|
|
88
|
-
"description": "Inline timestamp after the name. `sys.typo.label.sm` (12 / Semibold) / `sys.color.
|
|
88
|
+
"description": "Inline timestamp after the name. `sys.typo.label.sm` (12 / Semibold) / `sys.color.border.boldest` — one tonal step further than the name so the timestamp recedes.",
|
|
89
89
|
"accepts": [
|
|
90
90
|
"text"
|
|
91
91
|
]
|
|
92
92
|
},
|
|
93
93
|
"followAction": {
|
|
94
94
|
"required": false,
|
|
95
|
-
"description": "Inline follow toggle at the primary line's trailing edge. Bare text affordance (no chrome) — `sys.color.primary` at rest, `sys.color.
|
|
95
|
+
"description": "Inline follow toggle at the primary line's trailing edge. Bare text affordance (no chrome) — `sys.color.background.primary` at rest, `sys.color.text.subtle` when active. A `·` separator precedes it.",
|
|
96
96
|
"intrinsic": true
|
|
97
97
|
},
|
|
98
98
|
"subtitle": {
|
|
99
99
|
"required": false,
|
|
100
|
-
"description": "Secondary line plain text. `sys.typo.label.sm` / `sys.color.
|
|
100
|
+
"description": "Secondary line plain text. `sys.typo.label.sm` / `sys.color.text.subtle`. Mutually exclusive with `meta`.",
|
|
101
101
|
"accepts": [
|
|
102
102
|
"text"
|
|
103
103
|
]
|
|
@@ -121,14 +121,14 @@
|
|
|
121
121
|
"containerAlign": "center",
|
|
122
122
|
"avatarSize": 32,
|
|
123
123
|
"nameTypo": "sys.typo.label.sm",
|
|
124
|
-
"nameColor": "sys.color.
|
|
124
|
+
"nameColor": "sys.color.text.default",
|
|
125
125
|
"timestampTypo": "sys.typo.label.sm",
|
|
126
|
-
"timestampColor": "sys.color.
|
|
126
|
+
"timestampColor": "sys.color.border.boldest",
|
|
127
127
|
"subtitleTypo": "sys.typo.label.sm",
|
|
128
|
-
"subtitleColor": "sys.color.
|
|
128
|
+
"subtitleColor": "sys.color.text.subtle",
|
|
129
129
|
"metaTypo": "sys.typo.label.sm",
|
|
130
|
-
"metaColor": "sys.color.
|
|
131
|
-
"dotColor": "sys.color.
|
|
130
|
+
"metaColor": "sys.color.text.subtle",
|
|
131
|
+
"dotColor": "sys.color.border.boldest",
|
|
132
132
|
"dotLineHeight": "1",
|
|
133
133
|
"dotLineHeightNote": "The middot separator (`·`) inherits the surrounding text's font-size but uses `line-height: 1` so its line-box never exceeds the glyph's font-size — keeps the row's text-line tight even when the inherited line-height would otherwise allow extra vertical space around the middot.",
|
|
134
134
|
"primaryRowGap": "sys.layout.inline.md",
|
|
@@ -20,9 +20,7 @@ import { NavCard } from '@teamblind-chorus/ui';
|
|
|
20
20
|
<NavCard label="Cell label here" href="#" />
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### Nav (with trailing chevron)
|
|
23
|
+
## Nav
|
|
26
24
|
|
|
27
25
|
`variant="nav"` auto-renders the right-pointing chevron — the explicit drill-in form. Reach for it when the card routes into another surface (settings detail, picker, sub-flow).
|
|
28
26
|
|
|
@@ -34,7 +32,7 @@ import { NavCard } from '@teamblind-chorus/ui';
|
|
|
34
32
|
<NavCard variant="nav" label="Cell label here" href="#" />
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
## Supporting text
|
|
38
36
|
|
|
39
37
|
Two-line variant — primary label on top, supporting metadata below at `onSurfaceVariant`. Works with either variant; pair with `nav` when the drill-in is metadata-bearing.
|
|
40
38
|
|
|
@@ -51,7 +49,7 @@ import { NavCard } from '@teamblind-chorus/ui';
|
|
|
51
49
|
/>
|
|
52
50
|
```
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
## Leading icon
|
|
55
53
|
|
|
56
54
|
A leading 16 × 16 glyph at the inline padding edge. The icon vertically centres on the row's parent block — same `align-items: center` axis as the label column and trailing slot, so the glyph sits on the same midline as the label (one-line) or label + supportingText block (two-line).
|
|
57
55
|
|
|
@@ -69,7 +67,7 @@ import { BellIcon } from '@teamblind-chorus/ui/icons';
|
|
|
69
67
|
/>
|
|
70
68
|
```
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
## Leading thumbnail
|
|
73
71
|
|
|
74
72
|
A leading 32-rung [Thumbnail](../thumbnail/thumbnail.md) — used when the drill target is an entity (channel, person, brand) rather than a chrome action. The thumbnail block-centres on the row's vertical midline, same as an icon leading.
|
|
75
73
|
|
|
@@ -87,24 +85,7 @@ import { NavCard, Thumbnail } from '@teamblind-chorus/ui';
|
|
|
87
85
|
/>
|
|
88
86
|
```
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Multiple NavCards stacked vertically as a `NavCardGroup` — each card stays its own outlined affordance, separated by `sys.layout.stack.xs` (8px) gap. Use when several drill-in cards share a section but should read as discrete cards (vs a List drill-in rail — `text` rows with `nav: true` — where rows tile flush with hairline dividers).
|
|
93
|
-
|
|
94
|
-
```preview
|
|
95
|
-
nav-card/group
|
|
96
|
-
---
|
|
97
|
-
import { NavCard, NavCardGroup } from '@teamblind-chorus/ui';
|
|
98
|
-
import { BellIcon, BookmarkIcon, ProfileIcon } from '@teamblind-chorus/ui/icons';
|
|
99
|
-
|
|
100
|
-
<NavCardGroup aria-label="Account">
|
|
101
|
-
<NavCard variant="nav" label="Profile" supportingText="Display name, avatar, bio" leading={<ProfileIcon size={16} />} href="#" />
|
|
102
|
-
<NavCard variant="nav" label="Saved posts" supportingText="47 posts across 9 channels" leading={<BookmarkIcon size={16} />} href="#" />
|
|
103
|
-
<NavCard variant="nav" label="Notifications" leading={<BellIcon size={16} />} href="#" />
|
|
104
|
-
</NavCardGroup>
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Surface (opaque tier on a non-`surface` host)
|
|
88
|
+
## Surface
|
|
108
89
|
|
|
109
90
|
`appearance="surface"` paints the card with its own `sys.color.surface` fill so it reads as an opaque tier. Reach for it when the card sits on a transparent / non-`surface` host (coloured hero, tonal band, BottomSheet content slot) and the default transparent fill would let the card blend in.
|
|
110
91
|
|
|
@@ -129,9 +110,26 @@ import { BellIcon, BookmarkIcon, ProfileIcon } from '@teamblind-chorus/ui/icons'
|
|
|
129
110
|
</div>
|
|
130
111
|
```
|
|
131
112
|
|
|
113
|
+
## Group
|
|
114
|
+
|
|
115
|
+
Multiple NavCards stacked vertically as a `NavCardGroup` — each card stays its own outlined affordance, separated by `sys.layout.stack.xs` (8px) gap. Use when several drill-in cards share a section but should read as discrete cards (vs a List drill-in rail — `text` rows with `nav: true` — where rows tile flush with hairline dividers).
|
|
116
|
+
|
|
117
|
+
```preview
|
|
118
|
+
nav-card/group
|
|
119
|
+
---
|
|
120
|
+
import { NavCard, NavCardGroup } from '@teamblind-chorus/ui';
|
|
121
|
+
import { BellIcon, BookmarkIcon, ProfileIcon } from '@teamblind-chorus/ui/icons';
|
|
122
|
+
|
|
123
|
+
<NavCardGroup aria-label="Account">
|
|
124
|
+
<NavCard variant="nav" label="Profile" supportingText="Display name, avatar, bio" leading={<ProfileIcon size={16} />} href="#" />
|
|
125
|
+
<NavCard variant="nav" label="Saved posts" supportingText="47 posts across 9 channels" leading={<BookmarkIcon size={16} />} href="#" />
|
|
126
|
+
<NavCard variant="nav" label="Notifications" leading={<BellIcon size={16} />} href="#" />
|
|
127
|
+
</NavCardGroup>
|
|
128
|
+
```
|
|
129
|
+
|
|
132
130
|
## Slots
|
|
133
131
|
|
|
134
|
-
- **container** — outlined rounded box. `surface` fill, `radius.md` corners, hairline `
|
|
132
|
+
- **container** — outlined rounded box. `surface` fill, `radius.md` corners, hairline `border.default` stroke painted as inset box-shadow (never `border:`).
|
|
135
133
|
- **leading** *(optional)* — 16px icon (`currentColor`) or 32-rung [Thumbnail](../thumbnail/thumbnail.md). Block-centred on the row's vertical midline — same contract for icon and thumbnail. Omitted by default; label flushes to the inline padding edge.
|
|
136
134
|
- **labelCol** — vertical column holding label and (optional) supportingText. `min-width: 0` so both lines truncate.
|
|
137
135
|
- **label** — primary card text. 14px / Regular / `onSurface`. Single line; truncates.
|
|
@@ -142,7 +140,7 @@ import { BellIcon, BookmarkIcon, ProfileIcon } from '@teamblind-chorus/ui/icons'
|
|
|
142
140
|
|
|
143
141
|
| Slot | Token bindings |
|
|
144
142
|
|----------------|----------------|
|
|
145
|
-
| container | `surface` fill, `radius.md` corners, hairline `
|
|
143
|
+
| container | `surface` fill, `radius.md` corners, hairline `border.default` inset box-shadow, `48px` min-height, `8px` block / `16px` inline padding |
|
|
146
144
|
| leading | 16 × 16 (`sys.icon.md`) glyph in `currentColor` or 32 × 32 [Thumbnail](../thumbnail/thumbnail.md). Block-centred on the row's vertical midline (same contract for icon and thumbnail). `sys.layout.inline.md` (8px) gap to label column |
|
|
147
145
|
| labelCol | Flex column, `min-width: 0`, no inter-line margin (line-height carries the rhythm) |
|
|
148
146
|
| label | `sys.typo.body.sm` (14 / Regular) / `onSurface` |
|
|
@@ -172,7 +170,7 @@ A single rung. Min-height 48 (touch-target floor); consumers cannot shrink or gr
|
|
|
172
170
|
|
|
173
171
|
## Focus indicator
|
|
174
172
|
|
|
175
|
-
Outward
|
|
173
|
+
Outward single ring painted on the container's outer edge via an `::after` overlay (rest stroke sits on `::before`). Trigger: `:focus-visible`. NavCard sits as its own bounded surface with margin to siblings, so an outward ring reads cleanly — see [Focus ring composition](../../DESIGN.md#focus-ring-composition).
|
|
176
174
|
|
|
177
175
|
## Behavior
|
|
178
176
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../spec.schema.json",
|
|
3
3
|
"name": "NavCard",
|
|
4
4
|
"family": "nav-card",
|
|
5
|
-
"description": "Outlined, rounded single-row card. Single label (optional supporting line) over a `surface`-toned box with a hairline `
|
|
5
|
+
"description": "Outlined, rounded single-row card. Single label (optional supporting line) over a `surface`-toned box with a hairline `border.default` inset-shadow stroke and `radius.md` corners. Two variants select the trailing affordance: `default` ships no trailing icon (bare labelled card — settings entry, scope tile, informational drill-target), `nav` auto-renders the right-pointing chevron for explicit drill-in semantics. Whole card is the click target — keyboard focus, hover overlay, and tap commit all sit on the card. Reach for it when one row needs to read as its own discrete affordance rather than as one entry in a [List](../list/list.md) drill-in stack (`text` rows with `nav: true`).",
|
|
6
6
|
"element": "button",
|
|
7
7
|
"props": {
|
|
8
8
|
"variant": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"type": "enum",
|
|
36
36
|
"values": ["default", "surface"],
|
|
37
37
|
"default": "default",
|
|
38
|
-
"description": "Container fill. `default` is transparent — the card's identity is the outlined chrome (hairline + radius + label + chevron) and the host surface tone reads through. `surface` paints `sys.color.surface` so the card reads as its own opaque tier; reach for it when the card sits on a transparent / non-`surface` host (between bare-surface sections, on a tonal band the card needs to break out of)."
|
|
38
|
+
"description": "Container fill. `default` is transparent — the card's identity is the outlined chrome (hairline + radius + label + chevron) and the host surface tone reads through. `surface` paints `sys.color.surface.default` so the card reads as its own opaque tier; reach for it when the card sits on a transparent / non-`surface` host (between bare-surface sections, on a tonal band the card needs to break out of)."
|
|
39
39
|
},
|
|
40
40
|
"href": {
|
|
41
41
|
"type": "string",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"slots": {
|
|
61
61
|
"container": {
|
|
62
62
|
"required": true,
|
|
63
|
-
"description": "Outlined rounded box. Transparent fill by default (host tone reads through) — `surface` fill via `appearance=\"surface\"`. `radius.md` corners, hairline `
|
|
63
|
+
"description": "Outlined rounded box. Transparent fill by default (host tone reads through) — `surface` fill via `appearance=\"surface\"`. `radius.md` corners, hairline `border.default` stroke painted as inset box-shadow (never `border:`). Whole container is the interactive target.",
|
|
64
64
|
"intrinsic": true
|
|
65
65
|
},
|
|
66
66
|
"leading": {
|
|
@@ -95,17 +95,17 @@
|
|
|
95
95
|
"paddingBlock": "sys.layout.container.xs",
|
|
96
96
|
"paddingInline": "sys.layout.container.md",
|
|
97
97
|
"outlineWidth": "sys.borderWidth.hairline",
|
|
98
|
-
"outlineColor": "sys.color.
|
|
98
|
+
"outlineColor": "sys.color.border.default",
|
|
99
99
|
"leadingGap": "sys.layout.inline.md",
|
|
100
100
|
"trailingGap": "sys.layout.inline.md",
|
|
101
101
|
"leadingIconSize": "sys.icon.md",
|
|
102
102
|
"labelTypo": "sys.typo.body.sm",
|
|
103
|
-
"labelColor": "sys.color.
|
|
103
|
+
"labelColor": "sys.color.text.default",
|
|
104
104
|
"supportingTypo": "sys.typo.label.sm",
|
|
105
|
-
"supportingColor": "sys.color.
|
|
105
|
+
"supportingColor": "sys.color.text.subtle",
|
|
106
106
|
"supportingOffset": "0",
|
|
107
107
|
"trailingIconSize": "sys.icon.md",
|
|
108
|
-
"trailingIconColor": "sys.color.
|
|
108
|
+
"trailingIconColor": "sys.color.text.subtle",
|
|
109
109
|
"groupGap": "sys.layout.stack.xs"
|
|
110
110
|
},
|
|
111
111
|
"appearances": {
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"note": "No fill — the host surface tone reads through. The canonical NavCard: its identity is the outlined chrome (hairline + radius + label + chevron), not a fill. Hover / pressed paint as label-tone overlays mixed on the transparent base so the host tone keeps reading through underneath the state paint."
|
|
115
115
|
},
|
|
116
116
|
"surface": {
|
|
117
|
-
"background": "sys.color.surface",
|
|
117
|
+
"background": "sys.color.surface.default",
|
|
118
118
|
"note": "Opaque `surface` fill. Reach for it when the card sits on a transparent / non-`surface` host (a `surfaceContainerLow` tonal band, a coloured hero, a BottomSheet's content slot) and should read as its own opaque tier rather than blending into the host. Outline, label, chevron, and state overlays are unchanged."
|
|
119
119
|
}
|
|
120
120
|
},
|
|
@@ -126,21 +126,30 @@
|
|
|
126
126
|
"pressed": {
|
|
127
127
|
"overlay": { "color": "label", "opacity": "sys.state.pressed" }
|
|
128
128
|
},
|
|
129
|
+
"focused": {
|
|
130
|
+
"focusRing": {
|
|
131
|
+
"composition": "outward",
|
|
132
|
+
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
133
|
+
"innerCounterRing": { "width": "sys.borderWidth.hairline", "color": "sys.color.border.focused" },
|
|
134
|
+
"outerRing": { "width": "sys.borderWidth.thin", "color": "sys.color.border.focused" }
|
|
135
|
+
},
|
|
136
|
+
"note": "Keyboard-focus (:focus-visible) visual — a single outward ring on the card's outer edge, with no state-overlay tint (the ring alone carries focus here). Mirrors the `focusIndicator` block for spec-only renderers. Composes over the lifecycle state the card is in."
|
|
137
|
+
},
|
|
129
138
|
"disabled": {
|
|
130
|
-
"
|
|
131
|
-
"
|
|
139
|
+
"text": "sys.color.text.disabled",
|
|
140
|
+
"icon": "sys.color.icon.disabled",
|
|
141
|
+
"pointerEvents": "none",
|
|
142
|
+
"note": "Explicit disabled (no opacity): card text to text.disabled, icons to icon.disabled. Border stays border.default."
|
|
132
143
|
}
|
|
133
144
|
},
|
|
134
145
|
"focusIndicator": {
|
|
135
|
-
"description": "Keyboard-focus visual painted as a
|
|
146
|
+
"description": "Keyboard-focus visual painted as a single ring on the card's outer edge. Composes over whichever lifecycle state the card is in.",
|
|
136
147
|
"composition": "outward",
|
|
137
148
|
"compositionReason": "NavCard sits as its own bounded surface with margin to siblings; an outward ring reads cleanly without colliding with a neighbouring row's stroke.",
|
|
138
149
|
"ring": {
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"insetColor": "sys.color.focusInset",
|
|
143
|
-
"implementation": "outset box-shadow on the container's `::after` overlay; the rest stroke stays painted on `::before` so the two layers don't fight."
|
|
150
|
+
"width": "sys.borderWidth.hairline",
|
|
151
|
+
"color": "sys.color.border.focused",
|
|
152
|
+
"implementation": "outset box-shadow on the container's `::after` overlay; the rest stroke stays painted on `::before` so the rest stroke and focus ring don't fight."
|
|
144
153
|
},
|
|
145
154
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
146
155
|
},
|
|
@@ -33,9 +33,7 @@ import { NavList } from '@teamblind-chorus/ui';
|
|
|
33
33
|
/>
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
### With header action
|
|
36
|
+
## Header action
|
|
39
37
|
|
|
40
38
|
Extends the header with a trailing `accent` Text Button when the screen has a broader index page or "Manage" route.
|
|
41
39
|
|
|
@@ -48,11 +46,7 @@ import { NavList } from '@teamblind-chorus/ui';
|
|
|
48
46
|
label="Settings"
|
|
49
47
|
headerAction={{ label: 'Manage', href: '/settings/manage' }}
|
|
50
48
|
items={[
|
|
51
|
-
{ value: 'account',
|
|
52
|
-
{ value: 'notifications',label: 'Notifications', href: '/settings/notifications' },
|
|
53
|
-
{ value: 'privacy', label: 'Privacy', href: '/settings/privacy' },
|
|
54
|
-
{ value: 'appearance', label: 'Appearance', href: '/settings/appearance' },
|
|
55
|
-
{ value: 'language', label: 'Language', href: '/settings/language' },
|
|
49
|
+
{ value: 'account', label: 'Account', href: '/settings/account' },
|
|
56
50
|
]}
|
|
57
51
|
/>
|
|
58
52
|
```
|
|
@@ -55,17 +55,17 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"sizing": {
|
|
58
|
-
"containerFill": "sys.color.surface",
|
|
58
|
+
"containerFill": "sys.color.surface.default",
|
|
59
59
|
"containerPaddingBlock": "sys.layout.container.lg",
|
|
60
60
|
"containerPaddingInline": "sys.layout.container.md",
|
|
61
61
|
"headerToListGap": "sys.layout.stack.md",
|
|
62
62
|
"labelTypo": "sys.typo.heading.md",
|
|
63
|
-
"labelColor": "sys.color.
|
|
63
|
+
"labelColor": "sys.color.text.default",
|
|
64
64
|
"headerActionRendersAs": "Button variant='text' size='xsmall' appearance='accent' — link-affordance accent rule.",
|
|
65
65
|
"rowComposition": "list/entry label-only row (no thumbnail) — label.md primary, optional label.sm description line, family-default min-height (ref.space.600 = 48), leading column collapsed (0 leading-to-text gap), trailing slot filled with a default Icon Button (variant='icon', size='medium', icon=<ChevronRightIcon />).",
|
|
66
66
|
"rowInlinePaddingNote": "Each row keeps the list/entry native sys.layout.container.md inline padding (the tap target reaches the surface edge) and adds margin-inline: calc(-1 * sys.layout.container.md) so the visible label lines up with the section's content rail (aligned with the header label at 16 from the surface).",
|
|
67
67
|
"dividerWidth": "sys.borderWidth.hairline",
|
|
68
|
-
"dividerColor": "sys.color.
|
|
68
|
+
"dividerColor": "sys.color.border.default",
|
|
69
69
|
"dividerInset": "list/entry default — 16 / 16 inset from both row edges. Label-only rows always take the default inset regardless of `size` (no avatar column to anchor against)."
|
|
70
70
|
},
|
|
71
71
|
"rowProps": {
|