@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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/agents/AGENTS.md +143 -0
  4. package/agents/DESIGN.md +1311 -0
  5. package/agents/LOVABLE.md +472 -0
  6. package/agents/anti-patterns.md +533 -0
  7. package/agents/catalog.md +232 -0
  8. package/agents/components/avatar-rail/avatar-rail.family.json +46 -0
  9. package/agents/components/avatar-rail/avatar-rail.md +103 -0
  10. package/agents/components/avatar-rail/avatar-rail.spec.json +160 -0
  11. package/agents/components/badge/badge.family.json +45 -0
  12. package/agents/components/badge/badge.md +10 -0
  13. package/agents/components/badge/role.md +100 -0
  14. package/agents/components/badge/role.spec.json +75 -0
  15. package/agents/components/badge/update.md +132 -0
  16. package/agents/components/badge/update.spec.json +114 -0
  17. package/agents/components/banner/banner.family.json +28 -0
  18. package/agents/components/banner/banner.md +136 -0
  19. package/agents/components/banner/banner.spec.json +136 -0
  20. package/agents/components/bottom-sheet/bottom-sheet.family.json +29 -0
  21. package/agents/components/bottom-sheet/bottom-sheet.md +176 -0
  22. package/agents/components/bottom-sheet/bottom-sheet.spec.json +168 -0
  23. package/agents/components/bubble/bubble.family.json +29 -0
  24. package/agents/components/bubble/bubble.md +134 -0
  25. package/agents/components/bubble/bubble.spec.json +91 -0
  26. package/agents/components/button/button.family.json +76 -0
  27. package/agents/components/button/button.md +31 -0
  28. package/agents/components/button/check.md +138 -0
  29. package/agents/components/button/check.spec.json +161 -0
  30. package/agents/components/button/fab.md +161 -0
  31. package/agents/components/button/fab.spec.json +106 -0
  32. package/agents/components/button/icon.md +141 -0
  33. package/agents/components/button/icon.spec.json +164 -0
  34. package/agents/components/button/standard.md +219 -0
  35. package/agents/components/button/standard.spec.json +205 -0
  36. package/agents/components/button/text.md +186 -0
  37. package/agents/components/button/text.spec.json +215 -0
  38. package/agents/components/button/toggle.md +108 -0
  39. package/agents/components/button/toggle.spec.json +124 -0
  40. package/agents/components/button/toolbar.md +189 -0
  41. package/agents/components/button/toolbar.spec.json +109 -0
  42. package/agents/components/carousel/carousel.family.json +41 -0
  43. package/agents/components/carousel/carousel.md +40 -0
  44. package/agents/components/carousel/post.md +148 -0
  45. package/agents/components/carousel/post.spec.json +229 -0
  46. package/agents/components/carousel/profile.md +184 -0
  47. package/agents/components/carousel/profile.spec.json +219 -0
  48. package/agents/components/chip/chip.family.json +37 -0
  49. package/agents/components/chip/chip.md +10 -0
  50. package/agents/components/chip/filter.md +212 -0
  51. package/agents/components/chip/filter.spec.json +124 -0
  52. package/agents/components/chip/tag.md +137 -0
  53. package/agents/components/chip/tag.spec.json +104 -0
  54. package/agents/components/dialog/dialog.family.json +29 -0
  55. package/agents/components/dialog/dialog.md +113 -0
  56. package/agents/components/dialog/dialog.spec.json +156 -0
  57. package/agents/components/directory-list/directory-list.family.json +46 -0
  58. package/agents/components/directory-list/directory-list.md +87 -0
  59. package/agents/components/directory-list/directory-list.spec.json +104 -0
  60. package/agents/components/divider/divider.family.json +28 -0
  61. package/agents/components/divider/divider.md +78 -0
  62. package/agents/components/divider/divider.spec.json +51 -0
  63. package/agents/components/feed/ad.md +108 -0
  64. package/agents/components/feed/ad.spec.json +187 -0
  65. package/agents/components/feed/feed.family.json +48 -0
  66. package/agents/components/feed/feed.md +30 -0
  67. package/agents/components/feed/post.md +240 -0
  68. package/agents/components/feed/post.spec.json +361 -0
  69. package/agents/components/form-field/form-field.family.json +50 -0
  70. package/agents/components/form-field/form-field.md +11 -0
  71. package/agents/components/form-field/input.md +198 -0
  72. package/agents/components/form-field/input.spec.json +202 -0
  73. package/agents/components/form-field/search.md +81 -0
  74. package/agents/components/form-field/search.spec.json +135 -0
  75. package/agents/components/form-field/select.md +101 -0
  76. package/agents/components/form-field/select.spec.json +194 -0
  77. package/agents/components/form-field/textarea.md +89 -0
  78. package/agents/components/form-field/textarea.spec.json +176 -0
  79. package/agents/components/header/header.family.json +43 -0
  80. package/agents/components/header/header.md +18 -0
  81. package/agents/components/header/main.md +101 -0
  82. package/agents/components/header/main.spec.json +117 -0
  83. package/agents/components/header/sub.md +129 -0
  84. package/agents/components/header/sub.spec.json +81 -0
  85. package/agents/components/list/accordion.md +183 -0
  86. package/agents/components/list/accordion.spec.json +201 -0
  87. package/agents/components/list/entry.md +280 -0
  88. package/agents/components/list/entry.spec.json +237 -0
  89. package/agents/components/list/list.family.json +75 -0
  90. package/agents/components/list/list.md +24 -0
  91. package/agents/components/list/radio.md +144 -0
  92. package/agents/components/list/radio.spec.json +186 -0
  93. package/agents/components/list/standard.md +262 -0
  94. package/agents/components/list/standard.spec.json +221 -0
  95. package/agents/components/metadata/compact.md +69 -0
  96. package/agents/components/metadata/compact.spec.json +69 -0
  97. package/agents/components/metadata/metadata.family.json +42 -0
  98. package/agents/components/metadata/metadata.md +26 -0
  99. package/agents/components/metadata/standard.md +104 -0
  100. package/agents/components/metadata/standard.spec.json +152 -0
  101. package/agents/components/nav-card/nav-card.family.json +29 -0
  102. package/agents/components/nav-card/nav-card.md +179 -0
  103. package/agents/components/nav-card/nav-card.spec.json +161 -0
  104. package/agents/components/nav-list/nav-list.family.json +46 -0
  105. package/agents/components/nav-list/nav-list.md +91 -0
  106. package/agents/components/nav-list/nav-list.spec.json +107 -0
  107. package/agents/components/navigation-bar/main.md +201 -0
  108. package/agents/components/navigation-bar/main.spec.json +109 -0
  109. package/agents/components/navigation-bar/navigation-bar.family.json +44 -0
  110. package/agents/components/navigation-bar/navigation-bar.md +21 -0
  111. package/agents/components/navigation-bar/search.md +96 -0
  112. package/agents/components/navigation-bar/search.spec.json +142 -0
  113. package/agents/components/navigation-bar/sub.md +174 -0
  114. package/agents/components/navigation-bar/sub.spec.json +123 -0
  115. package/agents/components/page-shell/page-shell.family.json +22 -0
  116. package/agents/components/page-shell/page-shell.md +51 -0
  117. package/agents/components/profile-header/profile-header.family.json +29 -0
  118. package/agents/components/profile-header/profile-header.md +149 -0
  119. package/agents/components/profile-header/profile-header.spec.json +200 -0
  120. package/agents/components/progress/progress.family.json +27 -0
  121. package/agents/components/progress/progress.md +38 -0
  122. package/agents/components/progress/progress.spec.json +67 -0
  123. package/agents/components/side-sheet/side-sheet.family.json +30 -0
  124. package/agents/components/side-sheet/side-sheet.md +154 -0
  125. package/agents/components/side-sheet/side-sheet.spec.json +109 -0
  126. package/agents/components/skeleton/skeleton.family.json +28 -0
  127. package/agents/components/skeleton/skeleton.md +123 -0
  128. package/agents/components/skeleton/skeleton.spec.json +73 -0
  129. package/agents/components/status-tag/status-tag.family.json +26 -0
  130. package/agents/components/status-tag/status-tag.md +114 -0
  131. package/agents/components/status-tag/status-tag.spec.json +69 -0
  132. package/agents/components/suggestion-list/suggestion-list.family.json +46 -0
  133. package/agents/components/suggestion-list/suggestion-list.md +91 -0
  134. package/agents/components/suggestion-list/suggestion-list.spec.json +178 -0
  135. package/agents/components/switch/switch.family.json +27 -0
  136. package/agents/components/switch/switch.md +114 -0
  137. package/agents/components/switch/switch.spec.json +123 -0
  138. package/agents/components/tab-bar/tab-bar.family.json +27 -0
  139. package/agents/components/tab-bar/tab-bar.md +178 -0
  140. package/agents/components/tab-bar/tab-bar.spec.json +184 -0
  141. package/agents/components/tabs/rounded.md +150 -0
  142. package/agents/components/tabs/rounded.spec.json +140 -0
  143. package/agents/components/tabs/segmented.md +114 -0
  144. package/agents/components/tabs/segmented.spec.json +100 -0
  145. package/agents/components/tabs/tabs.family.json +59 -0
  146. package/agents/components/tabs/tabs.md +18 -0
  147. package/agents/components/tabs/underline.md +147 -0
  148. package/agents/components/tabs/underline.spec.json +139 -0
  149. package/agents/components/thumbnail/thumbnail.family.json +28 -0
  150. package/agents/components/thumbnail/thumbnail.md +152 -0
  151. package/agents/components/thumbnail/thumbnail.spec.json +172 -0
  152. package/agents/components/toast/toast.family.json +28 -0
  153. package/agents/components/toast/toast.md +133 -0
  154. package/agents/components/toast/toast.spec.json +89 -0
  155. package/agents/components/tooltip/tooltip.family.json +29 -0
  156. package/agents/components/tooltip/tooltip.md +139 -0
  157. package/agents/components/tooltip/tooltip.spec.json +110 -0
  158. package/agents/compose.md +240 -0
  159. package/agents/icons.json +831 -0
  160. package/agents/images.md +66 -0
  161. package/agents/manifest.json +87 -0
  162. package/agents/patterns/README.md +59 -0
  163. package/agents/patterns/actions.md +50 -0
  164. package/agents/patterns/browsing.md +52 -0
  165. package/agents/patterns/communications.md +56 -0
  166. package/agents/patterns/layout.md +72 -0
  167. package/agents/patterns/modals.md +50 -0
  168. package/agents/patterns/visual.md +55 -0
  169. package/agents/reconstruct.md +55 -0
  170. package/agents/scoped-adoption.md +111 -0
  171. package/agents/tokens.usage.json +1657 -0
  172. package/agents/usage.json +422 -0
  173. package/dist/icons/index.cjs +1332 -0
  174. package/dist/icons/index.cjs.map +1 -0
  175. package/dist/icons/index.d.cts +228 -0
  176. package/dist/icons/index.d.ts +228 -0
  177. package/dist/icons/index.js +1114 -0
  178. package/dist/icons/index.js.map +1 -0
  179. package/dist/index.cjs +5905 -0
  180. package/dist/index.cjs.map +1 -0
  181. package/dist/index.d.cts +896 -0
  182. package/dist/index.d.ts +896 -0
  183. package/dist/index.js +5847 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/styles.css +5765 -0
  186. package/eslint/README.md +79 -0
  187. package/eslint/index.js +78 -0
  188. package/eslint/rules.js +472 -0
  189. package/eslint/test.mjs +135 -0
  190. package/package.json +96 -0
  191. package/placeholder.png +0 -0
