@teamblind-chorus/ui 1.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/LICENSE +21 -0
- package/README.md +112 -0
- package/agents/AGENTS.md +143 -0
- package/agents/DESIGN.md +1311 -0
- package/agents/LOVABLE.md +472 -0
- package/agents/anti-patterns.md +533 -0
- package/agents/catalog.md +232 -0
- package/agents/components/avatar-rail/avatar-rail.family.json +46 -0
- package/agents/components/avatar-rail/avatar-rail.md +103 -0
- package/agents/components/avatar-rail/avatar-rail.spec.json +160 -0
- package/agents/components/badge/badge.family.json +45 -0
- package/agents/components/badge/badge.md +10 -0
- package/agents/components/badge/role.md +100 -0
- package/agents/components/badge/role.spec.json +75 -0
- package/agents/components/badge/update.md +132 -0
- package/agents/components/badge/update.spec.json +114 -0
- package/agents/components/banner/banner.family.json +28 -0
- package/agents/components/banner/banner.md +136 -0
- package/agents/components/banner/banner.spec.json +136 -0
- package/agents/components/bottom-sheet/bottom-sheet.family.json +29 -0
- package/agents/components/bottom-sheet/bottom-sheet.md +176 -0
- package/agents/components/bottom-sheet/bottom-sheet.spec.json +168 -0
- package/agents/components/bubble/bubble.family.json +29 -0
- package/agents/components/bubble/bubble.md +134 -0
- package/agents/components/bubble/bubble.spec.json +91 -0
- package/agents/components/button/button.family.json +76 -0
- package/agents/components/button/button.md +31 -0
- package/agents/components/button/check.md +138 -0
- package/agents/components/button/check.spec.json +161 -0
- package/agents/components/button/fab.md +161 -0
- package/agents/components/button/fab.spec.json +106 -0
- package/agents/components/button/icon.md +141 -0
- package/agents/components/button/icon.spec.json +164 -0
- package/agents/components/button/standard.md +219 -0
- package/agents/components/button/standard.spec.json +205 -0
- package/agents/components/button/text.md +186 -0
- package/agents/components/button/text.spec.json +215 -0
- package/agents/components/button/toggle.md +108 -0
- package/agents/components/button/toggle.spec.json +124 -0
- package/agents/components/button/toolbar.md +189 -0
- package/agents/components/button/toolbar.spec.json +109 -0
- package/agents/components/carousel/carousel.family.json +41 -0
- package/agents/components/carousel/carousel.md +40 -0
- package/agents/components/carousel/post.md +148 -0
- package/agents/components/carousel/post.spec.json +229 -0
- package/agents/components/carousel/profile.md +184 -0
- package/agents/components/carousel/profile.spec.json +219 -0
- package/agents/components/chip/chip.family.json +37 -0
- package/agents/components/chip/chip.md +10 -0
- package/agents/components/chip/filter.md +212 -0
- package/agents/components/chip/filter.spec.json +124 -0
- package/agents/components/chip/tag.md +137 -0
- package/agents/components/chip/tag.spec.json +104 -0
- package/agents/components/dialog/dialog.family.json +29 -0
- package/agents/components/dialog/dialog.md +113 -0
- package/agents/components/dialog/dialog.spec.json +156 -0
- package/agents/components/directory-list/directory-list.family.json +46 -0
- package/agents/components/directory-list/directory-list.md +87 -0
- package/agents/components/directory-list/directory-list.spec.json +104 -0
- package/agents/components/divider/divider.family.json +28 -0
- package/agents/components/divider/divider.md +78 -0
- package/agents/components/divider/divider.spec.json +51 -0
- package/agents/components/feed/ad.md +108 -0
- package/agents/components/feed/ad.spec.json +187 -0
- package/agents/components/feed/feed.family.json +48 -0
- package/agents/components/feed/feed.md +30 -0
- package/agents/components/feed/post.md +240 -0
- package/agents/components/feed/post.spec.json +361 -0
- package/agents/components/form-field/form-field.family.json +50 -0
- package/agents/components/form-field/form-field.md +11 -0
- package/agents/components/form-field/input.md +198 -0
- package/agents/components/form-field/input.spec.json +202 -0
- package/agents/components/form-field/search.md +81 -0
- package/agents/components/form-field/search.spec.json +135 -0
- package/agents/components/form-field/select.md +101 -0
- package/agents/components/form-field/select.spec.json +194 -0
- package/agents/components/form-field/textarea.md +89 -0
- package/agents/components/form-field/textarea.spec.json +176 -0
- package/agents/components/header/header.family.json +43 -0
- package/agents/components/header/header.md +18 -0
- package/agents/components/header/main.md +101 -0
- package/agents/components/header/main.spec.json +117 -0
- package/agents/components/header/sub.md +129 -0
- package/agents/components/header/sub.spec.json +81 -0
- package/agents/components/list/accordion.md +183 -0
- package/agents/components/list/accordion.spec.json +201 -0
- package/agents/components/list/entry.md +280 -0
- package/agents/components/list/entry.spec.json +237 -0
- package/agents/components/list/list.family.json +75 -0
- package/agents/components/list/list.md +24 -0
- package/agents/components/list/radio.md +144 -0
- package/agents/components/list/radio.spec.json +186 -0
- package/agents/components/list/standard.md +262 -0
- package/agents/components/list/standard.spec.json +221 -0
- package/agents/components/metadata/compact.md +69 -0
- package/agents/components/metadata/compact.spec.json +69 -0
- package/agents/components/metadata/metadata.family.json +42 -0
- package/agents/components/metadata/metadata.md +26 -0
- package/agents/components/metadata/standard.md +104 -0
- package/agents/components/metadata/standard.spec.json +152 -0
- package/agents/components/nav-card/nav-card.family.json +29 -0
- package/agents/components/nav-card/nav-card.md +179 -0
- package/agents/components/nav-card/nav-card.spec.json +161 -0
- package/agents/components/nav-list/nav-list.family.json +46 -0
- package/agents/components/nav-list/nav-list.md +91 -0
- package/agents/components/nav-list/nav-list.spec.json +107 -0
- package/agents/components/navigation-bar/main.md +201 -0
- package/agents/components/navigation-bar/main.spec.json +109 -0
- package/agents/components/navigation-bar/navigation-bar.family.json +44 -0
- package/agents/components/navigation-bar/navigation-bar.md +21 -0
- package/agents/components/navigation-bar/search.md +96 -0
- package/agents/components/navigation-bar/search.spec.json +142 -0
- package/agents/components/navigation-bar/sub.md +174 -0
- package/agents/components/navigation-bar/sub.spec.json +123 -0
- package/agents/components/page-shell/page-shell.family.json +22 -0
- package/agents/components/page-shell/page-shell.md +51 -0
- package/agents/components/profile-header/profile-header.family.json +29 -0
- package/agents/components/profile-header/profile-header.md +149 -0
- package/agents/components/profile-header/profile-header.spec.json +200 -0
- package/agents/components/progress/progress.family.json +27 -0
- package/agents/components/progress/progress.md +38 -0
- package/agents/components/progress/progress.spec.json +67 -0
- package/agents/components/side-sheet/side-sheet.family.json +30 -0
- package/agents/components/side-sheet/side-sheet.md +154 -0
- package/agents/components/side-sheet/side-sheet.spec.json +109 -0
- package/agents/components/skeleton/skeleton.family.json +28 -0
- package/agents/components/skeleton/skeleton.md +123 -0
- package/agents/components/skeleton/skeleton.spec.json +73 -0
- package/agents/components/status-tag/status-tag.family.json +26 -0
- package/agents/components/status-tag/status-tag.md +114 -0
- package/agents/components/status-tag/status-tag.spec.json +69 -0
- package/agents/components/suggestion-list/suggestion-list.family.json +46 -0
- package/agents/components/suggestion-list/suggestion-list.md +91 -0
- package/agents/components/suggestion-list/suggestion-list.spec.json +178 -0
- package/agents/components/switch/switch.family.json +27 -0
- package/agents/components/switch/switch.md +114 -0
- package/agents/components/switch/switch.spec.json +123 -0
- package/agents/components/tab-bar/tab-bar.family.json +27 -0
- package/agents/components/tab-bar/tab-bar.md +178 -0
- package/agents/components/tab-bar/tab-bar.spec.json +184 -0
- package/agents/components/tabs/rounded.md +150 -0
- package/agents/components/tabs/rounded.spec.json +140 -0
- package/agents/components/tabs/segmented.md +114 -0
- package/agents/components/tabs/segmented.spec.json +100 -0
- package/agents/components/tabs/tabs.family.json +59 -0
- package/agents/components/tabs/tabs.md +18 -0
- package/agents/components/tabs/underline.md +147 -0
- package/agents/components/tabs/underline.spec.json +139 -0
- package/agents/components/thumbnail/thumbnail.family.json +28 -0
- package/agents/components/thumbnail/thumbnail.md +152 -0
- package/agents/components/thumbnail/thumbnail.spec.json +172 -0
- package/agents/components/toast/toast.family.json +28 -0
- package/agents/components/toast/toast.md +133 -0
- package/agents/components/toast/toast.spec.json +89 -0
- package/agents/components/tooltip/tooltip.family.json +29 -0
- package/agents/components/tooltip/tooltip.md +139 -0
- package/agents/components/tooltip/tooltip.spec.json +110 -0
- package/agents/compose.md +240 -0
- package/agents/icons.json +831 -0
- package/agents/images.md +66 -0
- package/agents/manifest.json +87 -0
- package/agents/patterns/README.md +59 -0
- package/agents/patterns/actions.md +50 -0
- package/agents/patterns/browsing.md +52 -0
- package/agents/patterns/communications.md +56 -0
- package/agents/patterns/layout.md +72 -0
- package/agents/patterns/modals.md +50 -0
- package/agents/patterns/visual.md +55 -0
- package/agents/reconstruct.md +55 -0
- package/agents/scoped-adoption.md +111 -0
- package/agents/tokens.usage.json +1657 -0
- package/agents/usage.json +422 -0
- package/dist/icons/index.cjs +1332 -0
- package/dist/icons/index.cjs.map +1 -0
- package/dist/icons/index.d.cts +228 -0
- package/dist/icons/index.d.ts +228 -0
- package/dist/icons/index.js +1114 -0
- package/dist/icons/index.js.map +1 -0
- package/dist/index.cjs +5905 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +896 -0
- package/dist/index.d.ts +896 -0
- package/dist/index.js +5847 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +5765 -0
- package/eslint/README.md +79 -0
- package/eslint/index.js +78 -0
- package/eslint/rules.js +472 -0
- package/eslint/test.mjs +135 -0
- package/package.json +96 -0
- package/placeholder.png +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../spec.schema.json",
|
|
3
|
+
"name": "List",
|
|
4
|
+
"family": "list",
|
|
5
|
+
"subcomponent": "radio",
|
|
6
|
+
"description": "Single-select picker List variant. Each row carries a leading 24px (`sys.icon.lg`) radio indicator; clicking any row commits its value via `onChange(value)`. Exactly one row is selected at a time (or zero before the user picks). Use inside picker sheets, settings rows, sort-by menus. A major category that opens a second screen can set `nav: true` to add a trailing drill-in chevron alongside the radio indicator — the row commits its value via `onChange` and routes to the sub-screen via the row's `onClick`.",
|
|
7
|
+
"element": "ul",
|
|
8
|
+
"props": {
|
|
9
|
+
"embedded": {
|
|
10
|
+
"type": "boolean",
|
|
11
|
+
"default": false,
|
|
12
|
+
"description": "Composition mode flag. When `true` (or when the List is a direct child of `.chorus-carousel` / `.chorus-feed`), enters **embedded mode**: zeroes its own `background` + `padding` so chrome defers to the host. Pass explicitly inside `<Carousel>` / `<Feed>` for the contract to be visible in JSX; the DOM-ancestry safety net in styles.css also activates the mode when omitted. See `compositionModes` in `list.family.json`."
|
|
13
|
+
},
|
|
14
|
+
"items": {
|
|
15
|
+
"type": "node",
|
|
16
|
+
"required": true,
|
|
17
|
+
"description": "Array of row descriptors."
|
|
18
|
+
},
|
|
19
|
+
"value": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"optional": true,
|
|
22
|
+
"description": "Selected value. Controlled."
|
|
23
|
+
},
|
|
24
|
+
"onChange": {
|
|
25
|
+
"type": "function",
|
|
26
|
+
"optional": true,
|
|
27
|
+
"description": "Commits the next value when a row is selected."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"slots": {
|
|
31
|
+
"container": {
|
|
32
|
+
"required": true,
|
|
33
|
+
"description": "Outer scroll surface. Vertical stack with a transparent fill (inherits parent container tone); rows separated by a 1px outlineVariant divider, not a gap."
|
|
34
|
+
},
|
|
35
|
+
"row": {
|
|
36
|
+
"required": true,
|
|
37
|
+
"description": "Single list item. Whole row is the interactive target."
|
|
38
|
+
},
|
|
39
|
+
"leading": {
|
|
40
|
+
"required": true,
|
|
41
|
+
"description": "24px (`sys.icon.lg`) radio indicator — `RadioIcon` at rest, `RadioFillIcon` when selected. Decorative — keyboard focus and click target both sit on the row.",
|
|
42
|
+
"accepts": [
|
|
43
|
+
"icon"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"label": {
|
|
47
|
+
"required": true,
|
|
48
|
+
"description": "Primary row text. 16px / Regular / onSurface. Single line; truncates with ellipsis.",
|
|
49
|
+
"accepts": [
|
|
50
|
+
"text"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"supportingText": {
|
|
54
|
+
"required": false,
|
|
55
|
+
"description": "Secondary line under label. 14px / Regular / onSurfaceVariant, sits directly under the label with no extra gap.",
|
|
56
|
+
"accepts": [
|
|
57
|
+
"text"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"navChevron": {
|
|
61
|
+
"required": false,
|
|
62
|
+
"description": "Auto-rendered 16px right-pointing chevron at the trailing edge, painted when the row sets `nav: true` — marks a major category that opens a second screen, shown alongside the leading radio indicator. `onSurfaceVariant` tone, decorative (`aria-hidden`); never a separate hit target — the whole row is the click target."
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"rowProps": {
|
|
66
|
+
"value": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"required": true
|
|
69
|
+
},
|
|
70
|
+
"label": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"required": true
|
|
73
|
+
},
|
|
74
|
+
"supportingText": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"nav": {
|
|
79
|
+
"type": "boolean",
|
|
80
|
+
"default": false,
|
|
81
|
+
"description": "Drill-in case for a major category — when `true`, the row auto-renders a trailing right-pointing chevron alongside the radio indicator, signalling the option opens a second screen."
|
|
82
|
+
},
|
|
83
|
+
"onClick": {
|
|
84
|
+
"type": "function",
|
|
85
|
+
"optional": true,
|
|
86
|
+
"description": "Fires on row click in addition to `onChange`. On a `nav: true` row, wire it to route to the category's second screen."
|
|
87
|
+
},
|
|
88
|
+
"disabled": {
|
|
89
|
+
"type": "boolean",
|
|
90
|
+
"default": false
|
|
91
|
+
},
|
|
92
|
+
"strong": {
|
|
93
|
+
"type": "boolean",
|
|
94
|
+
"default": false,
|
|
95
|
+
"description": "Per-row label-emphasis opt-in (family-wide contract — see [list.md § Cross-sub contract](./list.md)). When `true`, the row's label weight promotes from `body.md` Regular (400) to `label.lg` Semibold (600) at the same 16px size and 1.5 line-height — geometry stays identical, only the glyph stroke thickens. Use sparingly to mark one primary row inside a denser scan; a stack where every row is `strong` defeats the marker."
|
|
96
|
+
},
|
|
97
|
+
"forcedState": {
|
|
98
|
+
"type": "literal",
|
|
99
|
+
"values": [
|
|
100
|
+
"hovered",
|
|
101
|
+
"pressed",
|
|
102
|
+
"focused"
|
|
103
|
+
],
|
|
104
|
+
"optional": true,
|
|
105
|
+
"description": "Docs-only — pins the row to a single visual state via `data-force-state`. Not for production use."
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"sizing": {
|
|
109
|
+
"rowPaddingBlock": "ref.space.100",
|
|
110
|
+
"rowPaddingInline": "ref.space.200",
|
|
111
|
+
"rowMinHeight": "ref.space.600",
|
|
112
|
+
"leadingTextGap": "sys.layout.inline.md",
|
|
113
|
+
"leadingTextGapNote": "8px (`sys.layout.inline.md`) between the leading radio indicator (an icon leading) and the text group — the icon-leading rung of the family's role-based row spacing.",
|
|
114
|
+
"trailingActionGap": "sys.layout.inline.md",
|
|
115
|
+
"trailingActionGapNote": "Fixed 8px between the text group and a trailing nav chevron (the major-category `nav: true` case) — the family-wide trailing gap.",
|
|
116
|
+
"dividerWidth": "sys.borderWidth.hairline",
|
|
117
|
+
"dividerColor": "sys.color.outlineVariant",
|
|
118
|
+
"labelTypo": "sys.typo.body.md",
|
|
119
|
+
"labelColor": "sys.color.onSurface",
|
|
120
|
+
"supportingTypo": "sys.typo.body.sm",
|
|
121
|
+
"supportingColor": "sys.color.onSurfaceVariant",
|
|
122
|
+
"supportingOffset": "0",
|
|
123
|
+
"leadingRadioSize": "24 × 24",
|
|
124
|
+
"leadingRadioColorRest": "sys.color.outline",
|
|
125
|
+
"leadingRadioColorSelected": "sys.color.primary",
|
|
126
|
+
"navChevronSize": "16 × 16",
|
|
127
|
+
"navChevronColor": "sys.color.onSurfaceVariant"
|
|
128
|
+
},
|
|
129
|
+
"states": {
|
|
130
|
+
"default": {
|
|
131
|
+
"overlay": null
|
|
132
|
+
},
|
|
133
|
+
"hovered": {
|
|
134
|
+
"overlay": {
|
|
135
|
+
"color": "label",
|
|
136
|
+
"opacity": "sys.state.hover"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"pressed": {
|
|
140
|
+
"overlay": {
|
|
141
|
+
"color": "label",
|
|
142
|
+
"opacity": "sys.state.pressed"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"selected": {
|
|
146
|
+
"leading": "Filled primary indicator; row foreground stays at onSurface. No fill change on the row itself."
|
|
147
|
+
},
|
|
148
|
+
"disabled": {
|
|
149
|
+
"containerOpacity": "sys.state.disabled",
|
|
150
|
+
"containerOpacityScope": "Dims the row content only — the inter-row divider and the focus overlay keep full opacity, so a disabled row never fades the hairline rule between it and the next row.",
|
|
151
|
+
"pointerEvents": "none",
|
|
152
|
+
"note": "Radio indicator dims with the row."
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"focusIndicator": {
|
|
156
|
+
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. The ring sits on the row, not on the leading indicator — the row is the keyboard target.",
|
|
157
|
+
"composition": "inward",
|
|
158
|
+
"compositionReason": "Rows tile the column flush with only a hairline `outlineVariant` divider between them; an outward ring would overlap the divider and the neighbouring row.",
|
|
159
|
+
"overlay": {
|
|
160
|
+
"color": "label",
|
|
161
|
+
"opacity": "sys.state.focus"
|
|
162
|
+
},
|
|
163
|
+
"ring": {
|
|
164
|
+
"outerWidth": "sys.borderWidth.thin",
|
|
165
|
+
"outerColor": "sys.color.focus",
|
|
166
|
+
"outerLayerPosition": "depth 0..2px from the row edge (the outer stroke)",
|
|
167
|
+
"insetWidth": "sys.borderWidth.hairline",
|
|
168
|
+
"insetColor": "sys.color.focusInset",
|
|
169
|
+
"insetLayerPosition": "depth 2..3px from the row edge (the counter-ring just inside the outer stroke)",
|
|
170
|
+
"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."
|
|
171
|
+
},
|
|
172
|
+
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
173
|
+
},
|
|
174
|
+
"behavior": {
|
|
175
|
+
"keyboardNavigation": "Arrow up/down moves focus between rows. Space and Enter commit the focused row's value. Home and End jump to first / last.",
|
|
176
|
+
"selectionModel": "Single-select only; selecting a row implicitly deselects the previously selected. Controlled — pass `value` and `onChange`; uncontrolled use defaults to no selection.",
|
|
177
|
+
"rowClickTarget": "Whole row is clickable. The leading indicator is never a separate hit target.",
|
|
178
|
+
"navDrillIn": "A row with `nav: true` renders the trailing chevron and routes to the category's second screen via the row's `onClick`, while the click still commits the row's value through `onChange`. The chevron is decorative — the whole row is the single click target, not the chevron alone.",
|
|
179
|
+
"truncationNotWrap": "Both label and supportingText truncate; the row never grows to fit long text."
|
|
180
|
+
},
|
|
181
|
+
"forbidden": [
|
|
182
|
+
"radio glyph as a separate hit area — the entire row is the click target",
|
|
183
|
+
"multi-select painted as radio — radio variant is single-select; use checkbox or chip/filter for multi-select",
|
|
184
|
+
"selected state painted with sys.color.primaryContainer fill — radio selected paints the inner dot, not the row fill"
|
|
185
|
+
]
|
|
186
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Standard
|
|
2
|
+
|
|
3
|
+
The default [List](./list.md) sub — display or navigation rows over the shared List anatomy. The whole row is the click target; there is no selection model. A row is text-only by default, opting into a **leading image** (40px [Thumbnail](../thumbnail/thumbnail.md) via `thumbnail`) or a **leading icon** (`sys.icon.lg` glyph via `icon`). Geometry, typography, divider, state overlays, and inward focus ring delegate to the [family-wide rules](./list.md).
|
|
4
|
+
|
|
5
|
+
**Layout inset.** `full-bleed` — sits as a direct child of the page shell. Each row pays its own `16px inline / 8px block` padding via `layout.container.*`; do **not** wrap the list in another `padding-inline` / `px-*` / `style={{ padding: … }}` div. Inside a bounded surface (Card / Dialog / BottomSheet / Sheet), apply the negative-margin opt-out — see [`AGENTS.md` § Composition rules](../../../AGENTS.md#composition-rules).
|
|
6
|
+
|
|
7
|
+
A row opts into an inline `count` Badge to the right of the label (4px / `sys.layout.inline.sm` — the unread / status-count case below). For the richer directory shape — a selectable avatar at the 32 / 48 / 56 rung with a stacked `secondary` identity line + optional description — reach for [list/entry](./entry.md), the canonical home for entity-row directory cases. Standard's leading image is single-density at the 40 rung; its own inline `count` covers the plain label + count case without the avatar-directory anatomy.
|
|
8
|
+
|
|
9
|
+
## Default
|
|
10
|
+
|
|
11
|
+
A plain text list — five menu rows with optional supporting text.
|
|
12
|
+
|
|
13
|
+
```preview
|
|
14
|
+
list/standard
|
|
15
|
+
---
|
|
16
|
+
import { List } from '@teamblind-chorus/ui';
|
|
17
|
+
|
|
18
|
+
<List
|
|
19
|
+
items={[
|
|
20
|
+
{ value: 'profile', label: 'Profile', supportingText: 'Name, photo, bio' },
|
|
21
|
+
{ value: 'notif', label: 'Notifications', supportingText: 'Email, push, in-app' },
|
|
22
|
+
{ value: 'privacy', label: 'Privacy', supportingText: 'Who can see your activity' },
|
|
23
|
+
{ value: 'language', label: 'Language' },
|
|
24
|
+
{ value: 'about', label: 'About' },
|
|
25
|
+
]}
|
|
26
|
+
/>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Use cases
|
|
30
|
+
|
|
31
|
+
### With trailing action
|
|
32
|
+
|
|
33
|
+
A Text Button in the row's `trailingIcon` slot turns a display row into row + action — the row label stays informational (no `onClick` on the row), the trailing button is the only commit target. Reach for it on settings rows that pair a value with a small "change / edit / view" action.
|
|
34
|
+
|
|
35
|
+
```preview
|
|
36
|
+
list/standard-with-trailing-action
|
|
37
|
+
---
|
|
38
|
+
import { Button, List } from '@teamblind-chorus/ui';
|
|
39
|
+
|
|
40
|
+
<List
|
|
41
|
+
aria-label="Notification channels"
|
|
42
|
+
items={[
|
|
43
|
+
{
|
|
44
|
+
value: 'email',
|
|
45
|
+
label: 'Email',
|
|
46
|
+
supportingText: 'work@example.com',
|
|
47
|
+
trailingIcon: (
|
|
48
|
+
<Button variant="text" size="small" appearance="accent" onClick={() => {}}>
|
|
49
|
+
Edit
|
|
50
|
+
</Button>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: 'sms',
|
|
55
|
+
label: 'SMS',
|
|
56
|
+
supportingText: '+1 (415) ***-2487',
|
|
57
|
+
trailingIcon: (
|
|
58
|
+
<Button variant="text" size="small" appearance="accent" onClick={() => {}}>
|
|
59
|
+
Edit
|
|
60
|
+
</Button>
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### With an inline count
|
|
68
|
+
|
|
69
|
+
Pass a `count` on a row and a Badge renders to the right of the label on the same line, separated by `4px` (`sys.layout.inline.sm`) — the unread / status-count case. The label shrinks first so a long label truncates against the count, which stays pinned at its intrinsic width. Unlike `trailingIcon`, the count tiles tight to the label inside the text group, so it **composes with the drill-in chevron**: a `nav: true` row carries the count by the label *and* keeps its trailing chevron — one row with both an icon and a badge.
|
|
70
|
+
|
|
71
|
+
```preview
|
|
72
|
+
list/standard-count
|
|
73
|
+
---
|
|
74
|
+
import { List, Badge } from '@teamblind-chorus/ui';
|
|
75
|
+
|
|
76
|
+
<List
|
|
77
|
+
items={[
|
|
78
|
+
{ value: 'profile', label: 'Profile', supportingText: 'Display name, avatar, bio', nav: true },
|
|
79
|
+
{ value: 'channels', label: 'My channels', supportingText: '12 joined · 3 muted', nav: true },
|
|
80
|
+
{ value: 'notif', label: 'Notifications', count: <Badge>3</Badge>, nav: true },
|
|
81
|
+
{ value: 'privacy', label: 'Privacy', nav: true },
|
|
82
|
+
{ value: 'account', label: 'Account', nav: true },
|
|
83
|
+
]}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Drill-in rows
|
|
88
|
+
|
|
89
|
+
Set `nav: true` on a row to auto-render a trailing right-pointing chevron — the drill-in affordance signalling the row routes to another surface. The whole row is the click target; the chevron is decorative. This is the canonical settings / menu navigation shape (it replaces the former `nav` variant). A per-item `trailingIcon` overrides the chevron for that row.
|
|
90
|
+
|
|
91
|
+
```preview
|
|
92
|
+
list/standard-nav
|
|
93
|
+
---
|
|
94
|
+
import { List } from '@teamblind-chorus/ui';
|
|
95
|
+
|
|
96
|
+
<List
|
|
97
|
+
items={[
|
|
98
|
+
{ value: 'profile', label: 'Profile', supportingText: 'Display name, avatar, bio', nav: true },
|
|
99
|
+
{ value: 'channels', label: 'My channels', supportingText: '12 joined · 3 muted', nav: true },
|
|
100
|
+
{ value: 'notif', label: 'Notifications', nav: true },
|
|
101
|
+
{ value: 'privacy', label: 'Privacy', nav: true },
|
|
102
|
+
{ value: 'account', label: 'Account', nav: true },
|
|
103
|
+
]}
|
|
104
|
+
/>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### With a leading icon
|
|
108
|
+
|
|
109
|
+
Pass an `icon` on a row and a 24px (`sys.icon.lg`) glyph renders at the leading edge in `onSurfaceVariant`, `8px` (`sys.layout.inline.md`) from the text group — the category-mark shape for settings / menu rows, lighter than a 40px leading image (and mutually exclusive with `thumbnail`). The glyph is decorative (the label carries the meaning) and the slot enforces the 24 rung regardless of the glyph's own `size`. Every other slot stays optional, so the icon composes with `supportingText`, an inline `count`, and the drill-in chevron.
|
|
110
|
+
|
|
111
|
+
```preview
|
|
112
|
+
list/standard-icon-leading
|
|
113
|
+
---
|
|
114
|
+
import { List, Badge } from '@teamblind-chorus/ui';
|
|
115
|
+
import { ProfileIcon, BellIcon, BookmarkIcon, PulseIcon } from '@teamblind-chorus/ui/icons';
|
|
116
|
+
|
|
117
|
+
<List
|
|
118
|
+
items={[
|
|
119
|
+
{ value: 'profile', label: 'Profile', supportingText: 'Display name, avatar, bio', icon: <ProfileIcon />, nav: true },
|
|
120
|
+
{ value: 'notif', label: 'Notifications', icon: <BellIcon />, count: <Badge>3</Badge>, nav: true },
|
|
121
|
+
{ value: 'saved', label: 'Saved', icon: <BookmarkIcon />, nav: true },
|
|
122
|
+
{ value: 'activity', label: 'Activity', icon: <PulseIcon />, nav: true },
|
|
123
|
+
]}
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Leading image
|
|
128
|
+
|
|
129
|
+
Pass a `thumbnail` on a row and a 40px [Thumbnail](../thumbnail/thumbnail.md) renders at the leading edge, vertically centred against the label column. `thumbnail` props (`src`, `alt`, `updateDot`, `logoBadge`) forward verbatim. The gap to the text group steps up to `12px` (`sys.layout.inline.lg`) so the avatar and the label column read as two distinct blocks. This is the channel / source / author row shape — same click semantics as a text row, no selection model.
|
|
130
|
+
|
|
131
|
+
```preview
|
|
132
|
+
list/standard-image
|
|
133
|
+
---
|
|
134
|
+
import { List } from '@teamblind-chorus/ui';
|
|
135
|
+
|
|
136
|
+
<List
|
|
137
|
+
items={[
|
|
138
|
+
{ value: 'design-weekly', label: 'Design Weekly', supportingText: 'Updated 2h ago', thumbnail: { alt: 'Design Weekly' } },
|
|
139
|
+
{ value: 'frontend', label: 'Frontend Friday', supportingText: 'Updated 1d ago', thumbnail: { alt: 'Frontend Friday' } },
|
|
140
|
+
{ value: 'changelog', label: 'Changelog', supportingText: 'Updated 3d ago', thumbnail: { alt: 'Changelog' } },
|
|
141
|
+
]}
|
|
142
|
+
/>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Leading image — with trailing action
|
|
146
|
+
|
|
147
|
+
A Text Button in the row's `trailingIcon` slot — the canonical "directory row + small commit" composition. Reach for it on follow / join / invite rows where the leading Thumbnail anchors the entity and the trailing button is the only commit. Row body stays informational.
|
|
148
|
+
|
|
149
|
+
```preview
|
|
150
|
+
list/standard-image-with-trailing-action
|
|
151
|
+
---
|
|
152
|
+
import { Button, List } from '@teamblind-chorus/ui';
|
|
153
|
+
|
|
154
|
+
<List
|
|
155
|
+
aria-label="Suggested channels"
|
|
156
|
+
items={[
|
|
157
|
+
{
|
|
158
|
+
value: 'product',
|
|
159
|
+
label: 'Product Design',
|
|
160
|
+
supportingText: '1,204 colleagues following',
|
|
161
|
+
thumbnail: { alt: 'Product Design' },
|
|
162
|
+
trailingIcon: (
|
|
163
|
+
<Button variant="text" size="small" appearance="accent" onClick={() => {}}>
|
|
164
|
+
Follow
|
|
165
|
+
</Button>
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
value: 'frontend',
|
|
170
|
+
label: 'Frontend',
|
|
171
|
+
supportingText: '892 colleagues following',
|
|
172
|
+
thumbnail: { alt: 'Frontend' },
|
|
173
|
+
trailingIcon: (
|
|
174
|
+
<Button variant="text" size="small" appearance="accent" onClick={() => {}}>
|
|
175
|
+
Follow
|
|
176
|
+
</Button>
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
]}
|
|
180
|
+
/>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Leading image — drill-in rows
|
|
184
|
+
|
|
185
|
+
Set `nav: true` on a leading-image row for an avatar-anchored row that routes to another surface (channel → channel detail, person → profile). The whole row is the click target; the chevron is decorative. A per-item `trailingIcon` overrides the chevron.
|
|
186
|
+
|
|
187
|
+
```preview
|
|
188
|
+
list/standard-image-nav
|
|
189
|
+
---
|
|
190
|
+
import { List } from '@teamblind-chorus/ui';
|
|
191
|
+
|
|
192
|
+
<List
|
|
193
|
+
items={[
|
|
194
|
+
{ value: 'design-weekly', label: 'Design Weekly', supportingText: '2.3k members', thumbnail: { alt: 'Design Weekly' }, nav: true },
|
|
195
|
+
{ value: 'frontend', label: 'Frontend Friday', supportingText: '1.1k members', thumbnail: { alt: 'Frontend Friday' }, nav: true },
|
|
196
|
+
{ value: 'changelog', label: 'Changelog', supportingText: '840 members', thumbnail: { alt: 'Changelog' }, nav: true },
|
|
197
|
+
]}
|
|
198
|
+
/>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Leading image — without divider
|
|
202
|
+
|
|
203
|
+
`divider: false` on a row suppresses its bottom hairline rule — useful when a visual group ends mid-stack and the divider would visually fence off the next group from its label. The row's footprint and inline padding stay unchanged.
|
|
204
|
+
|
|
205
|
+
```preview
|
|
206
|
+
list/standard-image-without-divider
|
|
207
|
+
---
|
|
208
|
+
import { List } from '@teamblind-chorus/ui';
|
|
209
|
+
|
|
210
|
+
<List
|
|
211
|
+
items={[
|
|
212
|
+
{ value: 'design-weekly', label: 'Design Weekly', supportingText: 'Updated 2h ago', thumbnail: { alt: 'Design Weekly' } },
|
|
213
|
+
{ value: 'frontend', label: 'Frontend Friday', supportingText: 'Updated 1d ago', thumbnail: { alt: 'Frontend Friday' }, divider: false },
|
|
214
|
+
{ value: 'changelog', label: 'Changelog', supportingText: 'Updated 3d ago', thumbnail: { alt: 'Changelog' } },
|
|
215
|
+
]}
|
|
216
|
+
/>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Slots
|
|
220
|
+
|
|
221
|
+
- **container** — outer vertical stack (delegates to family).
|
|
222
|
+
- **row** — single list item; whole row is the click target.
|
|
223
|
+
- **leading** *(optional)* — one of two mutually exclusive types, omitted when the row passes neither (the label then starts at the row's inline edge):
|
|
224
|
+
- *image type* — a [Thumbnail](../thumbnail/thumbnail.md) at the 40 rung, vertically centred, painted when the row passes `thumbnail`. `thumbnail` props (`src`, `alt`, `updateDot`, `logoBadge`) forward verbatim. Gap to the text group is `12px` (`sys.layout.inline.lg`).
|
|
225
|
+
- *icon type* — a 24px (`sys.icon.lg`) decorative glyph in `onSurfaceVariant`, vertically centred, painted when the row passes `icon`. Gap to the text group is `8px` (`sys.layout.inline.md`); the slot enforces the 24 rung regardless of the glyph's own `size`.
|
|
226
|
+
- **label** — primary row text. 16px / Regular / `onSurface`. Truncates first against an inline `count`.
|
|
227
|
+
- **count** *(optional, per-row)* — inline node to the right of the label on the same line, separated by `4px` (`sys.layout.inline.sm`). Canonical fill: a numeric [Badge](../badge/badge.md). Tiles tight to the label inside the text group (distinct from `trailingIcon` at the trailing edge); composes with the nav chevron on the same row.
|
|
228
|
+
- **supportingText** *(optional)* — secondary line under label. Sits directly under the label with no extra gap.
|
|
229
|
+
- **trailingIcon** *(optional, per-row)* — consumer-supplied node at the trailing edge. Each row decides independently. Canonical fills: a 16px icon (e.g. an external-link mark), or `<Button variant="text" appearance="accent">` (Follow / Invite) on leading-image rows. A status badge does **not** go here — it tiles next to the label via `count`. Its own hit target: a tap on this slot stops propagating before it reaches the row's `onClick`. Overrides the nav chevron on the same row.
|
|
230
|
+
- **navChevron** *(optional, per-row)* — auto-rendered 16px right-pointing chevron, painted when the row sets `nav: true`. `onSurfaceVariant`, decorative; never a separate hit target.
|
|
231
|
+
- **divider** *(optional, per-row)* — pass `divider: false` to suppress the row's bottom hairline. Use when a visual group ends mid-stack and the divider would visually fence off the next group from its label.
|
|
232
|
+
|
|
233
|
+
## States
|
|
234
|
+
|
|
235
|
+
No `selected` state — selection belongs to the [Radio sub](./radio.md).
|
|
236
|
+
|
|
237
|
+
## Focus indicator
|
|
238
|
+
|
|
239
|
+
Inward 3-layer ring inside the row's bounds — see the family-wide [Focus indicator](./list.md#cross-sub-contract). The preview pins **My channels** to its focused state via `forcedState: 'focused'` for static inspection.
|
|
240
|
+
|
|
241
|
+
```preview
|
|
242
|
+
list/focus-indicator
|
|
243
|
+
---
|
|
244
|
+
import { List } from '@teamblind-chorus/ui';
|
|
245
|
+
|
|
246
|
+
<List
|
|
247
|
+
items={[
|
|
248
|
+
{ value: 'profile', label: 'Profile', supportingText: 'Display name, avatar, bio', nav: true },
|
|
249
|
+
{ value: 'channels', label: 'My channels', supportingText: '12 joined · 3 muted', nav: true, forcedState: 'focused' },
|
|
250
|
+
{ value: 'notif', label: 'Notifications', nav: true },
|
|
251
|
+
{ value: 'privacy', label: 'Privacy', nav: true },
|
|
252
|
+
{ value: 'account', label: 'Account', nav: true },
|
|
253
|
+
]}
|
|
254
|
+
/>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Behavior
|
|
258
|
+
|
|
259
|
+
- **Keyboard navigation.** Arrow ↑ / ↓ moves focus between rows; Home / End jump to first / last.
|
|
260
|
+
- **Row click target.** The whole row is clickable when `onClick` is bound to the item. The leading thumbnail is never a separate hit target.
|
|
261
|
+
- **Trailing slot is its own hit target.** Clicks inside `trailingIcon` stop propagating before they reach the row — wire a favorite / mute / pin toggle there without it committing the row's primary action.
|
|
262
|
+
</content>
|