@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,219 @@
1
+ # Standard
2
+
3
+ The default inline action surface — a labelled commit (form submit, dialog confirm, row action). Two axes: **size** (`large` / `medium` / `small`), **appearance** (`primary` / `secondary` / `outlined` / `tertiary`).
4
+
5
+ **Reach for this when** you need a labelled commit inline with content — Save, Continue, Confirm, Cancel. **Skip when** the commit must float above scrolling content ([FAB](./fab.md)), the rung is body-text-sized ([Text Button](./text.md)), or the row is a dense toolbar ([Toolbar Button](./toolbar.md)).
6
+
7
+ **Layout inset.** inline — content-sized (or `fullWidth`); inherits its surface's padding.
8
+
9
+ ## Default
10
+
11
+ The single highest-emphasis action — `appearance="primary"`. One per view (Save, Continue, Confirm, Submit).
12
+
13
+ ```preview
14
+ button/standard/default
15
+ ---
16
+ import { Button } from '@teamblind-chorus/ui';
17
+
18
+ <Button appearance="primary" size="large">
19
+ Primary action
20
+ </Button>
21
+ ```
22
+
23
+ ## Use cases
24
+
25
+ ### Secondary
26
+
27
+ Lower-emphasis tier paired against `primary` — opposing actions (Cancel beside Save) or quieter alternatives. Safe to repeat on a single view.
28
+
29
+ ```preview
30
+ button/standard/secondary
31
+ ---
32
+ import { Button } from '@teamblind-chorus/ui';
33
+
34
+ <Button appearance="secondary" size="large">
35
+ Secondary action
36
+ </Button>
37
+ ```
38
+
39
+ ### Outlined
40
+
41
+ Bordered blue-on-transparent — supplementary option beside `primary` (*See more*, *Learn more*, *Skip for now*). For opposing paths use `secondary`.
42
+
43
+ ```preview
44
+ button/standard/outlined
45
+ ---
46
+ import { Button } from '@teamblind-chorus/ui';
47
+
48
+ <Button appearance="outlined" size="large">
49
+ See more
50
+ </Button>
51
+ ```
52
+
53
+ ### Tertiary
54
+
55
+ Neutral grey ghost — transparent at rest, label in `sys.color.onSurfaceVariant`. Reads as a button only on hover.
56
+
57
+ ```preview
58
+ button/standard/tertiary
59
+ ---
60
+ import { Button } from '@teamblind-chorus/ui';
61
+
62
+ <Button appearance="tertiary" size="large">
63
+ Tertiary action
64
+ </Button>
65
+ ```
66
+
67
+ ### With leading icon
68
+
69
+ Optional context glyph before the label. Inherits label color via `currentColor` (`sys.icon.lg` on `large`, `sys.icon.md` on `medium`/`small`).
70
+
71
+ ```preview
72
+ button/standard/with-leading-icon
73
+ ---
74
+ import { Button } from '@teamblind-chorus/ui';
75
+ import { PlusIcon } from '@teamblind-chorus/ui/icons';
76
+
77
+ <Button
78
+ appearance="primary"
79
+ size="large"
80
+ leadingIcon={<PlusIcon />}
81
+ >
82
+ Add item
83
+ </Button>
84
+ ```
85
+
86
+ ### Full width
87
+
88
+ Stretched to fill the column (`width: 100%`). Default mobile shape for hero surfaces, empty states, onboarding, login. On wider surfaces, fall back to content-sized.
89
+
90
+ ```preview
91
+ button/standard/full-width
92
+ ---
93
+ import { Button } from '@teamblind-chorus/ui';
94
+
95
+ <Button
96
+ appearance="primary"
97
+ size="large"
98
+ fullWidth
99
+ >
100
+ Confirm
101
+ </Button>
102
+ ```
103
+
104
+ ### Group
105
+
106
+ Adjacent Buttons share an **8px** gap (`sys.layout.inline.md` horizontal / `sys.layout.stack.xs` vertical). Horizontal: outlined left, primary right. Vertical: primary top, secondary below.
107
+
108
+ ```preview
109
+ button/standard/group
110
+ ---
111
+ import { Button } from '@teamblind-chorus/ui';
112
+
113
+ <div style={{ display: 'flex', gap: 8 }}>
114
+ <Button appearance="outlined" size="large">
115
+ See more
116
+ </Button>
117
+ <Button appearance="primary" size="large">
118
+ Confirm
119
+ </Button>
120
+ </div>
121
+ ```
122
+
123
+ ```preview
124
+ button/standard/group-vertical
125
+ ---
126
+ import { Button } from '@teamblind-chorus/ui';
127
+
128
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
129
+ <Button appearance="primary" size="large">
130
+ Save
131
+ </Button>
132
+ <Button appearance="secondary" size="large">
133
+ Cancel
134
+ </Button>
135
+ </div>
136
+ ```
137
+
138
+ ### Truncation
139
+
140
+ When the column is narrower than the label, it clips with an ellipsis — Buttons are single-line by contract.
141
+
142
+ ```preview
143
+ button/standard/truncation
144
+ ---
145
+ import { Button } from '@teamblind-chorus/ui';
146
+
147
+ <Button
148
+ appearance="primary"
149
+ size="large"
150
+ fullWidth
151
+ truncate
152
+ >
153
+ A very long label that should truncate gracefully
154
+ </Button>
155
+ ```
156
+
157
+ ### Focus indicator
158
+
159
+ Standard keyboard-focus ring (see [Focus ring composition](../../DESIGN.md#focus-ring-composition)).
160
+
161
+ ```preview
162
+ button/standard/focused
163
+ ---
164
+ import { Button } from '@teamblind-chorus/ui';
165
+
166
+ <Button appearance="primary" size="large" state="focused">
167
+ Primary action
168
+ </Button>
169
+ ```
170
+
171
+ ## Slots
172
+
173
+ - **label** — accessible name. Required, single line; long labels truncate.
174
+ - **leadingIcon** (optional) — context glyph before the label. Inherits via `currentColor` — see [Button → Icon colour inheritance](./button.md#icon-colour-inheritance).
175
+
176
+ No trailing icon.
177
+
178
+ ## Appearance
179
+
180
+ A **destructive** flavor swaps `primary` → `error` across any appearance; reserved for irreversible actions.
181
+
182
+ | Appearance | Background | Border (1px) | Label color | Notes |
183
+ |-------------|---------------|--------------------------|-----------------------------------|-----------------------------------------------------------------------|
184
+ | `primary` | `sys.color.primary` | — | `sys.color.onPrimary` | Single highest-emphasis action; one per view. |
185
+ | `secondary` | `sys.color.secondaryContainer` | — | `sys.color.onSecondaryContainer` | Lower-emphasis tier; opposing-action and quieter-alternative roles. |
186
+ | `outlined` | `transparent` | `sys.color.primary` (`sys.borderWidth.hairline`) | `sys.color.primary` | Supplementary option beside `primary`. |
187
+ | `tertiary` | `transparent` | — | `sys.color.onSurfaceVariant` | Lowest-emphasis neutral ghost. |
188
+
189
+ ## Sizes
190
+
191
+ | Size | Padding (block × Inline) | Gap (icon ↔ label) | Min-height | Min-width | Radius | Label | Icon |
192
+ |----------|---------------------------------------------------------------------|-------------------------------|--------------------------|----------------------------|-----------------------|------------------------------------|---------------------|
193
+ | `large` | `sys.layout.container.xs` × `sys.layout.container.md` (8 × 16) | `sys.layout.inline.md` (8) | `ref.space.600` (48) ‡ | **160** ⁂ | `sys.radius.md` (8) | `sys.typo.label.lg` (16, semibold) | `sys.icon.lg` (24) |
194
+ | `medium` | `sys.layout.container.xs` × `sys.layout.container.md` (8 × 16) | `sys.layout.inline.md` (8) | `ref.space.500` (40) ‡ | **160** ⁂ | `sys.radius.md` (8) | `sys.typo.label.md` (14, semibold) | `sys.icon.md` (16) |
195
+ | `small` | `sys.layout.container.2xs` × `sys.layout.container.sm` (4 × 12) | `sys.layout.inline.sm` (4) | `ref.space.400` (32) ‡ | **160** ⁂ | `sys.radius.sm` (4) † | `sys.typo.label.md` (14, semibold) | `sys.icon.md` (16) |
196
+
197
+ † Small Button's intended radius is 6px; implementation falls back to `sys.radius.sm` (4) until a 6px step lands.
198
+
199
+ ‡ `min-height` binds raw `ref.space.*` — `sys.*` does not expose 32 / 40 / 48 px steps.
200
+
201
+ ⁂ **Min-width 160px** is fixed across sizes so a row of buttons reads as one composition. `fullWidth` and `truncate` override.
202
+
203
+ ## States
204
+
205
+ | State | Overlay opacity | Additional |
206
+ |------------|----------------------------|-----------------------------------------------------------------------------|
207
+ | `default` | — | Container + label at rest. |
208
+ | `hovered` | `sys.state.hover` (8%) | Pointer-driven via `:hover`. |
209
+ | `pressed` | `sys.state.pressed` (16%) | Pointer-driven via `:active`. |
210
+ | `disabled` | overlay suppressed | Container at `sys.state.disabled` (40%) opacity, focus ring suppressed, `cursor: not-allowed`. |
211
+
212
+ ## Focus indicator
213
+
214
+ Standard outward ring drawn as a `position: absolute` pseudo-element so it never affects layout. Trigger: `:focus-visible`. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
215
+
216
+ ## Behavior
217
+
218
+ - **Chrome-aligned filled form** — layout by chrome (see [Button → Optical alignment](./button.md#optical-alignment)).
219
+ - **Single line.** Long labels truncate; 160px min-width keeps a row reading as one composition unless `fullWidth` or `truncate` overrides.
@@ -0,0 +1,205 @@
1
+ {
2
+ "$schema": "../../spec.schema.json",
3
+ "name": "Button",
4
+ "family": "button",
5
+ "subcomponent": "standard",
6
+ "description": "Standard inline action button. Two independent axes: size (chosen by surface), appearance (chosen by emphasis). Fixed-footprint. The default button form — reach for FAB / Toolbar Button / Toggle Button when the standard inline shape does not fit.",
7
+ "element": "button",
8
+ "props": {
9
+ "appearance": {
10
+ "type": "enum",
11
+ "values": [
12
+ "primary",
13
+ "secondary",
14
+ "outlined",
15
+ "tertiary"
16
+ ],
17
+ "default": "primary"
18
+ },
19
+ "size": {
20
+ "type": "enum",
21
+ "values": [
22
+ "large",
23
+ "medium",
24
+ "small"
25
+ ],
26
+ "default": "large"
27
+ },
28
+ "leadingIcon": {
29
+ "type": "node",
30
+ "optional": true
31
+ },
32
+ "fullWidth": {
33
+ "type": "boolean",
34
+ "default": false
35
+ },
36
+ "truncate": {
37
+ "type": "boolean",
38
+ "default": false
39
+ },
40
+ "disabled": {
41
+ "type": "boolean",
42
+ "default": false
43
+ }
44
+ },
45
+ "slots": {
46
+ "label": {
47
+ "required": true,
48
+ "description": "Accessible name. Single line.",
49
+ "accepts": [
50
+ "text"
51
+ ]
52
+ },
53
+ "leadingIcon": {
54
+ "required": false,
55
+ "description": "Optional context glyph before the label. Inherits currentColor.",
56
+ "accepts": [
57
+ "icon"
58
+ ]
59
+ }
60
+ },
61
+ "sizes": {
62
+ "large": {
63
+ "paddingBlock": "sys.layout.container.xs",
64
+ "paddingInline": "sys.layout.container.md",
65
+ "gap": "sys.layout.inline.md",
66
+ "minHeight": "ref.space.600",
67
+ "minWidth": "160px",
68
+ "radius": "sys.radius.md",
69
+ "labelTypo": "sys.typo.label.lg",
70
+ "iconSize": "sys.icon.lg"
71
+ },
72
+ "medium": {
73
+ "paddingBlock": "sys.layout.container.xs",
74
+ "paddingInline": "sys.layout.container.md",
75
+ "gap": "sys.layout.inline.md",
76
+ "minHeight": "ref.space.500",
77
+ "minWidth": "160px",
78
+ "radius": "sys.radius.md",
79
+ "labelTypo": "sys.typo.label.md",
80
+ "iconSize": "sys.icon.md"
81
+ },
82
+ "small": {
83
+ "paddingBlock": "sys.layout.container.2xs",
84
+ "paddingInline": "sys.layout.container.sm",
85
+ "gap": "sys.layout.inline.sm",
86
+ "minHeight": "ref.space.400",
87
+ "minWidth": "160px",
88
+ "radius": "sys.radius.sm",
89
+ "labelTypo": "sys.typo.label.md",
90
+ "iconSize": "sys.icon.md"
91
+ }
92
+ },
93
+ "appearances": {
94
+ "primary": {
95
+ "background": "sys.color.primary",
96
+ "border": null,
97
+ "label": "sys.color.onPrimary"
98
+ },
99
+ "secondary": {
100
+ "background": "sys.color.secondaryContainer",
101
+ "border": null,
102
+ "label": "sys.color.onSecondaryContainer"
103
+ },
104
+ "outlined": {
105
+ "background": "transparent",
106
+ "border": {
107
+ "width": "sys.borderWidth.hairline",
108
+ "color": "sys.color.primary"
109
+ },
110
+ "label": "sys.color.primary"
111
+ },
112
+ "tertiary": {
113
+ "background": "transparent",
114
+ "border": null,
115
+ "label": "sys.color.onSurfaceVariant"
116
+ }
117
+ },
118
+ "flavors": {
119
+ "destructive": {
120
+ "description": "Swaps the primary family → error family across every appearance. Reserved for irreversible commits (Delete, Remove, Discard).",
121
+ "appearances": {
122
+ "primary": {
123
+ "background": "sys.color.error",
124
+ "border": null,
125
+ "label": "sys.color.onError"
126
+ },
127
+ "secondary": {
128
+ "background": "sys.color.errorContainer",
129
+ "border": null,
130
+ "label": "sys.color.onErrorContainer"
131
+ },
132
+ "outlined": {
133
+ "background": "transparent",
134
+ "border": {
135
+ "width": "sys.borderWidth.hairline",
136
+ "color": "sys.color.error"
137
+ },
138
+ "label": "sys.color.error"
139
+ },
140
+ "tertiary": {
141
+ "background": "transparent",
142
+ "border": null,
143
+ "label": "sys.color.error"
144
+ }
145
+ }
146
+ }
147
+ },
148
+ "states": {
149
+ "default": {
150
+ "overlay": null
151
+ },
152
+ "hovered": {
153
+ "overlay": {
154
+ "color": "label",
155
+ "opacity": "sys.state.hover"
156
+ }
157
+ },
158
+ "pressed": {
159
+ "overlay": {
160
+ "color": "label",
161
+ "opacity": "sys.state.pressed"
162
+ }
163
+ },
164
+ "disabled": {
165
+ "overlay": null,
166
+ "containerOpacity": "sys.state.disabled",
167
+ "suppressFocusRing": true,
168
+ "cursor": "not-allowed"
169
+ }
170
+ },
171
+ "focusIndicator": {
172
+ "description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
173
+ "composition": "outward",
174
+ "compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
175
+ "overlay": {
176
+ "color": "label",
177
+ "opacity": "sys.state.focus"
178
+ },
179
+ "ring": {
180
+ "outerWidth": "sys.borderWidth.thin",
181
+ "outerColor": "sys.color.focus",
182
+ "insetWidth": "sys.borderWidth.hairline",
183
+ "insetColor": "sys.color.focusInset"
184
+ },
185
+ "trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
186
+ },
187
+ "behavior": {
188
+ "labelLine": "single",
189
+ "truncation": {
190
+ "whiteSpace": "nowrap",
191
+ "overflow": "hidden",
192
+ "textOverflow": "ellipsis"
193
+ },
194
+ "fullWidth": {
195
+ "width": "100%"
196
+ }
197
+ },
198
+ "forbidden": [
199
+ "raw <button> wrapper styled with Tailwind / inline-style instead of the chorus-button--standard chrome",
200
+ "appearance=tertiary / outlined used as the screen's primary commit — the highest-emphasis commit uses appearance=primary; the lower-emphasis appearances are for secondary / supporting actions",
201
+ "destructive flavor used outside Dialog / BottomSheet primary-action context",
202
+ "raw `border:` declaration — the edge stroke is an inset box-shadow",
203
+ "manual focus ring via `outline:` — the ring is the `::after` overlay layer"
204
+ ]
205
+ }
@@ -0,0 +1,186 @@
1
+ # Text
2
+
3
+ The link-shaped commit surface — reads as text at rest, paints a button-like hover overlay and focus ring on interaction. Two axes: **appearance** (`default` / `accent` / `onPrimary` / `inverse`), **size** (`medium` / `small` / `xsmall`).
4
+
5
+ **Reach for this when** the action is inline next to typographic content and commits — *Skip*, *Edit*, *Resend*, a section's trailing *See all*. **Skip when** the affordance navigates — use [Text link](../../DESIGN.md#text-links).
6
+
7
+ **Layout inset.** inline — optical alignment is on by default, so the visible label box *is* the layout box; chrome bleeds outward only on hover.
8
+
9
+ ## Default
10
+
11
+ Neutral label (`onSurfaceVariant`) in a transparent capsule — the base inline action ("Not now", trailing dismissals).
12
+
13
+ ```preview
14
+ button/text/default
15
+ ---
16
+ import { Button } from '@teamblind-chorus/ui';
17
+
18
+ <Button variant="text">Not now</Button>
19
+ ```
20
+
21
+ ## Use cases
22
+
23
+ ### Accent
24
+
25
+ Brand-blue label (`primary`) — the inline commit. Reach for `accent` when the button reads as a navigational link (*See all*, *Follow*, *View details*).
26
+
27
+ ```preview
28
+ button/text/accent
29
+ ---
30
+ import { Button } from '@teamblind-chorus/ui';
31
+
32
+ <Button variant="text" appearance="accent">Skip</Button>
33
+ ```
34
+
35
+ ### On primary
36
+
37
+ Always-white label on top of a `primary`-filled host (Tooltip `default`). Theme-stable.
38
+
39
+ ```preview
40
+ button/text/on-primary
41
+ ---
42
+ import { Button } from '@teamblind-chorus/ui';
43
+
44
+ <Button variant="text" appearance="onPrimary">Got it</Button>
45
+ ```
46
+
47
+ ### Inverse
48
+
49
+ For inverse hosts (Toast, coach-mark, snackbar). Label paints `inverseOnSurface`; tokens flip with theme.
50
+
51
+ ```preview
52
+ button/text/inverse
53
+ ---
54
+ import { Button } from '@teamblind-chorus/ui';
55
+
56
+ <Button variant="text" appearance="inverse">Undo</Button>
57
+ ```
58
+
59
+ ### With leading icon
60
+
61
+ 16px (`sys.icon.md`) glyph before the label at 4px gap — fixed across rungs.
62
+
63
+ ```preview
64
+ button/text/leading-icon
65
+ ---
66
+ import { Button, ChevronLeftIcon } from '@teamblind-chorus/ui';
67
+
68
+ <Button variant="text" leadingIcon={<ChevronLeftIcon />}>Back</Button>
69
+ ```
70
+
71
+ ### With trailing icon
72
+
73
+ Destination glyph after the label — chevron-right *Continue*, external-link *Open in new tab*.
74
+
75
+ ```preview
76
+ button/text/trailing-icon
77
+ ---
78
+ import { Button, ChevronRightIcon } from '@teamblind-chorus/ui';
79
+
80
+ <Button variant="text" trailingIcon={<ChevronRightIcon />}>Continue</Button>
81
+ ```
82
+
83
+ ### Dropdown
84
+
85
+ Disclosure trigger: the label reads as the **current value**, the trailing chevron flips with state — `ChevronDownIcon` at rest, `ChevronUpIcon` while the menu is open. Pair `aria-haspopup` + `aria-expanded` on the trigger and portal the menu so it escapes any clipping ancestor; never freeze the chevron when the menu is open.
86
+
87
+ Default rung is `xsmall` for inline / toolbar dropdowns (Size selectors, filter chrome, header trailing). Step up to `small` or `medium` when the dropdown is the row's primary commit.
88
+
89
+ ```preview
90
+ button/text/dropdown
91
+ ---
92
+ import { useState } from 'react';
93
+ import { Button } from '@teamblind-chorus/ui';
94
+ import { ChevronDownIcon, ChevronUpIcon } from '@teamblind-chorus/ui/icons';
95
+
96
+ function Example() {
97
+ const [open, setOpen] = useState(false);
98
+ return (
99
+ <Button
100
+ variant="text"
101
+ size="xsmall"
102
+ trailingIcon={open ? <ChevronUpIcon /> : <ChevronDownIcon />}
103
+ aria-haspopup="listbox"
104
+ aria-expanded={open}
105
+ onClick={() => setOpen((v) => !v)}
106
+ >
107
+ Medium
108
+ </Button>
109
+ );
110
+ }
111
+ ```
112
+
113
+ ### Group
114
+
115
+ Optical alignment means chrome-to-chrome gap **is** the visible label-to-label distance. Row gap: `medium`/`small` → 16px (`sys.layout.inline.xl`); `xsmall` → 12px (`sys.layout.inline.lg`).
116
+
117
+ ```preview
118
+ button/text/group
119
+ ---
120
+ import { Button } from '@teamblind-chorus/ui';
121
+
122
+ <div style={{ display: 'inline-flex', gap: 'var(--sys-layout-inline-md)' }}>
123
+ <Button variant="text">Cancel</Button>
124
+ <Button variant="text" appearance="accent">Save</Button>
125
+ </div>
126
+ ```
127
+
128
+ ### Focus indicator
129
+
130
+ Standard ring.
131
+
132
+ ```preview
133
+ button/text/focused
134
+ ---
135
+ import { Button } from '@teamblind-chorus/ui';
136
+
137
+ <Button variant="text" state="focused">Skip</Button>
138
+ ```
139
+
140
+ ## Slots
141
+
142
+ - **label** — accessible name. Required, single line.
143
+ - **leadingIcon** (optional) — context glyph before the label. 16px on every rung. Inherits via `currentColor`.
144
+ - **trailingIcon** (optional) — directional/destination glyph after the label.
145
+
146
+ ## Appearance
147
+
148
+ A **destructive** flavor swaps the label to `error` across every appearance.
149
+
150
+ | Appearance | Background (rest) | Label color | When to reach for it |
151
+ |-------------|-------------------|-----------------------------------|--------------------------------------------------------------------------------------|
152
+ | `default` | `transparent` | `sys.color.onSurfaceVariant` | Base inline action — "Not now", secondary inline trail commits. |
153
+ | `accent` | `transparent` | `sys.color.primary` | Brand-blue inline commit — "Skip", "See all". One per row. |
154
+ | `onPrimary` | `transparent` | `sys.color.onPrimary` | On a `primary`-filled host (Tooltip `default`). Theme-stable. |
155
+ | `inverse` | `transparent` | `sys.color.inverseOnSurface` | Inside an inverse host (Toast, coach-mark). |
156
+
157
+ ## Sizes
158
+
159
+ Three rungs. `medium` matches [Icon Button](./icon.md)'s 40-tall footprint; `small` / `xsmall` for denser call-sites. Icon and slot gap stay fixed (16px / 4px); only label rank and capsule height shrink.
160
+
161
+ | Size | Min-height | Padding (block × inline) | Slot gap | Label | Icon |
162
+ |-----------|------------------|----------------------------------|-----------------------------------|--------------------------------|-------------------------------|
163
+ | `medium` | 40px (`ref.space.500`) ‡ | 8 × 8 (`sys.layout.container.xs` × `sys.layout.container.xs`) | 4px (`sys.layout.inline.sm`) | 16 / Semibold (`sys.typo.heading.sm`) | 16px (`sys.icon.md`) |
164
+ | `small` | 32px (`ref.space.400`) ‡ | 4 × 8 (`sys.layout.container.2xs` × `sys.layout.container.xs`) | 4px (`sys.layout.inline.sm`) | 14 / Semibold (`sys.typo.label.md`) | 16px (`sys.icon.md`) |
165
+ | `xsmall` | 24px (`ref.space.300`) ‡ | 4 × 8 (`sys.layout.container.2xs` × `sys.layout.container.xs`) | 4px (`sys.layout.inline.sm`) | 12 / Semibold (`sys.typo.label.sm`) | 16px (`sys.icon.md`) |
166
+
167
+ ‡ `min-height` binds raw `ref.space.*` — `sys.*` does not expose 24/32/40 px steps.
168
+
169
+ ## States
170
+
171
+ Overlay paints the **label color** over the transparent container.
172
+
173
+ | State | Overlay opacity | Additional |
174
+ |------------|----------------------------|-----------------------------------------------------------------------------|
175
+ | `default` | — | Transparent capsule. |
176
+ | `hovered` | `sys.state.hover` (8%) | `:hover`. |
177
+ | `pressed` | `sys.state.pressed` (16%) | `:active`. |
178
+ | `disabled` | overlay suppressed | Label at `sys.state.disabled` (40%) opacity, focus ring suppressed, `cursor: not-allowed`. |
179
+
180
+ ## Focus indicator
181
+
182
+ Standard outward ring. Trigger: `:focus-visible`. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
183
+
184
+ ## Optical alignment
185
+
186
+ Transparent at rest — the eye locks onto the label. Default rendering negates per-rung padding via `margin: calc(-1 × padding-block) calc(-1 × padding-inline)` so the visible **label box is the layout box**. Not opt-in.