@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,201 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../spec.schema.json",
|
|
3
|
+
"name": "List",
|
|
4
|
+
"family": "list",
|
|
5
|
+
"subcomponent": "accordion",
|
|
6
|
+
"description": "Expandable-row List sub-component. Each `Accordion.Item` renders a List-row trigger (label + auto-rendered 16px chevron that rotates `0°` → `180°` on expand) over a content body that paints below when open. Row geometry, typography, divider, state overlays, and inward focus ring all delegate to the [family-wide rules](./list.md); this sub adds the expand/collapse contract and the top group-divider that separates the open trigger from its child rows. The family stays **edge-to-edge** (`layoutInset: full-bleed`): rows pay their own `16px inline / 8px block` padding via `layout.container.*`. Expanded content is indented an additional 16px on the leading edge so the body reads as nested INSIDE the trigger's label column, and carries a 40px min-height to keep short bodies on a touch-target rhythm. The chevron is decorative — the entire trigger header is the click target.",
|
|
7
|
+
"element": "div",
|
|
8
|
+
"props": {
|
|
9
|
+
"type": {
|
|
10
|
+
"type": "enum",
|
|
11
|
+
"values": ["single", "multiple"],
|
|
12
|
+
"default": "single",
|
|
13
|
+
"description": "`single` allows one open item at a time; `multiple` allows any number. Affects how `value` and `defaultValue` are typed."
|
|
14
|
+
},
|
|
15
|
+
"value": {
|
|
16
|
+
"type": "union",
|
|
17
|
+
"members": ["string", "string[]"],
|
|
18
|
+
"optional": true,
|
|
19
|
+
"description": "Controlled open-state. `string` for `type='single'`, `string[]` for `type='multiple'`. Pair with `onValueChange`."
|
|
20
|
+
},
|
|
21
|
+
"defaultValue": {
|
|
22
|
+
"type": "union",
|
|
23
|
+
"members": ["string", "string[]"],
|
|
24
|
+
"optional": true,
|
|
25
|
+
"description": "Uncontrolled initial open-state. Ignored when `value` is also passed."
|
|
26
|
+
},
|
|
27
|
+
"onValueChange": {
|
|
28
|
+
"type": "function",
|
|
29
|
+
"optional": true,
|
|
30
|
+
"description": "Called with the next open-state (`string | null` for single, `string[]` for multiple) when the user toggles an item."
|
|
31
|
+
},
|
|
32
|
+
"collapsible": {
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"default": true,
|
|
35
|
+
"description": "Single-mode only. When `false`, an open item cannot be collapsed by clicking its own trigger — one item is always open. Ignored when `type='multiple'`."
|
|
36
|
+
},
|
|
37
|
+
"embedded": {
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"default": false,
|
|
40
|
+
"description": "Composition mode flag inherited from the List family. When `true` (or when the Accordion is a direct child of `.chorus-carousel` / `.chorus-feed`), enters embedded mode — chrome defers to the host. See `compositionModes` in `list.family.json`."
|
|
41
|
+
},
|
|
42
|
+
"children": {
|
|
43
|
+
"type": "node",
|
|
44
|
+
"required": true,
|
|
45
|
+
"description": "One or more `Accordion.Item` children."
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"itemProps": {
|
|
49
|
+
"value": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"required": true,
|
|
52
|
+
"description": "Unique identifier used to track the item's open-state in the Accordion."
|
|
53
|
+
},
|
|
54
|
+
"label": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"required": true,
|
|
57
|
+
"description": "Trigger header text. Single line; wraps to a second line when long, no truncation."
|
|
58
|
+
},
|
|
59
|
+
"disabled": {
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"default": false
|
|
62
|
+
},
|
|
63
|
+
"strong": {
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"default": false,
|
|
66
|
+
"description": "Per-item trigger-label-emphasis opt-in (family-wide contract — see [list.md § Cross-sub contract](./list.md)). When `true`, the trigger'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 (e.g., the active section in a directory accordion)."
|
|
67
|
+
},
|
|
68
|
+
"children": {
|
|
69
|
+
"type": "node",
|
|
70
|
+
"required": true,
|
|
71
|
+
"description": "Content body rendered when the item is open."
|
|
72
|
+
},
|
|
73
|
+
"forcedState": {
|
|
74
|
+
"type": "literal",
|
|
75
|
+
"values": ["hovered", "pressed", "focused"],
|
|
76
|
+
"optional": true,
|
|
77
|
+
"description": "Docs-only — pins the trigger to a single visual state via `data-force-state`. Not for production use."
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"slots": {
|
|
81
|
+
"container": {
|
|
82
|
+
"required": true,
|
|
83
|
+
"description": "Outer stack. Transparent fill so the host surface tone reads through. `role='region'` carries the accordion's accessible name (`aria-label`).",
|
|
84
|
+
"intrinsic": true
|
|
85
|
+
},
|
|
86
|
+
"item": {
|
|
87
|
+
"required": true,
|
|
88
|
+
"description": "Single expandable row. Hairline `outlineVariant` divider inset 16px (`layout.container.md`) on BOTH the leading and trailing edges, painted as an absolutely-positioned `::after` overlay on every row except the last — matching the family-wide List row divider rule. `data-state='open' | 'closed'` reflects the current open-state.",
|
|
89
|
+
"intrinsic": true
|
|
90
|
+
},
|
|
91
|
+
"trigger": {
|
|
92
|
+
"required": true,
|
|
93
|
+
"description": "Header button. Holds the label and the auto-rendered trailing chevron. `role='button'`, `aria-expanded` reflects open-state, `aria-controls` references the content region. Same geometry as a List row (48px min-height, 8px block / 16px inline padding).",
|
|
94
|
+
"intrinsic": true
|
|
95
|
+
},
|
|
96
|
+
"label": {
|
|
97
|
+
"required": true,
|
|
98
|
+
"description": "Trigger label. `16px / Regular / onSurface` — matches the List family `label` spec. Wraps to a second line; no truncation.",
|
|
99
|
+
"accepts": ["text"]
|
|
100
|
+
},
|
|
101
|
+
"chevron": {
|
|
102
|
+
"required": true,
|
|
103
|
+
"description": "Auto-rendered 16px `ChevronDownIcon` at `onSurfaceVariant`. Rotates from `0°` to `180°` over 120ms `ease-out` on expand. Decorative.",
|
|
104
|
+
"intrinsic": true
|
|
105
|
+
},
|
|
106
|
+
"content": {
|
|
107
|
+
"required": true,
|
|
108
|
+
"description": "Body region. Paints below the trigger when open; uses `hidden` attribute when closed.\n\nTwo padding modes by content kind:\n\n- **Prose body** (text, icon, button, form-field): inline padding is `32px leading / 16px trailing` — one extra `layout.container.md` of indent on the leading edge so the prose reads as nested INSIDE the trigger's label column. `min-height: 40px` keeps short single-line bodies on a touch-target rhythm.\n- **Embedded row group** (`<List embedded>` or another `<Accordion embedded>`): inline padding is `16px leading / 0 trailing` — the leading indent is paid once by the body (sub-list rows align one container.md inside the trigger's label column) and the trailing rail is paid by the row's own inline padding, so the sub-list stretches flush to the accordion's right edge without a double-paid gutter. The body also paints a hairline `outlineVariant` divider at its TOP via a `::before` overlay (inset 16px on both inline edges, matching the inter-item divider rule) so the parent trigger and the child rows read as a parent ↔ child hierarchy.\n\nThe two modes are selected automatically via `:has([data-embedded='true'])` — call sites do not pass a mode prop; dropping a `<List embedded>` (or nested `<Accordion embedded>`) into the content body switches the body to compact-host geometry.",
|
|
109
|
+
"accepts": ["text", "icon", "button", "list", "form-field"]
|
|
110
|
+
},
|
|
111
|
+
"groupDivider": {
|
|
112
|
+
"required": false,
|
|
113
|
+
"description": "Hairline `outlineVariant` rule painted at the TOP edge of the open content body via a `::before` overlay when the body hosts a `<List embedded>` (or any same-kind row group). Inset 16px (`layout.container.md`) on both inline edges, matching the inter-item divider. Distinguishes the parent trigger from the child group so the hierarchy reads visually. Omitted for prose bodies.",
|
|
114
|
+
"intrinsic": true
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"sizing": {
|
|
118
|
+
"triggerMinHeight": "ref.space.600",
|
|
119
|
+
"triggerPaddingBlock": "sys.layout.container.xs",
|
|
120
|
+
"triggerPaddingInline": "sys.layout.container.md",
|
|
121
|
+
"triggerLabelTypo": "sys.typo.body.md",
|
|
122
|
+
"triggerLabelColor": "sys.color.onSurface",
|
|
123
|
+
"chevronSize": "sys.icon.md",
|
|
124
|
+
"chevronColor": "sys.color.onSurfaceVariant",
|
|
125
|
+
"chevronGap": "sys.layout.inline.md",
|
|
126
|
+
"chevronRotationDuration": "120ms",
|
|
127
|
+
"chevronRotationTiming": "ease-out",
|
|
128
|
+
"contentPaddingBlock": "sys.layout.container.xs",
|
|
129
|
+
"contentProsePaddingInlineStart": "calc(sys.layout.container.md * 2)",
|
|
130
|
+
"contentProsePaddingInlineEnd": "sys.layout.container.md",
|
|
131
|
+
"contentEmbeddedPaddingInlineStart": "sys.layout.container.md",
|
|
132
|
+
"contentEmbeddedPaddingInlineEnd": "0",
|
|
133
|
+
"contentIndent": "sys.layout.container.md",
|
|
134
|
+
"contentMinHeight": "ref.space.500",
|
|
135
|
+
"contentBodyTypo": "sys.typo.body.sm",
|
|
136
|
+
"contentBodyColor": "sys.color.onSurfaceVariant",
|
|
137
|
+
"embeddedRowLabelTypo": "sys.typo.body.sm",
|
|
138
|
+
"embeddedRowMinHeight": "ref.space.500",
|
|
139
|
+
"embeddedRowDivider": "none",
|
|
140
|
+
"dividerWidth": "sys.borderWidth.hairline",
|
|
141
|
+
"dividerColor": "sys.color.outlineVariant",
|
|
142
|
+
"dividerInsetInline": "sys.layout.container.md",
|
|
143
|
+
"groupDividerWidth": "sys.borderWidth.hairline",
|
|
144
|
+
"groupDividerColor": "sys.color.outlineVariant",
|
|
145
|
+
"groupDividerInsetInline": "sys.layout.container.md"
|
|
146
|
+
},
|
|
147
|
+
"appearances": {
|
|
148
|
+
"default": {
|
|
149
|
+
"background": "transparent",
|
|
150
|
+
"note": "The only appearance — Accordion paints no fill of its own. The host surface tone reads through every row. Reach for a wrapping [Carousel](../carousel/carousel.md) when the accordion needs its own labelled region."
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"states": {
|
|
154
|
+
"default": { "overlay": null },
|
|
155
|
+
"hovered": {
|
|
156
|
+
"overlay": { "color": "label", "opacity": "sys.state.hover" }
|
|
157
|
+
},
|
|
158
|
+
"pressed": {
|
|
159
|
+
"overlay": { "color": "label", "opacity": "sys.state.pressed" }
|
|
160
|
+
},
|
|
161
|
+
"disabled": {
|
|
162
|
+
"containerOpacity": "sys.state.disabled",
|
|
163
|
+
"pointerEvents": "none"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"focusIndicator": {
|
|
167
|
+
"description": "Keyboard-focus visual painted as an inward 3-layer ring inside the trigger's footprint. Composes over whichever lifecycle state the trigger is in.",
|
|
168
|
+
"composition": "inward",
|
|
169
|
+
"compositionReason": "Items tile flush with only a hairline `outlineVariant` divider between them; an outward ring would overlap the divider and the neighbouring row.",
|
|
170
|
+
"ring": {
|
|
171
|
+
"outerWidth": "sys.borderWidth.thin",
|
|
172
|
+
"outerColor": "sys.color.focus",
|
|
173
|
+
"insetWidth": "sys.borderWidth.hairline",
|
|
174
|
+
"insetColor": "sys.color.focusInset",
|
|
175
|
+
"implementation": "inset box-shadow on the trigger's `::before` overlay."
|
|
176
|
+
},
|
|
177
|
+
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
178
|
+
},
|
|
179
|
+
"behavior": {
|
|
180
|
+
"clickTarget": "Whole trigger header is the click target. The chevron is decorative — never a separate hit target.",
|
|
181
|
+
"keyboardActivation": "Space and Enter both toggle the item when the trigger holds focus. Arrow up/down moves focus between triggers.",
|
|
182
|
+
"single": "In `type='single'` mode, opening an item closes the previously open one. With `collapsible={true}` (default), clicking the open item closes it too — leaving zero open items.",
|
|
183
|
+
"multiple": "In `type='multiple'` mode, each trigger toggles its own item independently. `value` is a string array.",
|
|
184
|
+
"wrapNotTruncate": "Trigger label wraps onto a second line when long; the row grows. Truncation would hide the affordance from being scannable.",
|
|
185
|
+
"chevronRotation": "120ms `ease-out` transform on the chevron's rotation. Under `prefers-reduced-motion: reduce` the rotation snaps instantly."
|
|
186
|
+
},
|
|
187
|
+
"forbidden": [
|
|
188
|
+
"item rendered as its own outlined rounded surface — Accordion tiles items flush with a hairline divider; outlined-card stacks are NavCardGroup, not an accordion",
|
|
189
|
+
"Accordion wrapped in a parent `padding-inline` / `px-*` / `style={{ paddingInline: … }}` container outside a documented bounded surface — the family is full-bleed and double-pays the page gutter when an outer rail is also applied",
|
|
190
|
+
"prose content body painted flush to the trigger's leading edge — text / icon / button / form-field bodies MUST sit at `32px` inline-start (trigger inline padding + a further `layout.container.md` indent) so the prose reads as nested inside the row. Embedded row-group bodies are a separate contract (16 leading / 0 trailing) — see `contentEmbeddedPaddingInline*`.",
|
|
191
|
+
"expanded content body collapsed below `40px` min-height — short single-line bodies still need to land on a touch-target rhythm so the row's open-state reads as distinct from its closed-state",
|
|
192
|
+
"divider painted full-bleed (no inline inset) — the rule must be inset 16px on BOTH edges; a full-bleed rule reads as separating the container, not the rows",
|
|
193
|
+
"chevron replaced with a plus / minus glyph — the family uses the rotating chevron contract; alt glyphs read as a different affordance (an add action, a remove action)",
|
|
194
|
+
"content body painted with a chromatic fill (primaryContainer, etc.) — the body inherits the host surface tone; chromatic emphasis on an accordion body reads as a Banner aside, not a content section",
|
|
195
|
+
"destructive commit fired inline inside the content body without a Button wrapper — destructive commits open a Dialog / BottomSheet, never fire from a raw content link",
|
|
196
|
+
"child `<List>` dropped into the content body WITHOUT `embedded={true}` — the list keeps its own surface chrome + inline padding and double-pays the body's 32px leading indent, leaving child rows at a deeper inset than the trigger's label column. Use `embedded` so the list inherits the body's edges.",
|
|
197
|
+
"child list group rendered WITHOUT the top `outlineVariant` divider — the parent trigger and the first child row tile flush, collapsing the parent ↔ child hierarchy into one stack. The top divider is the visual contract that the group is nested, not co-equal.",
|
|
198
|
+
"top group divider replaced with a `border-top:` on the list container — `border` reflows the box and breaks the inward focus ring on the first list row. Paint via the body's `::before` overlay (no-layout stroke), never a layout border.",
|
|
199
|
+
"trigger label sized below 16px or set to Semibold — Accordion is a List sub and inherits the family `label` spec (`16 / Regular / onSurface`); a 14/Semibold trigger reads as a different family from the rows it nests"
|
|
200
|
+
]
|
|
201
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Entry
|
|
2
|
+
|
|
3
|
+
Directory-entry [List](./list.md) sub — an entity row pairing an optional leading [Thumbnail](../thumbnail/thumbnail.md) avatar with an identity group (label, optional inline `count` Badge, optional `secondary` line and `description`). Same click semantics as the [Standard sub](./standard.md); geometry, state overlays, and inward focus ring delegate to the [family-wide rules](./list.md). Slots and Anatomy below carry the rung and slot detail.
|
|
4
|
+
|
|
5
|
+
**Reach for this when** rendering an entity-row directory — follow suggestion, member directory, subscription / channel / topic / playlist directory, mention / recipient picker, entity search result — or, with `thumbnail` omitted, label-only nav rows (settings menu, category index). **Skip when** you need a Feed-specific attribution cluster (use [Metadata](../metadata/metadata.md) for Post / Ad card heads).
|
|
6
|
+
|
|
7
|
+
**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.md` / `layout.container.xs`; 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).
|
|
8
|
+
|
|
9
|
+
## Default
|
|
10
|
+
|
|
11
|
+
Directory entry rows — pick the Thumbnail rung via the **Size** dropdown (`xlarge` 56 → `small` 32). Row payload (label + stacked secondary + single-line description + trailing follow toggle) stays constant; only the leading avatar footprint changes. At `xlarge` the inter-row divider anchors to the text column (16 + 56 + 12 = 84) so the rule reads as separating identity columns under the wider avatar.
|
|
12
|
+
|
|
13
|
+
```preview
|
|
14
|
+
list/entry
|
|
15
|
+
---
|
|
16
|
+
import { Button, List } from '@teamblind-chorus/ui';
|
|
17
|
+
|
|
18
|
+
<List
|
|
19
|
+
variant="entry"
|
|
20
|
+
size="medium"
|
|
21
|
+
items={[
|
|
22
|
+
{
|
|
23
|
+
value: 'sourdough-bakers',
|
|
24
|
+
label: 'Sourdough Bakers',
|
|
25
|
+
secondary: '12.4K Followers',
|
|
26
|
+
description: 'Open-crumb obsession, cold-proof timelines, weekend bakes.',
|
|
27
|
+
thumbnail: { src: '/placeholder.png', alt: 'Sourdough Bakers' },
|
|
28
|
+
trailingIcon: (
|
|
29
|
+
<Button variant="toggle" onClick={() => {}}>Follow</Button>
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: 'indie-game-devs',
|
|
34
|
+
label: 'Indie Game Devs',
|
|
35
|
+
secondary: '8,210 Followers',
|
|
36
|
+
description: 'Solo dev diaries, first-release postmortems, jam recaps.',
|
|
37
|
+
thumbnail: { src: '/placeholder.png', alt: 'Indie Game Devs' },
|
|
38
|
+
trailingIcon: (
|
|
39
|
+
<Button variant="toggle" onClick={() => {}}>Follow</Button>
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
]}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Use cases
|
|
47
|
+
|
|
48
|
+
### With trailing Text Button (compact attribution row)
|
|
49
|
+
|
|
50
|
+
Pairs a leading Thumbnail + label with a trailing [Text Button](../button/text.md) (`size="xsmall"`) and nothing else — the most compact *image + label + trailing-text-button* identity-plus-commit row. The button carries the **link-affordance follow / invite** shape: `appearance="accent"` while inactive (`Follow`) so the commit reads as the loudest call, flipping to `appearance="default"` once active (`Following`) so the followed state recedes. Distinct from the [Default](#default)'s `variant="toggle"` Follow chip — reach for the text button when the row reads as an attribution line (channel / author + follow) rather than a directory entry with a pill control. This is the exact combo the [Post carousel](../carousel/post.md) card pins to the top of each post as its attached attribution element.
|
|
51
|
+
|
|
52
|
+
```preview
|
|
53
|
+
list/entry-with-text-button
|
|
54
|
+
---
|
|
55
|
+
import { Button, List } from '@teamblind-chorus/ui';
|
|
56
|
+
|
|
57
|
+
<List
|
|
58
|
+
variant="entry"
|
|
59
|
+
size="medium"
|
|
60
|
+
items={[
|
|
61
|
+
{
|
|
62
|
+
value: 'beauty-talk',
|
|
63
|
+
label: 'Beauty Talk',
|
|
64
|
+
thumbnail: { src: '/placeholder.png', alt: 'Beauty Talk' },
|
|
65
|
+
trailingIcon: (
|
|
66
|
+
<Button variant="text" size="xsmall" appearance="accent" onClick={() => {}}>Follow</Button>
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
value: 'plant-people',
|
|
71
|
+
label: 'Plant People',
|
|
72
|
+
thumbnail: { src: '/placeholder.png', alt: 'Plant People' },
|
|
73
|
+
trailingIcon: (
|
|
74
|
+
<Button variant="text" size="xsmall" onClick={() => {}}>Following</Button>
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
]}
|
|
78
|
+
/>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### With trailing star toggle
|
|
82
|
+
|
|
83
|
+
Uses the **single-shape fill-only contract**: always `<StarFillIcon>`, color flips by state (active = `var(--sys-color-icon-yellow)`, inactive = `var(--sys-color-icon-muted)`). Shape stays constant so the trailing rail keeps a stable hit-target footprint — never swap between outline (`StarIcon`) and fill (`StarFillIcon`) for the same affordance. Rows with a trailing affordance default to `size="small"` (32) — the smaller leading footprint keeps the trailing rail visually balanced.
|
|
84
|
+
|
|
85
|
+
```preview
|
|
86
|
+
list/entry-with-star
|
|
87
|
+
---
|
|
88
|
+
import { Badge, Button, List } from '@teamblind-chorus/ui';
|
|
89
|
+
import { StarFillIcon } from '@teamblind-chorus/ui/icons';
|
|
90
|
+
|
|
91
|
+
<List
|
|
92
|
+
variant="entry"
|
|
93
|
+
size="small"
|
|
94
|
+
items={[
|
|
95
|
+
{
|
|
96
|
+
value: 'sourdough',
|
|
97
|
+
label: 'Sourdough Bakers',
|
|
98
|
+
count: <Badge count={12} />,
|
|
99
|
+
thumbnail: { src: '/placeholder.png', alt: 'Sourdough Bakers' },
|
|
100
|
+
trailingIcon: (
|
|
101
|
+
<Button
|
|
102
|
+
variant="icon"
|
|
103
|
+
size="medium"
|
|
104
|
+
aria-label="Favorited"
|
|
105
|
+
aria-pressed="true"
|
|
106
|
+
icon={<StarFillIcon />}
|
|
107
|
+
style={{ color: 'var(--sys-color-icon-yellow)' }}
|
|
108
|
+
onClick={() => {}}
|
|
109
|
+
/>
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
value: 'stocks',
|
|
114
|
+
label: 'Stocks & Investing',
|
|
115
|
+
count: <Badge count={142} />,
|
|
116
|
+
thumbnail: { src: '/placeholder.png', alt: 'Stocks & Investing' },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
value: 'movie-talk',
|
|
120
|
+
label: 'Movie Talk',
|
|
121
|
+
count: <Badge count={24} />,
|
|
122
|
+
thumbnail: { src: '/placeholder.png', alt: 'Movie Talk' },
|
|
123
|
+
trailingIcon: (
|
|
124
|
+
<Button
|
|
125
|
+
variant="icon"
|
|
126
|
+
size="medium"
|
|
127
|
+
aria-label="Favorite"
|
|
128
|
+
aria-pressed="false"
|
|
129
|
+
icon={<StarFillIcon />}
|
|
130
|
+
style={{ color: 'var(--sys-color-icon-muted)' }}
|
|
131
|
+
onClick={() => {}}
|
|
132
|
+
/>
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
]}
|
|
136
|
+
/>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### As nav option (trailing chevron Icon Button)
|
|
140
|
+
|
|
141
|
+
The trailing slot carries a default [Icon Button](../button/icon.md) (`variant="icon"`, `size="medium"`) filled with a right-pointing chevron — the canonical nav-option drill-in affordance. Reach for it when the row routes to a sub-page and you still want to expose an identity-bearing thumbnail (workspace switch, channel directory drill-in). For pure label-only nav stacks, omit `thumbnail` instead — see [the label-only case below](#label-only-no-thumbnail).
|
|
142
|
+
|
|
143
|
+
```preview
|
|
144
|
+
list/entry-as-nav-option
|
|
145
|
+
---
|
|
146
|
+
import { Button, List } from '@teamblind-chorus/ui';
|
|
147
|
+
import { ChevronRightIcon } from '@teamblind-chorus/ui/icons';
|
|
148
|
+
|
|
149
|
+
<List
|
|
150
|
+
variant="entry"
|
|
151
|
+
size="medium"
|
|
152
|
+
items={[
|
|
153
|
+
{
|
|
154
|
+
value: 'sourdough',
|
|
155
|
+
label: 'Sourdough Bakers',
|
|
156
|
+
secondary: '12.4K Followers',
|
|
157
|
+
thumbnail: { src: '/placeholder.png', alt: 'Sourdough Bakers' },
|
|
158
|
+
trailingIcon: (
|
|
159
|
+
<Button
|
|
160
|
+
variant="icon"
|
|
161
|
+
size="medium"
|
|
162
|
+
aria-label="Open Sourdough Bakers"
|
|
163
|
+
icon={<ChevronRightIcon />}
|
|
164
|
+
onClick={() => {}}
|
|
165
|
+
/>
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
value: 'indie-game-devs',
|
|
170
|
+
label: 'Indie Game Devs',
|
|
171
|
+
secondary: '8,210 Followers',
|
|
172
|
+
thumbnail: { src: '/placeholder.png', alt: 'Indie Game Devs' },
|
|
173
|
+
trailingIcon: (
|
|
174
|
+
<Button
|
|
175
|
+
variant="icon"
|
|
176
|
+
size="medium"
|
|
177
|
+
aria-label="Open Indie Game Devs"
|
|
178
|
+
icon={<ChevronRightIcon />}
|
|
179
|
+
onClick={() => {}}
|
|
180
|
+
/>
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
]}
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Label only (no thumbnail)
|
|
188
|
+
|
|
189
|
+
Omit `thumbnail` on a row to collapse the leading column — the label sits flush at the 16 inline rail and the row reads as a label-only entry. Reach for it on settings menus, category indexes, and *pick a sub-page* stacks. Pair with a trailing chevron Icon Button to assemble the canonical nav-option row; this is the shape [NavList](../nav-list/nav-list.md) bundles under its header.
|
|
190
|
+
|
|
191
|
+
```preview
|
|
192
|
+
list/entry-label-only
|
|
193
|
+
---
|
|
194
|
+
import { Button, List } from '@teamblind-chorus/ui';
|
|
195
|
+
import { ChevronRightIcon } from '@teamblind-chorus/ui/icons';
|
|
196
|
+
|
|
197
|
+
<List
|
|
198
|
+
variant="entry"
|
|
199
|
+
items={[
|
|
200
|
+
{
|
|
201
|
+
value: 'location',
|
|
202
|
+
label: 'Location',
|
|
203
|
+
trailingIcon: (
|
|
204
|
+
<Button variant="icon" size="medium" aria-label="Open Location" icon={<ChevronRightIcon />} onClick={() => {}} />
|
|
205
|
+
),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
value: 'job',
|
|
209
|
+
label: 'Job Function',
|
|
210
|
+
trailingIcon: (
|
|
211
|
+
<Button variant="icon" size="medium" aria-label="Open Job Function" icon={<ChevronRightIcon />} onClick={() => {}} />
|
|
212
|
+
),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
value: 'learning',
|
|
216
|
+
label: 'Learning & Advising',
|
|
217
|
+
trailingIcon: (
|
|
218
|
+
<Button variant="icon" size="medium" aria-label="Open Learning & Advising" icon={<ChevronRightIcon />} onClick={() => {}} />
|
|
219
|
+
),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
value: 'money',
|
|
223
|
+
label: 'Money',
|
|
224
|
+
trailingIcon: (
|
|
225
|
+
<Button variant="icon" size="medium" aria-label="Open Money" icon={<ChevronRightIcon />} onClick={() => {}} />
|
|
226
|
+
),
|
|
227
|
+
},
|
|
228
|
+
]}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Slots
|
|
233
|
+
|
|
234
|
+
- **container** — outer vertical stack (delegates to family).
|
|
235
|
+
- **row** — single list item; whole row is the click target.
|
|
236
|
+
- **leading** *(optional)* — [Thumbnail](../thumbnail/thumbnail.md) at the list's `size` rung (32 / 40 / 48 / 56). `thumbnail` props forward verbatim. Omit per row to collapse the leading column entirely — the leading→text gap (12) also drops, and the label sits flush at the 16 inline rail. Mix-and-match per row is supported.
|
|
237
|
+
- **label** — primary row text. `sys.typo.label.md` (14 / Semibold) / `sys.color.onSurface`. Pairs flush with `count` on the primary line.
|
|
238
|
+
- **count** *(optional)* — inline node painted to the right of the label on the same line (canonical: `<Badge count={n} />`). Separated by `sys.layout.inline.sm` (4); label shrinks first so a long name truncates against the count.
|
|
239
|
+
- **secondary** *(optional)* — stacked meta line below the label inside the identity group (follower count, location). `sys.typo.label.sm` (12 / Semibold) / `sys.color.onSurface`. Tiles flush with the label — line-height-only spacing, no margin — so the two lines read as one tight identity block.
|
|
240
|
+
- **description** *(optional)* — single-line caption-tone supporting line below the identity group. `sys.typo.label.sm` (12 / Semibold) / `sys.color.onSurfaceVariant`. Separated from the identity group by `ref.space.25` (2). Truncates with ellipsis; never wraps.
|
|
241
|
+
- **trailingIcon** *(optional, per-row)* — consumer-supplied node at the trailing edge. Canonical fills: `<Button variant="toggle">` (Follow chip), `<Button variant="text" size="xsmall" appearance="accent">` (Follow / Invite link-affordance — the [attribution-row case](#with-trailing-text-button-compact-attribution-row)), `<Button variant="icon">` (favorite / overflow), `<Badge>`. Its own hit target — clicks stop propagating before reaching the row.
|
|
242
|
+
- **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.
|
|
243
|
+
|
|
244
|
+
## Anatomy
|
|
245
|
+
|
|
246
|
+
| Slot | Token bindings |
|
|
247
|
+
|----------------------------|----------------|
|
|
248
|
+
| row container | Block / inline padding (`layout.container.md` inline rail across every rung). Block: `8px` (`layout.container.xs`) at `small` / `medium` / `large`, `12px` (`layout.container.sm`) at `xlarge` — the 56 rung's identity stack reads denser. `min-height: 48` (`ref.space.600`). |
|
|
249
|
+
| leading thumbnail *(optional)* | 32 (`size="small"`) / 40 (`size="medium"`) / 48 (`size="large"`) / 56 (`size="xlarge"`). Omit per row to collapse the leading column. |
|
|
250
|
+
| leading → text column | `sys.layout.inline.lg` (12) when leading is present — Entry-specific override of the family-wide `inline.md` (8) row gap. Drops to `0` when `thumbnail` is omitted. |
|
|
251
|
+
| label | `sys.typo.label.md` (14 / Semibold) / `onSurface`, single-line ellipsis |
|
|
252
|
+
| label → count | `sys.layout.inline.sm` (4) |
|
|
253
|
+
| count | inline node — canonical `<Badge>` |
|
|
254
|
+
| label → secondary | `0` — line-height only |
|
|
255
|
+
| secondary | `sys.typo.label.sm` (12 / Semibold) / `onSurface`, single-line ellipsis |
|
|
256
|
+
| identity group → description | `ref.space.25` (2) |
|
|
257
|
+
| description | `sys.typo.label.sm` (12 / Semibold) / `onSurfaceVariant`, single-line ellipsis |
|
|
258
|
+
| text column → trailing | `sys.layout.inline.sm` (4) |
|
|
259
|
+
| trailingIcon | `<Button variant="icon">`, `<Button variant="toggle">`, `<Button variant="text" appearance="accent">`, or `<Badge>` |
|
|
260
|
+
| inter-row divider | `1px` `outlineVariant`. Default (`small` / `medium` / `large`): `16` inset from both edges. `xlarge`: leading inset anchors to the text column (`16 + 56 + 12 = 84`) so the rule reads as separating identity columns; trailing inset stays at `16`. **Label-only rows** (no thumbnail): leading inset falls back to the default `16` regardless of `size`. |
|
|
261
|
+
|
|
262
|
+
## States
|
|
263
|
+
|
|
264
|
+
No `selected` state. State overlays (hover / pressed / disabled) delegate to the [family-wide rules](./list.md#cross-sub-contract).
|
|
265
|
+
|
|
266
|
+
## Focus indicator
|
|
267
|
+
|
|
268
|
+
Inward 3-layer ring inside the row's bounds — see the family-wide [Focus indicator](./list.md#cross-sub-contract). The whole row is the keyboard target; a trailing toggle / icon button carries its own focus ring per its component spec.
|
|
269
|
+
|
|
270
|
+
## Behavior
|
|
271
|
+
|
|
272
|
+
- **Thumbnail is optional, per row.** Drop `thumbnail` from a row to collapse the leading column and the `leading → text` (12) gap; the label sits flush at the 16 inline rail. For pure label-only nav stacks, [NavList](../nav-list/nav-list.md) bundles this shape under a header.
|
|
273
|
+
- **Identity group is tight.** Label + (inline count) on the primary line, optional `secondary` stacked flush below — line-height-only spacing — so the entire identity group reads as one block.
|
|
274
|
+
- **Divider inset switches at `xlarge`.** Default rungs: hairline inset `16` from both edges so the rule reads as separating *content*. At `xlarge` the leading inset anchors to the text column (`16 + 56 + 12 = 84`) — the 56 avatar is large enough that a row-edge divider reads as a hard line under the avatar.
|
|
275
|
+
- **Block padding bumps at `xlarge`.** Default rungs use `container.xs` (8); `xlarge` bumps to `container.sm` (12) — the 56 rung's identity stack needs an extra step of breathing room.
|
|
276
|
+
- **Description is the supporting layer.** Sits below the identity group with `ref.space.25` (2) of separation. Always single-line; truncates. The row never grows to fit longer copy.
|
|
277
|
+
- **Truncates, never wraps.** Label and description both truncate; trailing slot is never pushed off-row by long text.
|
|
278
|
+
- **Trailing slot is its own hit target.** Clicks inside `trailingIcon` stop propagating before reaching the row — wire favorite / follow / overflow there without committing the row's primary action.
|
|
279
|
+
- **Keyboard navigation.** Arrow ↑ / ↓ moves focus between rows; Home / End jump to first / last.
|
|
280
|
+
- **Row content is the shared `EntryRow` atom.** Each row's inner cluster — leading Thumbnail + identity column (label + optional inline `count` + optional `secondary`) + optional `description` + trailing slot — is rendered by the shared `EntryRow` component (class `chorus-entry-row`). The List row wrapper owns only the row geometry (16 / 8 padding, 48 min-height, divider, hover / focus / keyboard). The [Post carousel](../carousel/post.md) card header renders the **same** `EntryRow` (passing `verified` for the inline trust mark), so the directory-entry *image + label + trailing action* combo has a single definition.
|