@@ -0,0 +1,896 @@
1
+ // AUTO-GENERATED by packages/ui/scripts/build-types.mjs.
2
+ // Source of truth: schema/components/<family>/<sub>.spec.json.
3
+ // Do not edit by hand — re-run the script after spec changes.
4
+
5
+ import * as React from "react";
6
+
7
+ // ── Badge (multi-sub: update, role) ──
8
+ interface BadgeUpdatePropsOwn {
9
+ variant?: "update";
10
+ /** Four rungs split across the two types. **Numeric** — `medium` / `small` carry the count label. **Dot** — `dot-md` (8×8) / `dot-sm` (6×6) drop the label entirely and paint the labelless update dot. The dot rungs ignore `count` and `children`. */
11
+ size?: "medium" | "small" | "dot-md" | "dot-sm";
12
+ /** Numeric type only. If provided, the badge formats the value: 1–99 render as the literal number, 100+ renders as `99+`. Pass children instead for non-numeric content (e.g. `NEW`). Ignored on the Dot rungs. */
13
+ count?: number;
14
+ /** Numeric type only — custom label, overrides count formatting. Ignored on the Dot rungs. */
15
+ children?: React.ReactNode;
16
+ }
17
+ export interface BadgeUpdateProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof BadgeUpdatePropsOwn>, BadgeUpdatePropsOwn {}
18
+
19
+ interface BadgeRolePropsOwn {
20
+ variant: "role";
21
+ /** `default` — tonal primaryContainer pair for ordinary role marks (Channel owner, Verified). `inverse` — high-contrast inverseSurface pair, reserved for the paid-expert PRO mark. */
22
+ appearance?: "default" | "inverse";
23
+ /** Required role / title label — short, single line, English display (Channel owner, Verified, Moderator). */
24
+ children: React.ReactNode;
25
+ }
26
+ export interface BadgeRoleProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof BadgeRolePropsOwn>, BadgeRolePropsOwn {}
27
+
28
+ export type BadgeProps =
29
+ | BadgeUpdateProps
30
+ | BadgeRoleProps;
31
+
32
+ // ── Banner (banner/banner) ──
33
+ interface BannerPropsOwn {
34
+ appearance?: "default" | "accent" | "destructive";
35
+ /** A 16 × 16 (`sys.icon.md`) glyph at the container's leading edge. Inherits the banner's foreground (`currentColor`) so the mark reads as part of the body copy. The slot occupies the body.sm line-box height so the glyph centers on the **first line** of the body — multi-line bodies keep the icon anchored to the first-line cap, not the block center. Ignored when `thumbnail` is also passed. */
36
+ icon?: React.ReactNode;
37
+ /** A leading visual rendered by [Thumbnail](../thumbnail/thumbnail.md) — used when the aside is anchored to a channel, author, or sub-brand image rather than to a glyph. Takes precedence over `icon`. */
38
+ thumbnail?: React.ReactNode;
39
+ /** { label, href? , onClick? } — a follow-through link rendered as a block child below the body. */
40
+ action?: Record<string, unknown>;
41
+ /** Body text — the explanation copy. */
42
+ children: React.ReactNode;
43
+ }
44
+ export interface BannerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof BannerPropsOwn>, BannerPropsOwn {}
45
+
46
+ // ── BottomSheet (bottom-sheet/bottom-sheet) ──
47
+ interface BottomSheetPropsOwn {
48
+ open: boolean;
49
+ onClose: (...args: any[]) => any;
50
+ title?: React.ReactNode;
51
+ body?: React.ReactNode;
52
+ /** { label, onClick } — delegates to Button appearance='primary', size='large', fullWidth. */
53
+ primaryAction?: Record<string, unknown>;
54
+ /** { label, onClick } — delegates to Button appearance='secondary', size='large', fullWidth. */
55
+ secondaryAction?: Record<string, unknown>;
56
+ /** When supplied, the sheet is a nested step inside its host flow — a back chevron (Icon Button → ChevronLeftIcon, sys.icon.lg, sys.layout.inline.md gap to the title) is rendered at the title's leading edge and invokes onBack on click. Consumer owns step state; the component only paints the chevron. */
57
+ onBack?: (...args: any[]) => any;
58
+ /** Accessible name on the back Icon Button. Only consulted when onBack is set. */
59
+ backLabel?: string;
60
+ children?: React.ReactNode;
61
+ /** When true, scopes the scrim and card to the nearest positioned ancestor instead of portaling to document.body. */
62
+ inline?: boolean;
63
+ }
64
+ export interface BottomSheetProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof BottomSheetPropsOwn>, BottomSheetPropsOwn {}
65
+
66
+ // ── Bubble (bubble/bubble) ──
67
+ interface BubblePropsOwn {
68
+ /** Bubble copy. Single line by default — overflow truncates with an ellipsis. */
69
+ children: React.ReactNode;
70
+ /** Which edge of the bubble grows a tail. `top` (default) places the tail on the bubble's top edge, pointing UP toward an anchor that sits above the bubble. `bottom` mirrors for anchors below the bubble. */
71
+ tailSide?: "top" | "bottom";
72
+ /** Where along the tail edge the tail sits. `center` (default) parks it dead-centre; `start` and `end` shift it toward the leading / trailing corner so the tail lines up with an off-centre anchor. */
73
+ tailAlign?: "start" | "center" | "end";
74
+ /** Composes with the bubble's own class. Operations colour overrides should land on inline style as `--bubble-fill` / `--bubble-ink` rather than re-skinning via className. */
75
+ className?: string;
76
+ /** Standard inline style. The two custom properties consumers most often set here are `--bubble-fill` (background tone) and `--bubble-ink` (label tone) for runtime tinting. */
77
+ style?: Record<string, unknown>;
78
+ }
79
+ export interface BubbleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof BubblePropsOwn>, BubblePropsOwn {}
80
+
81
+ // ── Button (multi-sub: standard, icon, text, check, toggle, toolbar) ──
82
+ interface ButtonStandardPropsOwn {
83
+ appearance?: "primary" | "secondary" | "outlined" | "tertiary";
84
+ size?: "large" | "medium" | "small";
85
+ leadingIcon?: React.ReactNode;
86
+ fullWidth?: boolean;
87
+ truncate?: boolean;
88
+ disabled?: boolean;
89
+ }
90
+ export interface ButtonStandardProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonStandardPropsOwn>, ButtonStandardPropsOwn {}
91
+
92
+ interface ButtonIconPropsOwn {
93
+ variant: "icon";
94
+ size?: "large" | "medium";
95
+ appearance?: "default" | "inverse";
96
+ /** The glyph. Required. */
97
+ icon: React.ReactNode;
98
+ /** Accessible name — required because Icon Button carries no visible text. */
99
+ "aria-label": string;
100
+ disabled?: boolean;
101
+ }
102
+ export interface ButtonIconProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonIconPropsOwn>, ButtonIconPropsOwn {}
103
+
104
+ interface ButtonTextPropsOwn {
105
+ variant: "text";
106
+ size?: "medium" | "small" | "xsmall";
107
+ appearance?: "default" | "accent" | "onPrimary" | "inverse";
108
+ /** Optional context glyph rendered before the label (e.g. a back chevron next to a 'Back' label, an external-link glyph next to 'Open'). Sized 16px on every rung — the glyph stays legible on the dense `xsmall` rung where the label drops to 12-rank type. */
109
+ leadingIcon?: React.ReactNode;
110
+ /** Optional directional / destination glyph rendered after the label (e.g. a chevron-right next to 'Continue', an external-link glyph next to 'Open in new tab'). Same per-size footprint as the leading slot. For the **dropdown** pattern (see `useCases.dropdown`), pass `<ChevronDownIcon />` at rest and flip to `<ChevronUpIcon />` while the menu is expanded — the chevron is a state signal tied to `aria-expanded`, never decorative. */
111
+ trailingIcon?: React.ReactNode;
112
+ disabled?: boolean;
113
+ }
114
+ export interface ButtonTextProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonTextPropsOwn>, ButtonTextPropsOwn {}
115
+
116
+ interface ButtonCheckPropsOwn {
117
+ variant: "check";
118
+ size?: "medium" | "small";
119
+ appearance?: "default" | "accent" | "inverse";
120
+ /** Toggle state. `true` paints the leading checkbox glyph as a filled square; `false` paints it as an outline. The component itself renders the checkbox glyph — consumers do NOT pass a checkbox icon node, only the `checked` state. */
121
+ checked?: boolean;
122
+ /** Optional context glyph rendered between the checkbox and the label (e.g. a coin glyph next to 'Use 1 perk'). Sized 16px on every rung — matches the optional middle icon footprint to `button/text` so the row reads at one density. */
123
+ icon?: React.ReactNode;
124
+ disabled?: boolean;
125
+ }
126
+ export interface ButtonCheckProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonCheckPropsOwn>, ButtonCheckPropsOwn {}
127
+
128
+ interface ButtonTogglePropsOwn {
129
+ variant: "toggle";
130
+ /** Whether the action has been committed. Maps to aria-pressed. */
131
+ active?: boolean;
132
+ leadingIcon?: React.ReactNode;
133
+ trailingIcon?: React.ReactNode;
134
+ disabled?: boolean;
135
+ }
136
+ export interface ButtonToggleProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonTogglePropsOwn>, ButtonTogglePropsOwn {}
137
+
138
+ interface ButtonToolbarPropsOwn {
139
+ variant: "toolbar";
140
+ appearance?: "default" | "accent" | "inverse";
141
+ leadingIcon?: React.ReactNode;
142
+ trailingIcon?: React.ReactNode;
143
+ disabled?: boolean;
144
+ }
145
+ export interface ButtonToolbarProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonToolbarPropsOwn>, ButtonToolbarPropsOwn {}
146
+
147
+ export type ButtonProps =
148
+ | ButtonStandardProps
149
+ | ButtonIconProps
150
+ | ButtonTextProps
151
+ | ButtonCheckProps
152
+ | ButtonToggleProps
153
+ | ButtonToolbarProps;
154
+
155
+ // ── Fab (button/fab) ──
156
+ interface FabPropsOwn {
157
+ variant?: "fab";
158
+ appearance?: "primary" | "secondary";
159
+ icon?: React.ReactNode;
160
+ label?: React.ReactNode;
161
+ }
162
+ export interface FabProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof FabPropsOwn>, FabPropsOwn {}
163
+
164
+ // ── SuggestionList (suggestion-list/suggestion-list) ──
165
+ interface SuggestionListPropsOwn {
166
+ /** Composition mode flag. When `true` (or when SuggestionList is a direct child of `.chorus-carousel` / `.chorus-feed`), the list 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 `suggestion-list.family.json`. */
167
+ embedded?: boolean;
168
+ /** Section title. */
169
+ label: string;
170
+ /** { label, href? , onClick? } — trailing text link in the header. */
171
+ headerAction?: Record<string, unknown>;
172
+ /** Array of channel descriptors: { value, name, followers, description, thumbnail, active?, onToggle? }. */
173
+ items: React.ReactNode;
174
+ }
175
+ export interface SuggestionListProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof SuggestionListPropsOwn>, SuggestionListPropsOwn {}
176
+
177
+ // ── AvatarRail (avatar-rail/avatar-rail) ──
178
+ interface AvatarRailPropsOwn {
179
+ /** Accessible name for the rail. */
180
+ "aria-label": string;
181
+ /** Array of channel entries: { value, label, href, thumbnail }. */
182
+ items: React.ReactNode;
183
+ /** { label, href? , onClick? } — trailing [Text Button](../button/text.md) (`size={'small'}`, `appearance={'accent'}`) at the end of the rail (typically 'View all' / 'Manage'). Link-affordance rule: link-shaped Text Buttons take `accent` for chromatic emphasis. */
184
+ trailingAction?: Record<string, unknown>;
185
+ /** Composition mode flag. When `true` (or when the AvatarRail is a direct child of `.chorus-carousel` / `.chorus-feed`), the rail 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 `avatar-rail.family.json`. */
186
+ embedded?: boolean;
187
+ }
188
+ export interface AvatarRailProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof AvatarRailPropsOwn>, AvatarRailPropsOwn {}
189
+
190
+ // ── Chip (multi-sub: filter, tag) ──
191
+ interface ChipFilterPropsOwn {
192
+ variant?: "filter";
193
+ selected?: boolean;
194
+ leadingIcon?: React.ReactNode;
195
+ trailingIcon?: React.ReactNode;
196
+ disabled?: boolean;
197
+ }
198
+ export interface ChipFilterProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ChipFilterPropsOwn>, ChipFilterPropsOwn {}
199
+
200
+ interface ChipTagPropsOwn {
201
+ variant: "tag";
202
+ /** Dismiss glyph (×); when present, chip becomes interactive. */
203
+ trailingIcon?: React.ReactNode;
204
+ }
205
+ export interface ChipTagProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof ChipTagPropsOwn>, ChipTagPropsOwn {}
206
+
207
+ export type ChipProps =
208
+ | ChipFilterProps
209
+ | ChipTagProps;
210
+
211
+ // ── Dialog (dialog/dialog) ──
212
+ interface DialogPropsOwn {
213
+ open: boolean;
214
+ onClose: (...args: any[]) => any;
215
+ title: React.ReactNode;
216
+ body?: React.ReactNode;
217
+ /** { src, alt } — illustrative image between the title and the actions. */
218
+ image?: Record<string, unknown>;
219
+ /** When true (default), the image sits directly under the title; when false, under the body. */
220
+ imageFirst?: boolean;
221
+ /** { label, onClick } — delegates to Button appearance='primary', size='large', fullWidth. */
222
+ primaryAction?: Record<string, unknown>;
223
+ /** { label, onClick } — delegates to Button appearance='tertiary', size='large', fullWidth. */
224
+ secondaryAction?: Record<string, unknown>;
225
+ /** When true, scopes the scrim and card to the nearest positioned ancestor instead of portaling to document.body. */
226
+ inline?: boolean;
227
+ }
228
+ export interface DialogProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof DialogPropsOwn>, DialogPropsOwn {}
229
+
230
+ // ── Divider (divider/divider) ──
231
+ interface DividerPropsOwn {
232
+ /** Defaults to `true`. The band is decorative chrome — screen readers should not announce it as a thematic break. Override to `false` only when the divider genuinely marks a semantic section change (rare; prefer a heading instead). */
233
+ "aria-hidden"?: boolean;
234
+ /** Composes with the component's own class. Use sparingly — Divider exposes no tokens to override and any sizing / colour override breaks the surface-agnostic contract. */
235
+ className?: string;
236
+ }
237
+ export interface DividerProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof DividerPropsOwn>, DividerPropsOwn {}
238
+
239
+ // ── DirectoryList (directory-list/directory-list) ──
240
+ interface DirectoryListPropsOwn {
241
+ /** Composition mode flag. When `true` (or when DirectoryList is a direct child of `.chorus-carousel` / `.chorus-feed`), the list enters **embedded mode**: zeroes its own `background` + `padding` so chrome defers to the host. */
242
+ embedded?: boolean;
243
+ /** Section title. */
244
+ label: string;
245
+ /** { label, href?, onClick? } — trailing accent Text Button in the header. Extend the header when there's an index page to route to. */
246
+ headerAction?: Record<string, unknown>;
247
+ /** Array of entity descriptors: { value, name, followers, description, thumbnail, active?, onToggle? }. */
248
+ items: React.ReactNode;
249
+ }
250
+ export interface DirectoryListProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof DirectoryListPropsOwn>, DirectoryListPropsOwn {}
251
+
252
+ // ── Feed (feed/post) ──
253
+ interface FeedPropsOwn {
254
+ /** Editorial label — 'HOT', 'NEW', 'PINNED'. Opt-in; most posts render without one. */
255
+ flag?: string;
256
+ /** Forwarded to Thumbnail verbatim at size 32. */
257
+ avatar?: Record<string, unknown>;
258
+ channel: string;
259
+ /** Destination URL for the channel link. Defaults to '#' (preview-stub). */
260
+ channelHref?: string;
261
+ timestamp: string;
262
+ /** When present, renders an inline text affordance after the timestamp. */
263
+ followAction?: boolean;
264
+ /** Array of independently-linked metadata items. Pass plain strings for stub links, or `{ label, href }` objects to route. */
265
+ meta?: React.ReactNode;
266
+ /** Controlled active state for the Like toggle. Omit (uncontrolled) and the component manages its own active state. */
267
+ liked?: boolean;
268
+ /** Fires with the next `liked` value when the Like Text Button is tapped. */
269
+ onLikeChange?: (...args: any[]) => any;
270
+ title?: string;
271
+ body?: string;
272
+ /** 80×80 trailing image; { src, alt, stacked? }. **Agents must always pass this slot at scaffold / generation time** — fill `src` with a real subject photo when implied, the bundled `/placeholder.png` otherwise. The `optional` flag and the `slotOmissionCollapses` rule are RUNTIME contracts (a Feed still reflows cleanly when a downstream consumer drops thumbnail for a verified text-only post); they are NOT a license for agents to omit the slot. See `forbidden` below — omission at scaffold time is forbidden. When `stacked === true` (2+ images attached), the thumbnail overlays a `SquareStackIcon` glyph at its top-right corner — see `sizing.thumbnailMultipleBadge` for the full anatomy. */
273
+ thumbnail?: Record<string, unknown>;
274
+ /** { label, participants } — inline poll banner. `label` is constrained to the literal `"Poll"` (the banner is editorial — see `tagBanner.labelEnum`); `participants` is the participant count shown after the divider. */
275
+ poll?: Record<string, unknown>;
276
+ /** { label, participants } — inline offer-evaluation banner. Same chrome as `poll` (surfaceVariant slab, leading glyph + label + divider + participant count), but the leading icon is `CompensationFillIcon` and the glyph + label paint in `sys.color.success` (resolves to `ref.palette.green.500`). Surfaces a 'compensation / offer evaluation' post — the author publishes their current salary or a competing offer and asks the community for better-option signals. `label` is constrained to the literal `"Offer"` (see `tagBanner.labelEnum`); `participants` is the participant count shown after the divider. */
277
+ offer?: Record<string, unknown>;
278
+ /** { title, source, src? } — inline link-share card. */
279
+ citation?: Record<string, unknown>;
280
+ mention?: string;
281
+ /** { likes, comments, views } — footer counters. */
282
+ engagement?: Record<string, unknown>;
283
+ /** Whole-card navigation. When present the card surface becomes the click target (an <article role="button">), and interior affordances (channel link, Follow, Like, Comments, citation, mention) route independently — a click originating on any inner <a>/<button> is ignored. This is the in-contract way to make a post navigable; do NOT wrap Feed in an external <Link>/<a> — that re-pays the page rail as a gutter and misaligns the card with NavigationBar / TabBar. */
284
+ onClick?: (...args: any[]) => any;
285
+ }
286
+ export interface FeedProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof FeedPropsOwn>, FeedPropsOwn {}
287
+
288
+ // ── FeedAd (feed/ad) ──
289
+ interface FeedAdPropsOwn {
290
+ /** { name, subtitle?, avatar? } — leading brand row. `name` is **required** — every sponsored placement MUST carry an explicit brand name; never render the row with an empty / missing name (the row exists specifically to attribute the ad to a buyer). `avatar` is forwarded to Thumbnail verbatim at size 32. `subtitle` defaults to the literal `Sponsored`; override but cannot omit. */
291
+ brand: Record<string, unknown>;
292
+ /** Fires when the trailing close (X) is tapped. Omit to render the card without a dismiss affordance. */
293
+ onDismiss?: (...args: any[]) => any;
294
+ /** Accessible label for the trailing close button. Defaults to 'Dismiss ad'. */
295
+ dismissLabel?: string;
296
+ /** Ad headline. heading.sm (16 / Semibold) / onSurface. Single line; truncates with ellipsis. */
297
+ title?: string;
298
+ /** Ad body copy. body.sm (14 / Regular) / onSurfaceVariant. Two-line clamp with trailing ellipsis. */
299
+ body?: string;
300
+ /** { src, alt } — hero media block. **Required** for every FeedAd placement: an ad without a hero image collapses to a brand row + text wall, which is not a shipped FeedAd shape. Renders at 16:10 inside the cta-group's `radius.md` clip. When generating mock / scaffold placements without a real ad creative, fill `src` with `/placeholder.png` rather than omitting `media` — the `surfaceContainerHigh` fallback is a runtime safety net for load failures, not a design-time omission. */
301
+ media: Record<string, unknown>;
302
+ /** { label, onClick?, color? } — the single full-width Standard Button at the foot. `color` accepts a free-form Hex supplied by the ad client; only the button fill and border swap, every other Standard Button token binding stays intact. */
303
+ cta?: Record<string, unknown>;
304
+ className?: string;
305
+ }
306
+ export interface FeedAdProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof FeedAdPropsOwn>, FeedAdPropsOwn {}
307
+
308
+ // ── FormField (multi-sub: input, textarea, search, select) ──
309
+ interface FormFieldInputPropsOwn {
310
+ variant?: "input";
311
+ appearance?: "default" | "error";
312
+ value?: string;
313
+ defaultValue?: string;
314
+ placeholder?: string;
315
+ /** Visible label rendered above the field box and associated with it (`<label htmlFor>`). */
316
+ label?: React.ReactNode;
317
+ /** Assistive text rendered below the field box, left-aligned. **Optional on every appearance** — omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.error` so the message reads as the error caption; an error field may still be shown without a helper, in which case only the field box re-tones. Mutually exclusive with `maxLength` — if both are given, the character count is shown and `helper` is ignored. */
318
+ helper?: React.ReactNode;
319
+ /** Caps the input length and renders a `current/max` character count below the field box, right-aligned. Mutually exclusive with `helper`. */
320
+ maxLength?: number;
321
+ disabled?: boolean;
322
+ }
323
+ export interface FormFieldInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, keyof FormFieldInputPropsOwn>, FormFieldInputPropsOwn {}
324
+
325
+ interface FormFieldTextareaPropsOwn {
326
+ variant: "textarea";
327
+ appearance?: "default" | "error";
328
+ value?: string;
329
+ defaultValue?: string;
330
+ placeholder?: string;
331
+ /** Visible label rendered above the field box and associated with it (`<label htmlFor>`). */
332
+ label?: React.ReactNode;
333
+ /** Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.error` on the error appearance. */
334
+ helper?: React.ReactNode;
335
+ /** Caps the value length and renders a `current/max` character count below the box, right-aligned. Mutually exclusive with `helper`. */
336
+ maxLength?: number;
337
+ /** Minimum visible rows. The textarea is `resize: vertical` so the user can pull it taller; the value never shrinks below `rows`. */
338
+ rows?: number;
339
+ disabled?: boolean;
340
+ }
341
+ export interface FormFieldTextareaProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof FormFieldTextareaPropsOwn>, FormFieldTextareaPropsOwn {}
342
+
343
+ interface FormFieldSearchPropsOwn {
344
+ variant: "search";
345
+ /** Search Bar carries only the `default` appearance — there is no `error` form (error reporting belongs to a labelled Input). */
346
+ appearance?: "default";
347
+ value?: string;
348
+ defaultValue?: string;
349
+ placeholder?: string;
350
+ disabled?: boolean;
351
+ }
352
+ export interface FormFieldSearchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, keyof FormFieldSearchPropsOwn>, FormFieldSearchPropsOwn {}
353
+
354
+ interface FormFieldSelectPropsOwn {
355
+ variant: "select";
356
+ appearance?: "default" | "error";
357
+ value?: string;
358
+ defaultValue?: string;
359
+ placeholder?: string;
360
+ label?: React.ReactNode;
361
+ helper?: React.ReactNode;
362
+ /** Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.onSurface` on the default appearance, `sys.color.onErrorContainer` on `error`) so the glyph reads as part of the typed content. */
363
+ leadingIcon?: React.ReactNode;
364
+ /** Fired when the field box or the trailing chevron is clicked. Consumers use this to raise a `BottomSheet` with the option list. */
365
+ onOpen?: (...args: any[]) => any;
366
+ disabled?: boolean;
367
+ }
368
+ export interface FormFieldSelectProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof FormFieldSelectPropsOwn>, FormFieldSelectPropsOwn {}
369
+
370
+ export type FormFieldProps =
371
+ | FormFieldInputProps
372
+ | FormFieldTextareaProps
373
+ | FormFieldSearchProps
374
+ | FormFieldSelectProps;
375
+
376
+ // ── Header (header/main) ──
377
+ interface HeaderPropsOwn {
378
+ /** Label typography rung. `large` (default) — `sys.typo.heading.md` (20 / Semibold) — the canonical section heading used by Section and other top-level page regions. `medium` — `sys.typo.heading.sm` (16 / Semibold) — denser host surfaces (in-sheet sub-section, bounded card, drawer column heading, secondary regions inside an already-headed page). */
379
+ size?: "large" | "medium";
380
+ /** Leading heading text. Single line; the consumer truncates upstream if the host width is too narrow. */
381
+ label?: string;
382
+ /** Trailing Text Button binding. Object: `{ label, href, onClick }`. Rendered as a Text Button at `size="xsmall"`, `appearance="accent"`. The link-affordance rule keeps the navigational intent in the accent tone across hosts. Mutually exclusive with `trailingIcon`. */
383
+ headerAction?: Record<string, unknown>;
384
+ /** Drill-in [Icon Button](../button/icon.md) mode. Pass `true` to render the canonical chevron-right glyph inside a Button `variant="icon"` `size="medium"` (32 × 32 capsule, 16-glyph), or pass any 16px icon node to override the glyph. The trailing Icon Button owns its own tap target via `onClick` / `href`; the surrounding `<header>` element stays non-interactive. Supply an `aria-label` for the icon-only button (defaults to `"Open <label>"` when `label` is a string). Mutually exclusive with `headerAction` and `headerDropdown`. */
385
+ trailingIcon?: React.ReactNode;
386
+ /** Trailing [Text Button dropdown](../button/text.md#dropdown) binding. Object: `{ label, onClick, open, 'aria-haspopup'?, 'aria-controls'? }`. Rendered as a Text Button at `size="xsmall"` with the default appearance — label IS the current selected value ('Top', 'Last 7 days', 'All time'), and the trailing chevron flips between `ChevronDownIcon` (when `open` is falsy) and `ChevronUpIcon` (when `open` is true) as a state signal. Consumer owns the menu surface and the `open` state. Mutually exclusive with `headerAction` and `trailingIcon` — priority `trailingIcon` > `headerAction` > `headerDropdown` when more than one is set. */
387
+ headerDropdown?: Record<string, unknown>;
388
+ /** Click handler. Honored when `trailingIcon` is set — wired to the trailing Icon Button's `onClick`. */
389
+ onClick?: (...args: any[]) => any;
390
+ /** Link href. Honored when `trailingIcon` is set — wired to the trailing Icon Button. */
391
+ href?: string;
392
+ /** Wrapping element. Defaults to `<header>` for top-level / labelled-region hosts; use `as="div"` when the surrounding host already carries the section semantics. The wrapper itself is never interactive — in `trailingIcon` mode the click target sits on the trailing Icon Button, not the wrapper. */
393
+ as?: "header" | "div";
394
+ }
395
+ export interface HeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof HeaderPropsOwn>, HeaderPropsOwn {}
396
+
397
+ // ── SubHeader (header/sub) ──
398
+ interface SubHeaderPropsOwn {
399
+ /** The section label text ("Following", "More Topics to follow"). Single line; the consumer keeps it short. `children` may be passed instead and takes precedence — `label` is the canonical string form. */
400
+ label?: string;
401
+ /** Trailing Text Button binding. Object: `{ label, href, onClick }`. Rendered as a Text Button at `size="xsmall"`, `appearance="accent"` — the one trailing affordance SubHeader carries, for a quiet region-level commit ("See all", "Edit", "Manage"). When set, the wrapper becomes a full-bleed flex row and the label is nested as the heading (`as`) on the leading edge so the heading's accessible name never absorbs the button. Mirrors Header's `headerAction` binding; SubHeader supports no icon or dropdown mode (those stay Header's, where the louder heading tone belongs). */
402
+ action?: Record<string, unknown>;
403
+ /** Wrapping element for the label. Defaults to `<h3>` so the muted label still registers as a heading for the group beneath it; use a different heading level to fit the host's document outline, or `as="div"` when the surrounding host already carries the section semantics. The label element is never interactive — in `action` mode the click target sits on the trailing Text Button, and the flex-row wrapper that holds both is a non-semantic `<div>`. */
404
+ as?: "h2" | "h3" | "h4" | "div";
405
+ /** Label content — overrides `label` when both are set. Use the `label` string for the canonical case; `children` is the escape hatch for inline composition. */
406
+ children?: React.ReactNode;
407
+ /** Composes with the component's own class. Use sparingly — SubHeader exposes its tone and inset through the spec; overriding them breaks the alignment-with-rows and muted-divider contracts. */
408
+ className?: string;
409
+ }
410
+ export interface SubHeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof SubHeaderPropsOwn>, SubHeaderPropsOwn {}
411
+
412
+ // ── List (multi-sub: standard, radio, entry, accordion) ──
413
+ interface ListStandardPropsOwn {
414
+ /** 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`. */
415
+ embedded?: boolean;
416
+ /** Array of row descriptors. A row carrying `thumbnail` props forwards them verbatim to the leading Thumbnail (the image type). */
417
+ items: React.ReactNode;
418
+ }
419
+ export interface ListStandardProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof ListStandardPropsOwn>, ListStandardPropsOwn {}
420
+
421
+ interface ListRadioPropsOwn {
422
+ /** 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`. */
423
+ embedded?: boolean;
424
+ /** Array of row descriptors. */
425
+ items: React.ReactNode;
426
+ /** Selected value. Controlled. */
427
+ value?: string;
428
+ /** Commits the next value when a row is selected. */
429
+ onChange?: (...args: any[]) => any;
430
+ }
431
+ export interface ListRadioProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof ListRadioPropsOwn>, ListRadioPropsOwn {}
432
+
433
+ interface ListEntryPropsOwn {
434
+ /** 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`. */
435
+ embedded?: boolean;
436
+ /** Thumbnail rung shared by every row in the list — `small` = 32, `medium` = 40, `large` = 48, `xlarge` = 56. The row's inline padding (16), block padding (8), and the family-wide row min-height (48) stay constant across sizes; only the leading Thumbnail footprint changes. Pick `small` for compact directories where the avatar reads as a mark, `medium` for the canonical channel / source directory, `large` for member-directory rows where the avatar carries identity weight, `xlarge` for the canonical follow-suggestion rung used by [SuggestionList](../suggestion-list/suggestion-list.md). */
437
+ size?: "small" | "medium" | "large" | "xlarge";
438
+ /** Array of row descriptors. Each item passes `thumbnail` props forwarded verbatim to the Thumbnail component. */
439
+ items: React.ReactNode;
440
+ }
441
+ export interface ListEntryProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof ListEntryPropsOwn>, ListEntryPropsOwn {}
442
+
443
+ interface ListAccordionPropsOwn {
444
+ /** `single` allows one open item at a time; `multiple` allows any number. Affects how `value` and `defaultValue` are typed. */
445
+ type?: "single" | "multiple";
446
+ /** Controlled open-state. `string` for `type='single'`, `string[]` for `type='multiple'`. Pair with `onValueChange`. */
447
+ value?: unknown;
448
+ /** Uncontrolled initial open-state. Ignored when `value` is also passed. */
449
+ defaultValue?: unknown;
450
+ /** Called with the next open-state (`string | null` for single, `string[]` for multiple) when the user toggles an item. */
451
+ onValueChange?: (...args: any[]) => any;
452
+ /** 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'`. */
453
+ collapsible?: boolean;
454
+ /** 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`. */
455
+ embedded?: boolean;
456
+ /** One or more `Accordion.Item` children. */
457
+ children: React.ReactNode;
458
+ }
459
+ export interface ListAccordionProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ListAccordionPropsOwn>, ListAccordionPropsOwn {}
460
+
461
+ export type ListProps =
462
+ | ListStandardProps
463
+ | ListRadioProps
464
+ | ListEntryProps
465
+ | ListAccordionProps;
466
+
467
+ // ── Metadata (multi-sub: standard, compact) ──
468
+ interface MetadataStandardPropsOwn {
469
+ /** Forwarded to [Thumbnail](../thumbnail/thumbnail.md) verbatim at `size={32}`. When omitted, the Thumbnail renders its image-area fallback over `surfaceContainerHigh`. */
470
+ avatar?: Record<string, unknown>;
471
+ /** Primary line entity name — channel topic (Post) or brand (Ad). Single line; truncates with ellipsis. */
472
+ name: string;
473
+ /** Destination URL for the entity name. When present, the name renders as an `<a>`; otherwise as a `<span>`. */
474
+ nameHref?: string;
475
+ /** Inline timestamp painted after the name on the primary line, in `label.sm` / `sys.color.outline` — one tonal step further than the name. Reach for it on Feed Post; omit on Feed Ad. */
476
+ timestamp?: string;
477
+ /** Whether to paint the inline follow toggle after the timestamp. When `true`, a dot separator + Follow/Following text toggle render at the primary line's trailing edge. State is controlled via `followed` + `onFollowChange`. */
478
+ followAction?: boolean;
479
+ /** Whether the viewer follows this entity. Drives the inline follow toggle's label and `aria-pressed`. */
480
+ followed?: boolean;
481
+ /** Fires with the next `followed` value when the inline follow toggle is tapped. */
482
+ onFollowChange?: (...args: any[]) => any;
483
+ /** Secondary line plain text — canonical use is 'Sponsored' on Feed Ad. Mutually exclusive with `meta` (when both are passed, `meta` wins). */
484
+ subtitle?: string;
485
+ /** Array of independently-linked metadata items rendered on the secondary line. Each entry is either a string (renders as a stub-href link) or a `{ label, href, badge }` object — `badge` is an optional SINGLE presentational mark node rendered AFTER the item's link, outside the <a> (canonical fill: Badge variant="role" on the trailing nickname item; at most one badge rides the nickname). The last item is canonically the user's nickname, displayed bare (no @ prefix). Items separate by middot. Reach for it on Feed Post; omit on Feed Ad. */
486
+ meta?: React.ReactNode;
487
+ /** Trailing-edge slot. Canonical fill: the Feed Ad dismiss × button. The slot's hit target is its own — taps on it never propagate to the entity-name link. */
488
+ trailing?: React.ReactNode;
489
+ }
490
+ export interface MetadataStandardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof MetadataStandardPropsOwn>, MetadataStandardPropsOwn {}
491
+
492
+ interface MetadataCompactPropsOwn {
493
+ variant: "compact";
494
+ /** Array of independently-linked identity items — canonical fill is `[company name, nickname]`, the nickname canonically last, displayed bare (no @ prefix). Each entry is either a string (renders as a stub-href link) or a `{ label, href, badge }` object — `badge` is an optional SINGLE presentational mark node rendered AFTER the item's link, outside the <a> (canonical fill: Badge variant="role" on the trailing nickname item). Items separate by middot. Same grammar as the standard sub's `meta`; here it is the whole cluster, so it is required. */
495
+ meta: React.ReactNode;
496
+ /** Posting time at the line's trailing edge — plain text (never a link), preceded by a middot, in `label.sm` / `sys.color.outline` so it recedes behind the identity links. Required: the timestamp is what distinguishes a compact attribution from a bare identity row. */
497
+ timestamp: string;
498
+ }
499
+ export interface MetadataCompactProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof MetadataCompactPropsOwn>, MetadataCompactPropsOwn {}
500
+
501
+ export type MetadataProps =
502
+ | MetadataStandardProps
503
+ | MetadataCompactProps;
504
+
505
+ // ── NavCard (nav-card/nav-card) ──
506
+ interface NavCardPropsOwn {
507
+ /** Trailing affordance. `default` ships NO trailing icon — the card reads as a labelled tile (settings entry, scope card, informational drill-target). `nav` auto-renders the right-pointing chevron — the explicit drill-in form. A consumer-supplied `trailingIcon` prop still overrides on either variant (e.g. an external-link arrow, an expand-down chevron). */
508
+ variant?: "default" | "nav";
509
+ /** Primary card text. Single line; truncates with ellipsis. */
510
+ label: string;
511
+ /** Secondary line under the label. Single line; truncates with ellipsis. */
512
+ supportingText?: string;
513
+ /** Optional leading slot — a 16px icon glyph (renders in `currentColor`) or a 32-rung [Thumbnail](../thumbnail/thumbnail.md). Block-centred on the row's vertical midline — both icon and thumbnail share the same contract regardless of one-line / two-line body. When omitted the label sits flush at the inline padding edge. */
514
+ leading?: React.ReactNode;
515
+ /** Overrides the variant's trailing affordance. Pass a custom 16px glyph when the action isn't a navigation drill-in (inline expand-down arrow, external-link arrow). On `variant="default"` no trailing renders unless `trailingIcon` is supplied; on `variant="nav"` the auto-chevron is replaced. */
516
+ trailingIcon?: React.ReactNode;
517
+ /** Container fill. `default` is transparent — the card's identity is the outlined chrome (hairline + radius + label + chevron) and the host surface tone reads through. `surface` paints `sys.color.surface` so the card reads as its own opaque tier; reach for it when the card sits on a transparent / non-`surface` host (between bare-surface sections, on a tonal band the card needs to break out of). */
518
+ appearance?: "default" | "surface";
519
+ /** When provided, renders as `<a href>` instead of `<button>`. Mutually exclusive with `onClick` as the primary commit, though either may be combined for analytics. */
520
+ href?: string;
521
+ onClick?: (...args: any[]) => any;
522
+ disabled?: boolean;
523
+ /** Docs-only — pins the card to a single visual state via `data-force-state`. Not for production use. */
524
+ forcedState?: "hovered" | "pressed" | "focused";
525
+ }
526
+ export interface NavCardProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof NavCardPropsOwn>, NavCardPropsOwn {}
527
+
528
+ // ── NavList (nav-list/nav-list) ──
529
+ interface NavListPropsOwn {
530
+ /** Composition mode flag. When `true` (or when NavList is a direct child of `.chorus-carousel` / `.chorus-feed`), the list enters **embedded mode**: zeroes its own `background` + `padding` so chrome defers to the host. */
531
+ embedded?: boolean;
532
+ /** Section title. */
533
+ label: string;
534
+ /** { label, href?, onClick? } — trailing accent Text Button in the header. Extend the header when there's an index page to route to. */
535
+ headerAction?: Record<string, unknown>;
536
+ /** Array of nav row descriptors forwarded to [List](../list/entry.md) `variant="entry"` as label-only rows: { value, label, supportingText?, href?, onClick?, trailingIcon? }. `supportingText` is mapped to the entry sub's `description` slot (caption-tone supporting line under the label). `thumbnail` is intentionally NOT exposed — the leading column is always collapsed. */
537
+ items: React.ReactNode;
538
+ }
539
+ export interface NavListProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof NavListPropsOwn>, NavListPropsOwn {}
540
+
541
+ // ── NavigationBar (multi-sub: main, sub, search) ──
542
+ interface NavigationBarMainPropsOwn {
543
+ variant?: "main";
544
+ /** Default: brand logotype `<img>` rendered at a fixed 24px height (width auto). Fallback: plain string rendered as `heading.lg` text that truncates with ellipsis. */
545
+ title: React.ReactNode;
546
+ /** Tab-root case — fires when the leading menu / hamburger is tapped (opens the app drawer). Mutually exclusive with `onBack`. */
547
+ onMenuClick?: (...args: any[]) => any;
548
+ /** Drill-in (content-detail) case — passing `onBack` swaps the leading menu glyph for a back chevron wired to it (route to the previous screen). Takes precedence over `onMenuClick` when both are passed. */
549
+ onBack?: (...args: any[]) => any;
550
+ /** Accessible label for the back chevron in the drill-in case. Defaults to "Back". */
551
+ backLabel?: string;
552
+ /** Up to four { icon, 'aria-label' } entries; laid out left-to-right with no inter-icon gap. Three is the conventional ceiling on a tab root; the drill-in (content-detail) cluster uses up to four (share / notify / bookmark / more). */
553
+ trailingActions?: React.ReactNode;
554
+ }
555
+ export interface NavigationBarMainProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof NavigationBarMainPropsOwn>, NavigationBarMainPropsOwn {}
556
+
557
+ interface NavigationBarSubPropsOwn {
558
+ variant: "sub";
559
+ /** Container fill and foreground tone. `surface` (default) — opaque `sys.color.surface` fill, `onSurface` icons and title; the canonical page-chrome treatment. `overlay` — transparent container with **fixed white** icons (`ref.palette.white.1000`), used when the bar floats over a hero/cover image (e.g. inside [Profile header](../profile-header/profile-header.md)). In `overlay`, the title slot is intentionally muted — pass `title=""` (empty string) when the host already carries page identity below the bar. */
560
+ appearance?: "surface" | "overlay";
561
+ /** Page name. Optional — omit it for bars whose side actions say everything (canonical: the composer text-button pair, Cancel / Post). A title-less bar renders a non-heading placeholder in the centre cell so the side slots stay in their 1fr/auto/1fr columns. Pass an empty string in `overlay` appearance when the host (e.g. ProfileHeader) carries the identity directly below the bar. */
562
+ title?: string;
563
+ /** Optional leading affordance. Accepts the { icon, 'aria-label' } descriptor (renders the 24px Icon Button internally — typically a back-arrow), a { label, href } link descriptor, or a node. Text Button and Icon Button are both first-class fills: pick per side independently of the trailing slot — e.g. a default-appearance Text Button (Cancel) on composer bars. */
564
+ leading?: Record<string, unknown>;
565
+ /** Optional trailing affordance — Icon Button / Toolbar Button / Text Button, chosen independently of the leading slot. **Prefer the object form `{ icon, 'aria-label' }` for the icon case** — the component then renders the 24px Icon Button internally and the `sys.icon.lg` contract is guaranteed. If a raw `<Button variant="icon" />` node is passed instead, it MUST carry `size="large"` (= `sys.icon.lg` / 24); `size="medium"` resolves to `sys.icon.md` (16) and breaks symmetry with the leading slot. */
566
+ trailing?: React.ReactNode;
567
+ }
568
+ export interface NavigationBarSubProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof NavigationBarSubPropsOwn>, NavigationBarSubPropsOwn {}
569
+
570
+ interface NavigationBarSearchPropsOwn {
571
+ variant: "search";
572
+ /** Controlled value. Omit (and pass `defaultValue` instead) for an uncontrolled field. */
573
+ value?: string;
574
+ defaultValue?: string;
575
+ /** Faint guide text shown when the value is empty. Sentence case, typically a one-line invitation (`Search by keyword`, `Search channels`). */
576
+ placeholder?: string;
577
+ /** Called on every keystroke with the new value. Host owns the typeahead / submit pipeline. */
578
+ onChange?: (...args: any[]) => any;
579
+ /** Called when the user commits the query (Enter / form submit). Host wires the route or query side-effect. */
580
+ onSubmit?: (...args: any[]) => any;
581
+ /** Wires the leading back-arrow to the host's back action — the bar does not own routing. */
582
+ onBack: (...args: any[]) => any;
583
+ /** aria-label for the leading back-arrow icon button. */
584
+ backLabel?: string;
585
+ /** aria-label for the trailing clear (×) button. */
586
+ clearLabel?: string;
587
+ /** Defaults to true because the bar is the search page's primary surface — the user arrived here to type. Set to false when the field receives a hydrated query that the user is reviewing rather than editing. */
588
+ autoFocus?: boolean;
589
+ }
590
+ export interface NavigationBarSearchProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof NavigationBarSearchPropsOwn>, NavigationBarSearchPropsOwn {}
591
+
592
+ export type NavigationBarProps =
593
+ | NavigationBarMainProps
594
+ | NavigationBarSubProps
595
+ | NavigationBarSearchProps;
596
+
597
+ // ── ProfileHeader (profile-header/profile-header) ──
598
+ interface ProfileHeaderPropsOwn {
599
+ /** Entity name — channel topic, person, or company. Renders as the page-level `<h1>` at `sys.typo.heading.lg` / Semibold / `onSurface`. Single line; truncates with ellipsis at narrow widths. */
600
+ name: string;
601
+ /** Forwarded to [Thumbnail](../thumbnail/thumbnail.md) verbatim at `size={56}` with `outlined={true}`. Object: `{ src, alt, updateDot?, logoBadge? }`. The avatar overlaps the cover band's bottom edge by half its height; the 2-token `surface`-tone halo that separates the circle from the cover comes from Thumbnail's `outlined` case (see [Thumbnail § With surface outline](../thumbnail/thumbnail.md#with-surface-outline)). */
602
+ avatar: Record<string, unknown>;
603
+ /** { src?, alt? } — image-area override for the cover band. `src` accepts any URL; falls back to `/placeholder.png` (the universal Chorus image-area placeholder) when omitted. Same contract as `Thumbnail.src` / `ProfileCarousel.cover` — `object-fit: cover` preserves aspect ratio and crops to the band's intrinsic `375 / 120` ratio. */
604
+ cover?: Record<string, unknown>;
605
+ /** Visibility class. `public` paints the [GlobeIcon](../../icons/svg/Globe.svg) + the `visibilityLabel` (default 'Public'); `private` paints the [LockIcon](../../icons/svg/Lock.svg) + the `visibilityLabel` (default 'Private'). Consumers override the label text via `visibilityLabel`. */
606
+ visibility?: "public" | "private";
607
+ /** Override the visibility label text — used for localisation (e.g. '공개' / '비공개'). When omitted, defaults to 'Public' / 'Private' based on `visibility`. */
608
+ visibilityLabel?: string;
609
+ /** Follower count line — formatted by the consumer so the unit / locale stays in their hands (e.g. '999 followers', '12.4K followers', '999 팔로워'). Paints in `sys.typo.body.sm` / `sys.color.onSurfaceVariant` to the right of a bullet separator on the meta row. */
610
+ followers: string;
611
+ /** Whether the viewer is following this entity. Drives the trailing [Toggle Button](../button/toggle.md): false → 'Follow' (primary fill); true → 'Following' (transparent fill + hairline outline so the committed state recedes against any host surface tier). */
612
+ followed?: boolean;
613
+ /** Fires when the follow toggle commits — receives the next `followed` boolean. Consumer owns persistence. */
614
+ onFollowChange?: (...args: any[]) => any;
615
+ /** Inactive follow-button label. Defaults to 'Follow'. */
616
+ followLabel?: string;
617
+ /** Active follow-button label. Defaults to 'Following'. */
618
+ followingLabel?: string;
619
+ /** Whether to render the overlay [Navigation bar/page](../navigation-bar/sub.md) at the cover's top edge. Defaults to `true` (back-arrow leading + search trailing, transparent + fixed-white icons). Pass `false` when the host route already paints its own top chrome and the cover should be clean. */
620
+ nav?: boolean;
621
+ /** Paint an iOS-style app status bar (time + cellular / Wi-Fi / battery glyphs) above the overlay nav at the cover's top edge — transparent fill, fixed-white (`ref.palette.white.1000`) glyphs, so the cover image shows through the OS-chrome zone (edge-to-edge / immersive screen). Off by default. Pass `true` for the canonical 9:41 time, or `statusBar={{ time: '…' }}` to override. Decorative OS-chrome mimicry: `aria-hidden`, intentionally off-token (raw px + `-apple-system` font). When present it pays `env(safe-area-inset-top)` and the overlay nav collapses its own inset so the two never double-stack below the notch. */
622
+ statusBar?: unknown;
623
+ /** Click handler for the overlay nav's leading back chevron. */
624
+ onBack?: (...args: any[]) => any;
625
+ /** Click handler for the overlay nav's trailing search icon. */
626
+ onSearch?: (...args: any[]) => any;
627
+ /** Accessible label for the overlay nav's back chevron. Defaults to 'Back'. */
628
+ backLabel?: string;
629
+ /** Accessible label for the overlay nav's search icon. Defaults to 'Search'. */
630
+ searchLabel?: string;
631
+ }
632
+ export interface ProfileHeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof ProfileHeaderPropsOwn>, ProfileHeaderPropsOwn {}
633
+
634
+ // ── Progress (progress/progress) ──
635
+ interface ProgressPropsOwn {
636
+ /** Progress value. When `max` is omitted, this is the ratio 0..1; with `max`, it is the raw count and the ratio is `value / max`. Clamped to 0..1. */
637
+ value?: number;
638
+ /** Upper bound for `value`. Defaults to 1 so a `value` of 0.4 reads as 40%. */
639
+ max?: number;
640
+ /** Accessible label describing what is in progress. Required when the bar is not paired with a visible label next to it. */
641
+ "aria-label"?: string;
642
+ }
643
+ export interface ProgressProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ProgressPropsOwn>, ProgressPropsOwn {}
644
+
645
+ // ── PostCarousel (carousel/post) ──
646
+ interface PostCarouselPropsOwn {
647
+ /** Array of up to 5 compact post cards. Each item: { id?, avatar, channel, verified?, followAction?, followed?, onFollowChange?, title, body, mention?, views, moreLabel?, onClick? }. Items beyond index 4 are dropped — the section enforces the 5-card maximum. */
648
+ items: React.ReactNode;
649
+ }
650
+ export interface PostCarouselProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof PostCarouselPropsOwn>, PostCarouselPropsOwn {}
651
+
652
+ // ── ProfileCarousel (carousel/profile) ──
653
+ interface ProfileCarouselPropsOwn {
654
+ /** Array of profile-card descriptors. Each item: { id?, avatar, cover?, name, followers, metrics?, description?, followed?, onFollowChange?, followLabel?, followingLabel?, onClick? }. `metrics` and `description` are mutually exclusive — when `description` is present, it replaces the metrics row. Items beyond index 4 are dropped — the rail enforces a hard 5-card maximum. */
655
+ items: React.ReactNode;
656
+ }
657
+ export interface ProfileCarouselProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ProfileCarouselPropsOwn>, ProfileCarouselPropsOwn {}
658
+
659
+ // ── SideSheet (side-sheet/side-sheet) ──
660
+ interface SideSheetPropsOwn {
661
+ /** Controlled open state. */
662
+ open: boolean;
663
+ /** Fired on backdrop tap, Escape key, or any consumer-triggered close. */
664
+ onClose: (...args: any[]) => any;
665
+ /** Edge the card sits flush against. `left` (default) — the off-canvas navigation / channel directory pattern. `right` — filter rail, settings pane, or sub-nav overlay on right-handed surfaces. */
666
+ anchor?: "left" | "right";
667
+ /** Card width. Number → pixels; string → any CSS length (`'80vw'`, `'min(320px, 80vw)'`). Mobile-first: defaults to 320 so the trailing 40-ish px peek lets the user see the page underneath at a glance. */
668
+ width?: unknown;
669
+ /** Pinned bottom action rail. Hosts a single Text Button or any compact action node (e.g. "Browse all channels"). Stays flush at the card bottom while the body scrolls. */
670
+ footer?: React.ReactNode;
671
+ /** Body content. Free-form composition; the canonical fill is one or more Header (size="medium") + embedded [list/entry](../list/entry.md) directory stack pairs (40 avatar + label + inline count Badge + optional trailing icon toggle). */
672
+ children: React.ReactNode;
673
+ /** Docs-only — renders the sheet inline (no portal, no fixed positioning, no body scroll lock) so the card can be shown next to other primitives in a documentation preview without occluding the page. Never use in production. */
674
+ inline?: boolean;
675
+ }
676
+ export interface SideSheetProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof SideSheetPropsOwn>, SideSheetPropsOwn {}
677
+
678
+ // ── Skeleton (skeleton/skeleton) ──
679
+ interface SkeletonPropsOwn {
680
+ /** Selects the default footprint and corner radius. `text` and `block` paint at `sys.radius.xs`; `circle` paints at `sys.radius.full`. */
681
+ shape?: "text" | "block" | "circle";
682
+ /** Overrides the default inline footprint. Numbers are coerced to `px`; strings pass through (`100%`, `'12rem'`, …). When omitted, `text` and `block` stretch to 100% of the host; `circle` keeps a `ref.space.500` (40 × 40) footprint. */
683
+ width?: unknown;
684
+ /** Overrides the default block footprint. Numbers are coerced to `px`. When omitted, `text` is `ref.space.200` (16px), `block` is `ref.space.1000` (80px), `circle` is `ref.space.500` (40px). */
685
+ height?: unknown;
686
+ /** Accessible label announced by screen readers. Defaults to `'Loading'`. */
687
+ "aria-label"?: string;
688
+ }
689
+ export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SkeletonPropsOwn>, SkeletonPropsOwn {}
690
+
691
+ // ── StatusTag (status-tag/status-tag) ──
692
+ interface StatusTagPropsOwn {
693
+ /** Tonal fill / foreground pair. `neutral` is the quiet informational default; `error` is the rejection / blocked / failed state. */
694
+ appearance?: "neutral" | "error";
695
+ /** Tag label text. Single line; should be a short word or phrase (≤ 6 chars in Latin, ≤ 4 in CJK). Wraps would push the pill onto two lines and break the inline-with-label rhythm — keep it short instead. */
696
+ children: React.ReactNode;
697
+ }
698
+ export interface StatusTagProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof StatusTagPropsOwn>, StatusTagPropsOwn {}
699
+
700
+ // ── Switch (switch/switch) ──
701
+ interface SwitchPropsOwn {
702
+ /** Controlled value. When provided, the consumer owns the state; pair with `onCheckedChange`. */
703
+ checked?: boolean;
704
+ /** Uncontrolled initial value. Ignored when `checked` is also passed. */
705
+ defaultChecked?: boolean;
706
+ /** Called with `(nextChecked, event)` the moment the user flips the switch. */
707
+ onCheckedChange?: (...args: any[]) => any;
708
+ disabled?: boolean;
709
+ /** Accessible label. Required when the Switch is not paired with a visible label via `aria-labelledby` or a wrapping `<label>`. */
710
+ "aria-label"?: string;
711
+ /** ID of the element that names this Switch. Mutually exclusive with `aria-label`. */
712
+ "aria-labelledby"?: string;
713
+ /** Docs-only — pins the switch to a single visual state via `data-force-state`. Not for production use. */
714
+ forcedState?: "hovered" | "pressed" | "focused";
715
+ }
716
+ export interface SwitchProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof SwitchPropsOwn>, SwitchPropsOwn {}
717
+
718
+ // ── TabBar (tab-bar/tab-bar) ──
719
+ interface TabBarPropsOwn {
720
+ /** Array of destinations: { value, label, icon, activeIcon?, href?, onClick?, appearance? }. */
721
+ items: React.ReactNode;
722
+ /** Currently active destination's `value`. The matching item renders with the active treatment (filled icon + onSurface). */
723
+ value?: string;
724
+ /** (value) => void. Fires when an item is tapped, after the item's own onClick (suppressed if the click was default-prevented). */
725
+ onChange?: (...args: any[]) => any;
726
+ /** Accessible name for the nav landmark. Defaults to 'Primary'. */
727
+ "aria-label"?: string;
728
+ }
729
+ export interface TabBarProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof TabBarPropsOwn>, TabBarPropsOwn {}
730
+
731
+ // ── Tabs (multi-sub: underline, rounded, segmented) ──
732
+ interface TabsUnderlinePropsOwn {
733
+ variant?: "underline";
734
+ /** The active tab's value. */
735
+ value: string;
736
+ /** Fired with the next value when a tab is selected. */
737
+ onChange: (...args: any[]) => any;
738
+ /** Accessible name for the tablist. */
739
+ "aria-label": string;
740
+ /** Composition mode flag. When `true` (or when Tabs 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 `tabs.family.json`. */
741
+ embedded?: boolean;
742
+ }
743
+ export interface TabsUnderlineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof TabsUnderlinePropsOwn>, TabsUnderlinePropsOwn {}
744
+
745
+ interface TabsRoundedPropsOwn {
746
+ variant: "rounded";
747
+ /** The active tab's value. */
748
+ value: string;
749
+ /** Fired with the next value when a tab is selected. */
750
+ onChange: (...args: any[]) => any;
751
+ /** Accessible name for the tablist. */
752
+ "aria-label": string;
753
+ /** Composition mode flag. When `true` (or when Tabs 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 `tabs.family.json`. */
754
+ embedded?: boolean;
755
+ }
756
+ export interface TabsRoundedProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof TabsRoundedPropsOwn>, TabsRoundedPropsOwn {}
757
+
758
+ interface TabsSegmentedPropsOwn {
759
+ variant: "segmented";
760
+ /** The active segment's value. */
761
+ value: string;
762
+ /** Fired with the next value when a segment is selected. */
763
+ onChange: (...args: any[]) => any;
764
+ /** Accessible name for the tablist. */
765
+ "aria-label": string;
766
+ /** Composition mode flag. When `true` (or when Tabs 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 `tabs.family.json`. */
767
+ embedded?: boolean;
768
+ }
769
+ export interface TabsSegmentedProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof TabsSegmentedPropsOwn>, TabsSegmentedPropsOwn {}
770
+
771
+ export type TabsProps =
772
+ | TabsUnderlineProps
773
+ | TabsRoundedProps
774
+ | TabsSegmentedProps;
775
+
776
+ // ── Thumbnail (thumbnail/thumbnail) ──
777
+ interface ThumbnailPropsOwn {
778
+ size?: 56 | 48 | 40 | 32 | 24 | 20 | 16;
779
+ /** URL of the image asset that fills the circular slot — a raster (PNG / JPG / WebP) or vector (SVG) source. Required for the rendered form; the prop is technically optional because the Thumbnail also has a defined `surfaceContainerHigh` fallback, but any production composition is expected to pass a real asset. When generating mock or scaffold compositions without a real channel / author image, use the bundled placeholder asset at `/placeholder.png` rather than omitting `src` — the empty-surface fallback is for runtime load failures, not for design-time scaffolding. */
780
+ src?: string;
781
+ alt: string;
782
+ updateDot?: boolean;
783
+ /** { src, alt } — 16×16 circular badge at the bottom-right. */
784
+ logoBadge?: Record<string, unknown>;
785
+ /** When `true`, paints a 2-token-wide `sys.color.surface` outline around the Thumbnail container as an outset halo. The outline reads as an isolation ring that separates the Thumbnail's circular edge from anything visually noisy underneath it. Painted as `box-shadow: 0 0 0 sys.borderWidth.thin sys.color.surface` so it never reflows the slot's intrinsic diameter — same no-layout-stroke idiom the rest of the system uses. Pair with hosts whose chrome reads as a `surface*` tier (so the halo blends in) and whose backdrop differs from that tier (so the halo actually separates). */
786
+ outlined?: boolean;
787
+ }
788
+ export interface ThumbnailProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ThumbnailPropsOwn>, ThumbnailPropsOwn {}
789
+
790
+ // ── Toast (toast/toast) ──
791
+ interface ToastPropsOwn {
792
+ /** Body text — short confirmation phrase. Single line by intent; wraps within the toast when the viewport forces it. */
793
+ children: React.ReactNode;
794
+ /** A Button node rendered at the trailing edge. The canonical bindings are `<Button variant="text" size="small" appearance="inverse">` for an action (Undo, View, Retry) or `<Button variant="icon" size="medium" appearance="inverse">` for explicit dismissal. Accepts a node (rather than a shorthand object) so the call site spells out the sub-component composition — including its `appearance="inverse"` binding — instead of hiding it inside the Toast. */
795
+ trailing?: React.ReactNode;
796
+ }
797
+ export interface ToastProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ToastPropsOwn>, ToastPropsOwn {}
798
+
799
+ // ── Tooltip (tooltip/tooltip) ──
800
+ interface TooltipPropsOwn {
801
+ /** Body text — a short, essential hint or label. The bubble's width is content-driven (it hugs the body) and capped at 300; copy should stay brief and intuitive at a glance — a fragment, a one-line hint, an action label. The 300 cap is a safety backstop, not a target; if the message routinely fills it, it likely belongs in a Banner or Dialog. */
802
+ children: React.ReactNode;
803
+ /** `default` paints the bubble in `primary` with an `onPrimary` foreground — the brand-blue tooltip. `inverse` paints in the inverse cluster (`inverseSurface` / `inverseOnSurface`) for use on screens already saturated with primary tone, where the brand-blue bubble would compete with the surrounding chrome. */
804
+ appearance?: "default" | "inverse";
805
+ /** Where the bubble sits relative to its trigger. The `<edge>` axis (top / bottom) chooses whether the bubble floats above or below the trigger and which edge the caret renders on. The `<align>` axis (start / center / end) shifts the caret along the parallel axis so it lines up with an off-centre trigger — pick `-start` when the trigger sits near the leading edge of the viewport, `-end` when it sits near the trailing edge, and the bare edge name when the trigger is comfortably centred. Left/right placements are intentionally not in the enum — Tooltip is a top/bottom affordance; for side-anchored disclosure surfaces reach for a popover-style host instead. */
806
+ placement?: "top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end";
807
+ /** Optional Button node rendered after the body. Canonical bindings: `<Button variant="text" size="small" appearance="onPrimary">` for the `default` (primary) tooltip, or `<Button variant="text" size="small" appearance="inverse">` for the `inverse` tooltip — so the label paints in the correct on-host token in either theme. Layout: inline-trailing when the body fits on one line; stacks below the body once the body wraps. */
808
+ action?: React.ReactNode;
809
+ }
810
+ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof TooltipPropsOwn>, TooltipPropsOwn {}
811
+
812
+ // ── Exports without a spec ── (wrappers/groups; permissive until specced)
813
+ export interface TabProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
814
+ export interface FeedGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
815
+ export interface FormFieldGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
816
+ export interface PageShellProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
817
+ export interface NavCardGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
818
+ export interface CarouselProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
819
+ export interface SideSheetGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
820
+ export interface SkeletonGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
821
+ export interface AccordionProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
822
+
823
+ export const Badge: React.ForwardRefExoticComponent<BadgeProps & React.RefAttributes<HTMLSpanElement>>;
824
+ export const Banner: React.ForwardRefExoticComponent<BannerProps & React.RefAttributes<HTMLDivElement>>;
825
+ export const BottomSheet: React.ForwardRefExoticComponent<BottomSheetProps & React.RefAttributes<HTMLDivElement>>;
826
+ export const Bubble: React.ForwardRefExoticComponent<BubbleProps & React.RefAttributes<HTMLDivElement>>;
827
+ export const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
828
+ export const Fab: React.ForwardRefExoticComponent<FabProps & React.RefAttributes<HTMLButtonElement>>;
829
+ export const SuggestionList: React.ForwardRefExoticComponent<SuggestionListProps & React.RefAttributes<HTMLDivElement>>;
830
+ export const AvatarRail: React.ForwardRefExoticComponent<AvatarRailProps & React.RefAttributes<HTMLDivElement>>;
831
+ export const Chip: React.ForwardRefExoticComponent<ChipProps & React.RefAttributes<HTMLElement>>;
832
+ export const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
833
+ export const Divider: React.ForwardRefExoticComponent<DividerProps & React.RefAttributes<HTMLElement>>;
834
+ export const DirectoryList: React.ForwardRefExoticComponent<DirectoryListProps & React.RefAttributes<HTMLElement>>;
835
+ export const Feed: React.ForwardRefExoticComponent<FeedProps & React.RefAttributes<HTMLElement>>;
836
+ export const FeedAd: React.ForwardRefExoticComponent<FeedAdProps & React.RefAttributes<HTMLElement>>;
837
+ export const FormField: React.ForwardRefExoticComponent<FormFieldProps & React.RefAttributes<HTMLElement>>;
838
+ export const Header: React.ForwardRefExoticComponent<HeaderProps & React.RefAttributes<HTMLElement>>;
839
+ export const SubHeader: React.ForwardRefExoticComponent<SubHeaderProps & React.RefAttributes<HTMLElement>>;
840
+ export const List: React.ForwardRefExoticComponent<ListProps & React.RefAttributes<HTMLElement>>;
841
+ export const Metadata: React.ForwardRefExoticComponent<MetadataProps & React.RefAttributes<HTMLDivElement>>;
842
+ export const NavCard: React.ForwardRefExoticComponent<NavCardProps & React.RefAttributes<HTMLButtonElement>>;
843
+ export const NavList: React.ForwardRefExoticComponent<NavListProps & React.RefAttributes<HTMLElement>>;
844
+ export const NavigationBar: React.ForwardRefExoticComponent<NavigationBarProps & React.RefAttributes<HTMLElement>>;
845
+ export const ProfileHeader: React.ForwardRefExoticComponent<ProfileHeaderProps & React.RefAttributes<HTMLElement>>;
846
+ export const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
847
+ export const PostCarousel: React.ForwardRefExoticComponent<PostCarouselProps & React.RefAttributes<HTMLDivElement>>;
848
+ export const ProfileCarousel: React.ForwardRefExoticComponent<ProfileCarouselProps & React.RefAttributes<HTMLDivElement>>;
849
+ export const SideSheet: React.ForwardRefExoticComponent<SideSheetProps & React.RefAttributes<HTMLElement>>;
850
+ export const Skeleton: React.ForwardRefExoticComponent<SkeletonProps & React.RefAttributes<HTMLSpanElement>>;
851
+ export const StatusTag: React.ForwardRefExoticComponent<StatusTagProps & React.RefAttributes<HTMLSpanElement>>;
852
+ export const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
853
+ export const TabBar: React.ForwardRefExoticComponent<TabBarProps & React.RefAttributes<HTMLElement>>;
854
+ export const Tabs: React.ForwardRefExoticComponent<TabsProps & React.RefAttributes<HTMLDivElement>>;
855
+ export const Thumbnail: React.ForwardRefExoticComponent<ThumbnailProps & React.RefAttributes<HTMLDivElement>>;
856
+ export const Toast: React.ForwardRefExoticComponent<ToastProps & React.RefAttributes<HTMLDivElement>>;
857
+ export const Tooltip: React.ForwardRefExoticComponent<TooltipProps & React.RefAttributes<HTMLDivElement>>;
858
+ export const Tab: React.ForwardRefExoticComponent<TabProps & React.RefAttributes<HTMLElement>>;
859
+ export const FeedGroup: React.ForwardRefExoticComponent<FeedGroupProps & React.RefAttributes<HTMLElement>>;
860
+ export const FormFieldGroup: React.ForwardRefExoticComponent<FormFieldGroupProps & React.RefAttributes<HTMLElement>>;
861
+ export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLElement>>;
862
+ export const NavCardGroup: React.ForwardRefExoticComponent<NavCardGroupProps & React.RefAttributes<HTMLElement>>;
863
+ export const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<HTMLElement>>;
864
+ export const SideSheetGroup: React.ForwardRefExoticComponent<SideSheetGroupProps & React.RefAttributes<HTMLElement>>;
865
+ export const SkeletonGroup: React.ForwardRefExoticComponent<SkeletonGroupProps & React.RefAttributes<HTMLElement>>;
866
+ export const Accordion: React.ForwardRefExoticComponent<AccordionProps & React.RefAttributes<HTMLElement>>;
867
+
868
+ // ── Aliases ── (additional export names declared via spec.exportAlias).
869
+ // Each alias drops the `variant` discriminator — the runtime wrapper locks it,
870
+ // so consumers don't need to repeat the variant in JSX.
871
+ export type AlertProps = Omit<BannerProps, "variant">;
872
+ export const Alert: React.ForwardRefExoticComponent<AlertProps & React.RefAttributes<HTMLDivElement>>;
873
+ export type SheetProps = Omit<BottomSheetProps, "variant">;
874
+ export const Sheet: React.ForwardRefExoticComponent<SheetProps & React.RefAttributes<HTMLDivElement>>;
875
+ export type DrawerProps = Omit<BottomSheetProps, "variant">;
876
+ export const Drawer: React.ForwardRefExoticComponent<DrawerProps & React.RefAttributes<HTMLDivElement>>;
877
+ export type ChannelListProps = Omit<SuggestionListProps, "variant">;
878
+ export const ChannelList: React.ForwardRefExoticComponent<ChannelListProps & React.RefAttributes<HTMLDivElement>>;
879
+ export type ChannelRailProps = Omit<AvatarRailProps, "variant">;
880
+ export const ChannelRail: React.ForwardRefExoticComponent<ChannelRailProps & React.RefAttributes<HTMLDivElement>>;
881
+ export type InputProps = Omit<FormFieldInputProps, "variant">;
882
+ export const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
883
+ export type TextareaProps = Omit<FormFieldTextareaProps, "variant">;
884
+ export const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLElement>>;
885
+ export type SearchBarProps = Omit<FormFieldSearchProps, "variant">;
886
+ export const SearchBar: React.ForwardRefExoticComponent<SearchBarProps & React.RefAttributes<HTMLInputElement>>;
887
+ export type SelectProps = Omit<FormFieldSelectProps, "variant">;
888
+ export const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLDivElement>>;
889
+ export type AppBarProps = Omit<NavigationBarMainProps, "variant">;
890
+ export const AppBar: React.ForwardRefExoticComponent<AppBarProps & React.RefAttributes<HTMLElement>>;
891
+ export type SideDrawerProps = Omit<SideSheetProps, "variant">;
892
+ export const SideDrawer: React.ForwardRefExoticComponent<SideDrawerProps & React.RefAttributes<HTMLElement>>;
893
+ export type BottomNavProps = Omit<TabBarProps, "variant">;
894
+ export const BottomNav: React.ForwardRefExoticComponent<BottomNavProps & React.RefAttributes<HTMLElement>>;
895
+ export type AvatarProps = Omit<ThumbnailProps, "variant">;
896
+ export const Avatar: React.ForwardRefExoticComponent<AvatarProps & React.RefAttributes<HTMLDivElement>>;