@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
|
@@ -63,16 +63,16 @@
|
|
|
63
63
|
},
|
|
64
64
|
"selectionStates": {
|
|
65
65
|
"unselected": {
|
|
66
|
-
"background": "sys.color.primary",
|
|
67
|
-
"label": "sys.color.
|
|
66
|
+
"background": "sys.color.background.primary",
|
|
67
|
+
"label": "sys.color.text.onFill",
|
|
68
68
|
"border": null
|
|
69
69
|
},
|
|
70
70
|
"selected": {
|
|
71
71
|
"background": "transparent",
|
|
72
|
-
"label": "sys.color.
|
|
72
|
+
"label": "sys.color.text.default",
|
|
73
73
|
"border": {
|
|
74
74
|
"width": "sys.borderWidth.hairline",
|
|
75
|
-
"color": "sys.color.
|
|
75
|
+
"color": "sys.color.border.default"
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
},
|
|
@@ -102,11 +102,11 @@
|
|
|
102
102
|
"layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
|
|
103
103
|
"innerCounterRing": {
|
|
104
104
|
"width": "sys.borderWidth.hairline",
|
|
105
|
-
"color": "sys.color.
|
|
105
|
+
"color": "sys.color.border.focused"
|
|
106
106
|
},
|
|
107
107
|
"outerRing": {
|
|
108
108
|
"width": "sys.borderWidth.thin",
|
|
109
|
-
"color": "sys.color.
|
|
109
|
+
"color": "sys.color.border.focused"
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
112
|
"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 button is in; never via plain mouse click."
|
|
@@ -121,21 +121,19 @@
|
|
|
121
121
|
"focusIndicator": {
|
|
122
122
|
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
|
|
123
123
|
"composition": "outward",
|
|
124
|
-
"compositionReason": "Action affordance with breathing room around it; the
|
|
124
|
+
"compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
|
|
125
125
|
"overlay": {
|
|
126
126
|
"color": "label",
|
|
127
127
|
"opacity": "sys.state.focus"
|
|
128
128
|
},
|
|
129
129
|
"ring": {
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
133
|
-
"insetColor": "sys.color.focusInset"
|
|
130
|
+
"width": "sys.borderWidth.hairline",
|
|
131
|
+
"color": "sys.color.border.focused"
|
|
134
132
|
},
|
|
135
133
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
136
134
|
},
|
|
137
135
|
"forbidden": [
|
|
138
|
-
"active state painted with sys.color.primary fill — active is transparent + hairline outline (the active state recedes, not asserts)",
|
|
136
|
+
"active state painted with sys.color.background.primary fill — active is transparent + hairline outline (the active state recedes, not asserts)",
|
|
139
137
|
"active state painted with any opaque surface fill (surface, surfaceContainer, surfaceContainerHigh) — the committed form is transparent so the host surface shows through; do not re-bind to a tier'd fill",
|
|
140
138
|
"rest state without an explicit `active={false}` — toggle is a binary contract, never tristate",
|
|
141
139
|
"manual width override that breaks the full-card stretch when used inside ProfileCarousel.followAction"
|
|
@@ -22,9 +22,7 @@ import { Button } from '@teamblind-chorus/ui';
|
|
|
22
22
|
</Button>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
### Accent
|
|
25
|
+
## Accent
|
|
28
26
|
|
|
29
27
|
Brand-blue fill, `onPrimary` label — the single-commit form. Used when the Toolbar Button IS the surface's commit affordance.
|
|
30
28
|
|
|
@@ -38,7 +36,7 @@ import { Button } from '@teamblind-chorus/ui';
|
|
|
38
36
|
</Button>
|
|
39
37
|
```
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
## Inverse
|
|
42
40
|
|
|
43
41
|
Mirror for inverse hosts (snackbars, coach-mark surfaces). Geometry identical; colour pair flips.
|
|
44
42
|
|
|
@@ -52,7 +50,7 @@ import { Button } from '@teamblind-chorus/ui';
|
|
|
52
50
|
</Button>
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
## Leading icon
|
|
56
54
|
|
|
57
55
|
Context glyph before the label — tag for *Filters*, calendar for *Pick date*.
|
|
58
56
|
|
|
@@ -70,7 +68,7 @@ import { PlusIcon } from '@teamblind-chorus/ui/icons';
|
|
|
70
68
|
</Button>
|
|
71
69
|
```
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
## Trailing icon
|
|
74
72
|
|
|
75
73
|
Directional/destination glyph — chevron-down to open a menu, "×" to clear. Unlike standard [Button](./button.md), Toolbar Button carries trailing icons because its role is often *trigger* rather than commit.
|
|
76
74
|
|
|
@@ -88,7 +86,7 @@ import { CheckedIcon } from '@teamblind-chorus/ui/icons';
|
|
|
88
86
|
</Button>
|
|
89
87
|
```
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
## Icon only
|
|
92
90
|
|
|
93
91
|
Glyph-only 32×32 square. Requires `aria-label`. When the label slot is absent, inline padding drops to `sys.layout.container.xs` (8) so the glyph centers.
|
|
94
92
|
|
|
@@ -105,7 +103,22 @@ import { PlusIcon } from '@teamblind-chorus/ui/icons';
|
|
|
105
103
|
/>
|
|
106
104
|
```
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
## Focus ring
|
|
107
|
+
|
|
108
|
+
Hairline stroke is kept underneath the standard ring.
|
|
109
|
+
|
|
110
|
+
```preview
|
|
111
|
+
button/toolbar/focused
|
|
112
|
+
---
|
|
113
|
+
import { Button } from '@teamblind-chorus/ui';
|
|
114
|
+
import { PlusIcon } from '@teamblind-chorus/ui/icons';
|
|
115
|
+
|
|
116
|
+
<Button variant="toolbar" state="focused" leadingIcon={<PlusIcon />}>
|
|
117
|
+
Add row
|
|
118
|
+
</Button>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Group
|
|
109
122
|
|
|
110
123
|
Adjacent Toolbar Buttons share `4px` gap (`sys.layout.inline.sm`) with Filter chips. Mix freely.
|
|
111
124
|
|
|
@@ -128,21 +141,6 @@ import { PlusIcon, CheckedIcon } from '@teamblind-chorus/ui/icons';
|
|
|
128
141
|
</div>
|
|
129
142
|
```
|
|
130
143
|
|
|
131
|
-
### Focus indicator
|
|
132
|
-
|
|
133
|
-
Hairline stroke is kept underneath the standard ring.
|
|
134
|
-
|
|
135
|
-
```preview
|
|
136
|
-
button/toolbar/focused
|
|
137
|
-
---
|
|
138
|
-
import { Button } from '@teamblind-chorus/ui';
|
|
139
|
-
import { PlusIcon } from '@teamblind-chorus/ui/icons';
|
|
140
|
-
|
|
141
|
-
<Button variant="toolbar" state="focused" leadingIcon={<PlusIcon />}>
|
|
142
|
-
Add row
|
|
143
|
-
</Button>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
144
|
## Slots
|
|
147
145
|
|
|
148
146
|
- **label** — accessible name. Required, single line.
|
|
@@ -155,9 +153,9 @@ Only the container ↔ label colour pair flips; geometry identical. For inline d
|
|
|
155
153
|
|
|
156
154
|
| Appearance | Background | Border | Label / icon | When to reach for it |
|
|
157
155
|
|-------------|-------------------------------------|--------------------------------------------------|------------------------------------|----------------------|
|
|
158
|
-
| `default` | `sys.color.surfaceContainerHigh` | 1px `sys.color.
|
|
159
|
-
| `accent` | `sys.color.primary` | none | `sys.color.
|
|
160
|
-
| `inverse` | `sys.color.
|
|
156
|
+
| `default` | `sys.color.surfaceContainerHigh` | 1px `sys.color.border.default` | `sys.color.text.default` | Quiet inline action — toolbar opener, "Edit" beside a row title. |
|
|
157
|
+
| `accent` | `sys.color.background.primary` | none | `sys.color.text.onFill` | The single commit — a [Page](../navigation-bar/sub.md) bar's "Save". Never two in a row. |
|
|
158
|
+
| `inverse` | `sys.color.background.inverse` | none | `sys.color.text.inverse` | Inside an inverse host (Toast, coach-mark). |
|
|
161
159
|
|
|
162
160
|
## Sizes
|
|
163
161
|
|
|
@@ -61,23 +61,23 @@
|
|
|
61
61
|
},
|
|
62
62
|
"appearances": {
|
|
63
63
|
"default": {
|
|
64
|
-
"background": "sys.color.
|
|
65
|
-
"label": "sys.color.
|
|
64
|
+
"background": "sys.color.surface.default",
|
|
65
|
+
"label": "sys.color.text.default",
|
|
66
66
|
"border": {
|
|
67
67
|
"width": "sys.borderWidth.hairline",
|
|
68
|
-
"color": "sys.color.
|
|
68
|
+
"color": "sys.color.border.default"
|
|
69
69
|
},
|
|
70
70
|
"note": "The canonical Toolbar Button chrome (mirrors Filter chip's unselected state). The chip-shape signals an inline action that lives within a row of peers (a toolbar, a card footer, a Page bar where the affordance is one of several actions the user might pick)."
|
|
71
71
|
},
|
|
72
72
|
"accent": {
|
|
73
|
-
"background": "sys.color.primary",
|
|
74
|
-
"label": "sys.color.
|
|
73
|
+
"background": "sys.color.background.primary",
|
|
74
|
+
"label": "sys.color.text.onFill",
|
|
75
75
|
"border": null,
|
|
76
76
|
"note": "Brand-blue commit chip — reach for it when the Toolbar Button IS the surface's commit affordance (a Page bar's 'Save', a sheet's 'Confirm', a flow step's 'Done'). The accent fill claims commit-rank attention while keeping the 32px Toolbar Button footprint, so the bar reads at one density even though the action is the screen's most important. Pair sparingly — never two accent Toolbar Buttons in the same row."
|
|
77
77
|
},
|
|
78
78
|
"inverse": {
|
|
79
|
-
"background": "sys.color.
|
|
80
|
-
"label": "sys.color.
|
|
79
|
+
"background": "sys.color.background.inverse",
|
|
80
|
+
"label": "sys.color.text.inverse",
|
|
81
81
|
"border": null,
|
|
82
82
|
"note": "Inverse-toned chip for use inside an inverse host (snackbars, coach-mark surfaces, dark coach overlays). Same chrome geometry as `default`; the colour pair flips to the inverse cluster so the chip reads against the host's `inverseSurface` fill without a per-host tweak."
|
|
83
83
|
}
|
|
@@ -88,16 +88,14 @@
|
|
|
88
88
|
"focusIndicator": {
|
|
89
89
|
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. Visual ring is delegated to Filter chip's composition; this block restates the contract for external readers.",
|
|
90
90
|
"composition": "outward",
|
|
91
|
-
"compositionReason": "Action affordance with breathing room around it; the
|
|
91
|
+
"compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding toolbar row.",
|
|
92
92
|
"overlay": {
|
|
93
93
|
"color": "label",
|
|
94
94
|
"opacity": "sys.state.focus"
|
|
95
95
|
},
|
|
96
96
|
"ring": {
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"insetWidth": "sys.borderWidth.hairline",
|
|
100
|
-
"insetColor": "sys.color.focusInset"
|
|
97
|
+
"width": "sys.borderWidth.hairline",
|
|
98
|
+
"color": "sys.color.border.focused"
|
|
101
99
|
},
|
|
102
100
|
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
103
101
|
},
|
|
@@ -19,7 +19,7 @@ Both subs share the same pager geometry — a `ref.space.500` (40px) trailing pe
|
|
|
19
19
|
|
|
20
20
|
Every Carousel paints a [Header](../header/header.md) (`size="large"`) at the top:
|
|
21
21
|
|
|
22
|
-
- **Label** *(required)* — `sys.typo.heading.md` / Semibold / `sys.color.
|
|
22
|
+
- **Label** *(required)* — `sys.typo.heading.md` / Semibold / `sys.color.text.default`. Leading position.
|
|
23
23
|
- **headerAction** *(optional)* — trailing [Text Button](../button/text.md) (`size={'xsmall'}`, `appearance={'accent'}`) per the link-affordance rule. Extends the header when there's an index page to route to.
|
|
24
24
|
|
|
25
25
|
Carousel forwards `label` and `headerAction` to [Header](../header/header.md) verbatim; the header anatomy lives in Header's spec. Other hosts (in-sheet sub-sections, bounded cards, [SuggestionList](../suggestion-list/suggestion-list.md)) reach for `<Header />` directly — Carousel is the labelled-region host, Header the leading-row primitive it composes.
|
|
@@ -55,9 +55,7 @@ import { Carousel, PostCarousel } from '@teamblind-chorus/ui';
|
|
|
55
55
|
</Carousel>
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
### With header action
|
|
58
|
+
## Header action
|
|
61
59
|
|
|
62
60
|
Extend the header with a trailing `accent` Text Button when there's an index page to route to. Lifts the `headerAction` prop on the `<Carousel>` wrapper.
|
|
63
61
|
|
|
@@ -69,17 +67,15 @@ import { Carousel, PostCarousel } from '@teamblind-chorus/ui';
|
|
|
69
67
|
<Carousel label="Trending right now" headerAction={{ label: 'See all', href: '#' }}>
|
|
70
68
|
<PostCarousel
|
|
71
69
|
items={[
|
|
72
|
-
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Engineering',
|
|
73
|
-
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Compensation', verified: true, followAction: true, title: 'Equity refresh negotiations — what actually moves', body: 'A read on the conversations that get an actual refresh on the calendar versus the ones that get a polite no.', mention: '@career', views: '9K' },
|
|
74
|
-
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Plant People', verified: false, followAction: true, title: 'Monstera dropping aerial roots — repot or train?', body: 'Two-year-old monstera, roots crawling out of the drainage holes. Light and watering are dialed in.', mention: '@plant-parents', views: '3K' },
|
|
70
|
+
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Engineering', verified: true, followAction: true, title: 'The migration that finally landed after three quarters', body: 'Internal postmortem turned editorial — the scaffolding that held the rewrite together when the timeline did not.', mention: '@infra-talk', views: '14K' },
|
|
75
71
|
]}
|
|
76
72
|
/>
|
|
77
73
|
</Carousel>
|
|
78
74
|
```
|
|
79
75
|
|
|
80
|
-
|
|
76
|
+
## Editorial card
|
|
81
77
|
|
|
82
|
-
|
|
78
|
+
The card drops `verified` and `followAction` — its header collapses to avatar + channel name. Reach for it on editorial collections where the card is informational only (round-ups, archives, *what we're reading*); the surface should not invite a per-card commit.
|
|
83
79
|
|
|
84
80
|
```preview
|
|
85
81
|
carousel/post-editorial
|
|
@@ -89,9 +85,7 @@ import { Carousel, PostCarousel } from '@teamblind-chorus/ui';
|
|
|
89
85
|
<Carousel label="Editor picks">
|
|
90
86
|
<PostCarousel
|
|
91
87
|
items={[
|
|
92
|
-
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Career',
|
|
93
|
-
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Compensation', title: 'Equity refresh negotiations — what actually moves', body: 'A read on the conversations that get an actual refresh on the calendar versus the ones that get a polite no.', views: '9K' },
|
|
94
|
-
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Engineering', title: 'The migration that finally landed after three quarters', body: 'Internal postmortem turned editorial — the scaffolding that held the rewrite together when the timeline did not.', views: '14K' },
|
|
88
|
+
{ avatar: { src: '/placeholder.png', alt: 'Channel' }, channel: 'Career', title: 'The quiet math of staying versus leaving', body: 'Salary checks, offer evaluations, and the long thread that runs longer than any single conversation can.', views: '18K' },
|
|
95
89
|
]}
|
|
96
90
|
/>
|
|
97
91
|
</Carousel>
|
|
@@ -103,12 +97,12 @@ import { Carousel, PostCarousel } from '@teamblind-chorus/ui';
|
|
|
103
97
|
- **pager** — horizontal scroll-snap track. `scroll-snap-type: x mandatory`; native scrollbar hidden.
|
|
104
98
|
- **card** — one compact post card per page; outline-only surface.
|
|
105
99
|
- **avatar** — [Thumbnail](../thumbnail/thumbnail.md) `size={40}`, every prop (`src`, `alt`, `updateDot`, `logoBadge`) forwarded verbatim.
|
|
106
|
-
- **verified** *(optional)* — `VerifiedFillIcon` (`sys.icon.md`, `sys.color.primary`) to the LEFT of the channel name so the trust signal reads first.
|
|
107
|
-
- **channel** — channel / author name. `sys.typo.label.md` / Semibold / `sys.color.
|
|
108
|
-
- **followAction** *(optional)* — trailing [Text Button](../button/text.md) (`xsmall`). `accent` inactive (link-affordance) → `default` active (recedes). Same trailing-Text-Button shape as [List/entry](../list/entry.md)'s [attribution-row case](../list/entry.md#
|
|
109
|
-
- **title** — `sys.typo.label.md` / Semibold / `sys.color.
|
|
110
|
-
- **body** — `sys.typo.body.sm` / `sys.color.
|
|
111
|
-
- **mention** *(optional)* — `sys.typo.body.sm` / `sys.color.primary` (not italic).
|
|
100
|
+
- **verified** *(optional)* — `VerifiedFillIcon` (`sys.icon.md`, `sys.color.background.primary`) to the LEFT of the channel name so the trust signal reads first.
|
|
101
|
+
- **channel** — channel / author name. `sys.typo.label.md` / Semibold / `sys.color.text.default`.
|
|
102
|
+
- **followAction** *(optional)* — trailing [Text Button](../button/text.md) (`xsmall`). `accent` inactive (link-affordance) → `default` active (recedes). Same trailing-Text-Button shape as [List/entry](../list/entry.md)'s [attribution-row case](../list/entry.md#trailing-text-button) — see the Behavior note below.
|
|
103
|
+
- **title** — `sys.typo.label.md` / Semibold / `sys.color.text.default`. Single line, truncates.
|
|
104
|
+
- **body** — `sys.typo.body.sm` / `sys.color.text.subtle`. Three-line clamp.
|
|
105
|
+
- **mention** *(optional)* — `sys.typo.body.sm` / `sys.color.background.primary` (not italic).
|
|
112
106
|
- **footer** — leading 'See more' [Text Button](../button/text.md) (`xsmall` / `secondary`) + trailing view count (`EyeIcon` + count `<span>`).
|
|
113
107
|
- **pagination** — [Pagination](../pagination/pagination.md) component, one dot per card (`count` = card count, `activeIndex` from scroll position). Decorative — dot tokens and the `aria-hidden` contract live on its spec; the carousel centers the intrinsic-width row (`align-self: center`).
|
|
114
108
|
|
|
@@ -118,11 +112,11 @@ import { Carousel, PostCarousel } from '@teamblind-chorus/ui';
|
|
|
118
112
|
|-------------------|----------------|
|
|
119
113
|
| container | No fill / padding — surrounding [Carousel](../carousel/carousel.md) provides chrome; `sys.layout.stack.md` pager→dots gap |
|
|
120
114
|
| pager | `gap: sys.layout.inline.md`, `padding-left: sys.layout.container.md` + `scroll-padding-left: sys.layout.container.md` (the 16 rail; full-bleed host → trailing edge reached intrinsically, no negative margin), `scroll-snap-type: x mandatory`, `scrollbar-width: none` |
|
|
121
|
-
| card | `flex: 0 0 calc(100% - sys.layout.inline.md - ref.space.500)`; `sys.color.surface` fill, `sys.radius.md`, `sys.borderWidth.hairline sys.color.
|
|
115
|
+
| card | `flex: 0 0 calc(100% - sys.layout.inline.md - ref.space.500)`; `sys.color.surface` fill, `sys.radius.md`, `sys.borderWidth.hairline sys.color.border.default` outline (inset box-shadow), `sys.layout.container.md` padding, `sys.layout.stack.sm` between blocks, `scroll-snap-align: start` |
|
|
122
116
|
| header | Row: avatar + verified mark + name + spacer + follow action; `align-items: center`; `sys.layout.inline.md` gap |
|
|
123
|
-
| avatar / verified | [Thumbnail](../thumbnail/thumbnail.md) `size={40}` delegated verbatim · `VerifiedFillIcon` at `sys.icon.md` / `sys.color.primary` (resolves to `ref.palette.blue.500`), leading of the channel name |
|
|
124
|
-
| channel / title | `sys.typo.label.md`, `sys.color.
|
|
125
|
-
| body / mention | `sys.typo.body.sm` / `sys.color.
|
|
117
|
+
| avatar / verified | [Thumbnail](../thumbnail/thumbnail.md) `size={40}` delegated verbatim · `VerifiedFillIcon` at `sys.icon.md` / `sys.color.background.primary` (resolves to `ref.palette.blue.500`), leading of the channel name |
|
|
118
|
+
| channel / title | `sys.typo.label.md`, `sys.color.text.default`, single-line truncate |
|
|
119
|
+
| body / mention | `sys.typo.body.sm` / `sys.color.text.subtle` (three-line clamp) · mention in `sys.color.background.primary` |
|
|
126
120
|
| followAction | [Text Button](../button/text.md) `size={'xsmall'}`, `appearance={'accent'}` inactive → `appearance={'default'}` active; state tokens delegate to Text Button |
|
|
127
121
|
| footer | Row: leading 'See more' Text Button (`xsmall` / `secondary`) + trailing view count `<span>`; `justify-content: space-between` |
|
|
128
122
|
| pagination dot | [Pagination](../pagination/pagination.md) component delegated verbatim — 6 × 6 dots, active/inactive colors, and row gap bind on its spec |
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"card": {
|
|
28
28
|
"required": true,
|
|
29
|
-
"description": "One compact post card per page. `flex: 0 0 calc(100% - sys.layout.inline.md - ref.space.500)` so a 40px peek of the next card always surfaces at the trailing edge. `scroll-snap-align: start`. Inner padding `sys.layout.container.md`, `sys.color.surface` fill, `sys.radius.md` corner, `sys.borderWidth.hairline` outline in `sys.color.
|
|
29
|
+
"description": "One compact post card per page. `flex: 0 0 calc(100% - sys.layout.inline.md - ref.space.500)` so a 40px peek of the next card always surfaces at the trailing edge. `scroll-snap-align: start`. Inner padding `sys.layout.container.md`, `sys.color.surface.default` fill, `sys.radius.md` corner, `sys.borderWidth.hairline` outline in `sys.color.border.default`.",
|
|
30
30
|
"intrinsic": true
|
|
31
31
|
},
|
|
32
32
|
"header": {
|
|
33
33
|
"required": true,
|
|
34
|
-
"description": "Top row of the card: avatar (Thumbnail size 40) + channel name + optional VerifiedIcon (size sys.icon.md, sys.color.primary) + trailing follow affordance. Rendered by the **shared `EntryRow` atom** — the same component [List/entry](../list/entry.md) rows render — wrapped in a carousel-specific `chorus-post-carousel__card-header`. See `behavior.headerReusesEntryCombo`.",
|
|
34
|
+
"description": "Top row of the card: avatar (Thumbnail size 40) + channel name + optional VerifiedIcon (size sys.icon.md, sys.color.background.primary) + trailing follow affordance. Rendered by the **shared `EntryRow` atom** — the same component [List/entry](../list/entry.md) rows render — wrapped in a carousel-specific `chorus-post-carousel__card-header`. See `behavior.headerReusesEntryCombo`.",
|
|
35
35
|
"intrinsic": true
|
|
36
36
|
},
|
|
37
37
|
"avatar": {
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
},
|
|
45
45
|
"verified": {
|
|
46
46
|
"required": false,
|
|
47
|
-
"description": "Inline `VerifiedFillIcon` at `sys.icon.md`, painted in `sys.color.primary` (resolves to `ref.palette.blue.500`). Sits to the **LEFT** of the channel name so the reader's eye lands on the trust signal first, then the name. Decorative — pair with a textual affordance elsewhere if the verified status itself is meaningful.",
|
|
47
|
+
"description": "Inline `VerifiedFillIcon` at `sys.icon.md`, painted in `sys.color.background.primary` (resolves to `ref.palette.blue.500`). Sits to the **LEFT** of the channel name so the reader's eye lands on the trust signal first, then the name. Decorative — pair with a textual affordance elsewhere if the verified status itself is meaningful.",
|
|
48
48
|
"accepts": [
|
|
49
49
|
"icon"
|
|
50
50
|
],
|
|
51
51
|
"defaultIcon": "VerifiedFillIcon",
|
|
52
|
-
"defaultIconColor": "sys.color.
|
|
52
|
+
"defaultIconColor": "sys.color.icon.accent.blue.default"
|
|
53
53
|
},
|
|
54
54
|
"channel": {
|
|
55
55
|
"required": true,
|
|
56
|
-
"description": "Channel / author name. `sys.typo.label.md` / Semibold / `sys.color.
|
|
56
|
+
"description": "Channel / author name. `sys.typo.label.md` / Semibold / `sys.color.text.default`. Single line; truncates. Sits to the right of the optional `verified` mark inside the channel-row sub-container.",
|
|
57
57
|
"accepts": [
|
|
58
58
|
"text"
|
|
59
59
|
]
|
|
@@ -68,28 +68,28 @@
|
|
|
68
68
|
},
|
|
69
69
|
"title": {
|
|
70
70
|
"required": true,
|
|
71
|
-
"description": "Post title. `sys.typo.label.md` / Semibold / `sys.color.
|
|
71
|
+
"description": "Post title. `sys.typo.label.md` / Semibold / `sys.color.text.default`. One line; truncates with ellipsis.",
|
|
72
72
|
"accepts": [
|
|
73
73
|
"text"
|
|
74
74
|
]
|
|
75
75
|
},
|
|
76
76
|
"body": {
|
|
77
77
|
"required": true,
|
|
78
|
-
"description": "Post excerpt. `sys.typo.body.sm` / Regular / `sys.color.
|
|
78
|
+
"description": "Post excerpt. `sys.typo.body.sm` / Regular / `sys.color.text.subtle`. Three-line clamp with trailing ellipsis.",
|
|
79
79
|
"accepts": [
|
|
80
80
|
"text"
|
|
81
81
|
]
|
|
82
82
|
},
|
|
83
83
|
"mention": {
|
|
84
84
|
"required": false,
|
|
85
|
-
"description": "Tap-anywhere mention / tag line below the body. `sys.typo.body.sm` / `sys.color.primary` (no italic — the carousel card reads tags as part of its body block, unlike the Feed · Post card which italicises a single mention).",
|
|
85
|
+
"description": "Tap-anywhere mention / tag line below the body. `sys.typo.body.sm` / `sys.color.background.primary` (no italic — the carousel card reads tags as part of its body block, unlike the Feed · Post card which italicises a single mention).",
|
|
86
86
|
"accepts": [
|
|
87
87
|
"text"
|
|
88
88
|
]
|
|
89
89
|
},
|
|
90
90
|
"footer": {
|
|
91
91
|
"required": true,
|
|
92
|
-
"description": "Bottom row: leading `more` affordance + trailing view count. The 'See more' label renders as a [Text Button](../button/text.md) (`size={'xsmall'}`, `appearance={'secondary'}`) — same family as the card-header follow action — so both card affordances share one state contract. The view count renders as a non-interactive `<span>` matching the same `xsmall` rhythm (`EyeIcon` at `sys.icon.md` + `sys.typo.label.sm` / `sys.color.
|
|
92
|
+
"description": "Bottom row: leading `more` affordance + trailing view count. The 'See more' label renders as a [Text Button](../button/text.md) (`size={'xsmall'}`, `appearance={'secondary'}`) — same family as the card-header follow action — so both card affordances share one state contract. The view count renders as a non-interactive `<span>` matching the same `xsmall` rhythm (`EyeIcon` at `sys.icon.md` + `sys.typo.label.sm` / `sys.color.text.subtle`).",
|
|
93
93
|
"intrinsic": true
|
|
94
94
|
},
|
|
95
95
|
"pagination": {
|
|
@@ -106,8 +106,8 @@
|
|
|
106
106
|
"pagePeek": "ref.space.500",
|
|
107
107
|
"pagePeekNote": "Guaranteed minimum visibility of the next card at the trailing edge. Pinned to `ref.space.500` (40px) — a raw ref step rather than a responsive sys-layout rung — because the carousel wants a fixed-pixel visibility floor independent of the responsive sys-layout shift. The card's flex-basis is `calc(100% - sys.layout.inline.md - ref.space.500)`, so the inter-card gap plus the peek subtract from the pager's inline space in lock-step.",
|
|
108
108
|
"pageSnapAnchor": "Cards stick to the leading edge of the pager (the pager's own `padding-left: sys.layout.container.md`, the 16 rail). `scroll-snap-align: start` on each card plus the pager's `scroll-padding-left: sys.layout.container.md` together guarantee that, after every swipe, the snapped card aligns flush with the 16 rail — and the trailing edge holds the 40px peek of the next card.",
|
|
109
|
-
"cardFill": "sys.color.surface",
|
|
110
|
-
"cardOutline": "sys.borderWidth.hairline sys.color.
|
|
109
|
+
"cardFill": "sys.color.surface.default",
|
|
110
|
+
"cardOutline": "sys.borderWidth.hairline sys.color.border.default",
|
|
111
111
|
"cardRadius": "sys.radius.md",
|
|
112
112
|
"cardPadding": "sys.layout.container.md",
|
|
113
113
|
"cardStackGap": "sys.layout.stack.sm",
|
|
@@ -115,23 +115,23 @@
|
|
|
115
115
|
"cardAvatarSize": 40,
|
|
116
116
|
"cardAvatarRendersAs": "Thumbnail at size 40 — the carousel does not paint its own circular crop or fallback; both come from the Thumbnail family.",
|
|
117
117
|
"cardChannelTypo": "sys.typo.label.md",
|
|
118
|
-
"cardChannelColor": "sys.color.
|
|
118
|
+
"cardChannelColor": "sys.color.text.default",
|
|
119
119
|
"cardVerifiedIcon": "VerifiedFillIcon",
|
|
120
120
|
"cardVerifiedSize": "sys.icon.md",
|
|
121
|
-
"cardVerifiedColor": "sys.color.
|
|
121
|
+
"cardVerifiedColor": "sys.color.text.link",
|
|
122
122
|
"cardVerifiedColorResolved": "ref.palette.blue.500",
|
|
123
123
|
"cardVerifiedPosition": "Leading — sits to the LEFT of the channel name inside the channel-row sub-container.",
|
|
124
124
|
"cardFollowActionRendersAs": "Button variant='text' size='xsmall' appearance='accent' (inactive — link-affordance rule) → appearance='default' (active — followed state recedes). All state tokens delegate to the Text Button family.",
|
|
125
125
|
"cardMoreActionRendersAs": "Button variant='text' size='xsmall' appearance='secondary'. Same Text Button rung as the follow action so the card's two affordances share one state contract.",
|
|
126
126
|
"cardTitleTypo": "sys.typo.label.md",
|
|
127
|
-
"cardTitleColor": "sys.color.
|
|
127
|
+
"cardTitleColor": "sys.color.text.default",
|
|
128
128
|
"cardBodyTypo": "sys.typo.body.sm",
|
|
129
|
-
"cardBodyColor": "sys.color.
|
|
129
|
+
"cardBodyColor": "sys.color.text.subtle",
|
|
130
130
|
"cardBodyLineClamp": 3,
|
|
131
131
|
"cardMentionTypo": "sys.typo.body.sm",
|
|
132
|
-
"cardMentionColor": "sys.color.
|
|
132
|
+
"cardMentionColor": "sys.color.text.mention",
|
|
133
133
|
"cardFooterTypo": "sys.typo.label.sm",
|
|
134
|
-
"cardFooterColor": "sys.color.
|
|
134
|
+
"cardFooterColor": "sys.color.text.subtle",
|
|
135
135
|
"cardFooterIcon": "EyeIcon",
|
|
136
136
|
"cardFooterIconSize": "sys.icon.md",
|
|
137
137
|
"paginationRendersAs": "Pagination component — dot size / gap / radius and active / inactive colors all delegate to the [Pagination](../pagination/pagination.md) spec.",
|
|
@@ -56,9 +56,7 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
|
|
|
56
56
|
</Carousel>
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
##
|
|
60
|
-
|
|
61
|
-
### With header action
|
|
59
|
+
## Header action
|
|
62
60
|
|
|
63
61
|
Extend the header with a trailing `accent` Text Button when there's an index page to route to. Lifts the `headerAction` prop on the `<Carousel>` wrapper.
|
|
64
62
|
|
|
@@ -80,33 +78,12 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
|
|
|
80
78
|
{ icon: 'heart', value: '81%' },
|
|
81
79
|
],
|
|
82
80
|
},
|
|
83
|
-
{
|
|
84
|
-
avatar: { src: '/placeholder.png', alt: 'Tesla' },
|
|
85
|
-
name: 'Tesla',
|
|
86
|
-
followers: '1.4K followers',
|
|
87
|
-
metrics: [
|
|
88
|
-
{ icon: 'star', value: '4.7' },
|
|
89
|
-
{ icon: 'pulse', value: '86' },
|
|
90
|
-
{ icon: 'heart', value: '85.3%' },
|
|
91
|
-
],
|
|
92
|
-
followed: true,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
avatar: { src: '/placeholder.png', alt: 'Stripe' },
|
|
96
|
-
name: 'Stripe',
|
|
97
|
-
followers: '2.1K followers',
|
|
98
|
-
metrics: [
|
|
99
|
-
{ icon: 'star', value: '4.5' },
|
|
100
|
-
{ icon: 'pulse', value: '92.4' },
|
|
101
|
-
{ icon: 'heart', value: '88%' },
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
81
|
]}
|
|
105
82
|
/>
|
|
106
83
|
</Carousel>
|
|
107
84
|
```
|
|
108
85
|
|
|
109
|
-
|
|
86
|
+
## With description
|
|
110
87
|
|
|
111
88
|
The metrics row swaps out for a two-line description. Use for editorial collections where the value of each profile is best explained in copy (channel topic, hot pitch) rather than numeric signals. The description block is fixed to the same two-line height as the metrics row, so cards stay flush across both modes even when description copy clamps with an ellipsis.
|
|
112
89
|
|
|
@@ -124,19 +101,6 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
|
|
|
124
101
|
followers: '12.4K followers',
|
|
125
102
|
description: 'Hands-on threads about systems, infra, and the work behind the launch.',
|
|
126
103
|
},
|
|
127
|
-
{
|
|
128
|
-
avatar: { src: '/placeholder.png', alt: 'Compensation' },
|
|
129
|
-
name: 'Compensation',
|
|
130
|
-
followers: '8.1K followers',
|
|
131
|
-
description: 'Salary checks, offer evaluations, and the quiet math of staying versus leaving — the channel that runs longer than any single conversation can.',
|
|
132
|
-
followed: true,
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
avatar: { src: '/placeholder.png', alt: 'Career' },
|
|
136
|
-
name: 'Career',
|
|
137
|
-
followers: '5.3K followers',
|
|
138
|
-
description: 'Promotion packets, scope debates, and the rewrites that actually cleared.',
|
|
139
|
-
},
|
|
140
104
|
]}
|
|
141
105
|
/>
|
|
142
106
|
</Carousel>
|
|
@@ -149,9 +113,9 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
|
|
|
149
113
|
- **card** — one profile card per page; fixed at **176px** wide.
|
|
150
114
|
- **cover** — top band; 88px tall image-area slot. Renders an `<img>` defaulting to `/placeholder.png` (universal Chorus placeholder). `object-fit: cover` crops to fill the band; `sys.color.surfaceContainerHigh` underlies as the no-image fallback. Consumers override via `items[i].cover.src`.
|
|
151
115
|
- **avatar** — [Thumbnail](../thumbnail/thumbnail.md) `size={64}` with [`outlined={true}`](../thumbnail/thumbnail.md#with-surface-outline), centered and overlapping the cover band's bottom edge. The 2-token (`sys.borderWidth.thin`) `surface`-tone outset halo separating the circle from the cover image is owned by Thumbnail's outlined case — the carousel forwards the prop instead of painting a halo on its own wrapper.
|
|
152
|
-
- **name** — entity name; `sys.typo.label.md` / Semibold / `sys.color.
|
|
153
|
-
- **followers** — follower count; `sys.typo.label.sm` / `sys.color.
|
|
154
|
-
- **metrics** *(optional)* — row of `icon + value` chips: `star → StarFillIcon (sys.color.icon.yellow)`, `pulse → PulseFillIcon (sys.color.success)`, `heart → HeartFillIcon (sys.color.icon.red)`. Mutually exclusive with `description`.
|
|
116
|
+
- **name** — entity name; `sys.typo.label.md` / Semibold / `sys.color.text.default`; centered, single line truncate.
|
|
117
|
+
- **followers** — follower count; `sys.typo.label.sm` / `sys.color.text.subtle`; centered.
|
|
118
|
+
- **metrics** *(optional)* — row of `icon + value` chips: `star → StarFillIcon (sys.color.icon.accent.yellow.default)`, `pulse → PulseFillIcon (sys.color.text.success)`, `heart → HeartFillIcon (sys.color.icon.accent.red.default)`. Mutually exclusive with `description`.
|
|
155
119
|
- **description** *(optional)* — two-line clamped paragraph that replaces the metrics row when present. Block height fixed to two lines of `sys.typo.label.sm` regardless of copy length, so card height stays consistent across metrics-carrying and copy-carrying cards.
|
|
156
120
|
- **followAction** — full-width [Toggle Button](../button/text.md) (`variant={'toggle'}`); `Follow` (inactive) / `Following` (active).
|
|
157
121
|
- **pagination** — [Pagination](../pagination/pagination.md) component, one dot per card (`count` = card count, `activeIndex` from scroll position). Decorative — dot tokens and the `aria-hidden` contract live on its spec; the carousel centers the intrinsic-width row (`align-self: center`).
|
|
@@ -164,11 +128,11 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
|
|
|
164
128
|
| card | Fixed `width: 176px`, `sys.color.surface` fill, `sys.radius.md`, inset hairline outline, `scroll-snap-align: start` |
|
|
165
129
|
| cover | 88px tall image-area slot. Default `src` = `/placeholder.png` (universal image placeholder), `object-fit: cover`, `sys.color.surfaceContainerHigh` underlay |
|
|
166
130
|
| avatar | [Thumbnail](../thumbnail/thumbnail.md) `size={64}` `outlined`, vertical center on cover's bottom edge. The 2-token `surface`-tone halo separating the circle from the cover image is painted by Thumbnail's `outlined` case (outset `box-shadow: 0 0 0 sys.borderWidth.thin sys.color.surface`) — wrapper has no halo of its own |
|
|
167
|
-
| name | `sys.typo.label.md`, `sys.color.
|
|
168
|
-
| followers | `sys.typo.label.sm`, `sys.color.
|
|
131
|
+
| name | `sys.typo.label.md`, `sys.color.text.default`, centered |
|
|
132
|
+
| followers | `sys.typo.label.sm`, `sys.color.text.subtle`, centered |
|
|
169
133
|
| metrics row | `sys.layout.inline.md` gap, centered. Fixed-height slot — `calc(sys.typo.label.sm.size * sys.typo.label.sm.line * 2)` so the row always reserves two lines of `label.sm` regardless of content. |
|
|
170
|
-
| metric chip | `sys.icon.md` glyph + `sys.typo.label.sm` value; star → `StarFillIcon` (`sys.color.icon.yellow`), pulse → `PulseFillIcon` (`sys.color.success`), heart → `HeartFillIcon` (`sys.color.icon.red`) |
|
|
171
|
-
| description | `sys.typo.label.sm` / `sys.color.
|
|
134
|
+
| metric chip | `sys.icon.md` glyph + `sys.typo.label.sm` value; star → `StarFillIcon` (`sys.color.icon.accent.yellow.default`), pulse → `PulseFillIcon` (`sys.color.text.success`), heart → `HeartFillIcon` (`sys.color.icon.accent.red.default`) |
|
|
135
|
+
| description | `sys.typo.label.sm` / `sys.color.text.subtle`, centered, two-line clamp with trailing ellipsis. Two-layer DOM — outer container owns the same fixed-height slot as `metrics row` (min/max-height = 2 label.sm lines); inner `<p>` owns the `-webkit-line-clamp: 2` truncation. Split sidesteps a Chrome quirk where `display: -webkit-box` and explicit `height` on one element break the third-line clip. |
|
|
172
136
|
| followAction | [Toggle Button](../button/text.md) (Chip-toggle anatomy), stretched to full card width |
|
|
173
137
|
| pagination dot | [Pagination](../pagination/pagination.md) component delegated verbatim — 6 × 6 dots, active/inactive colors, and row gap bind on its spec |
|
|
174
138
|
|