@teamblind-chorus/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/agents/AGENTS.md +143 -0
- package/agents/DESIGN.md +1311 -0
- package/agents/LOVABLE.md +472 -0
- package/agents/anti-patterns.md +533 -0
- package/agents/catalog.md +232 -0
- package/agents/components/avatar-rail/avatar-rail.family.json +46 -0
- package/agents/components/avatar-rail/avatar-rail.md +103 -0
- package/agents/components/avatar-rail/avatar-rail.spec.json +160 -0
- package/agents/components/badge/badge.family.json +45 -0
- package/agents/components/badge/badge.md +10 -0
- package/agents/components/badge/role.md +100 -0
- package/agents/components/badge/role.spec.json +75 -0
- package/agents/components/badge/update.md +132 -0
- package/agents/components/badge/update.spec.json +114 -0
- package/agents/components/banner/banner.family.json +28 -0
- package/agents/components/banner/banner.md +136 -0
- package/agents/components/banner/banner.spec.json +136 -0
- package/agents/components/bottom-sheet/bottom-sheet.family.json +29 -0
- package/agents/components/bottom-sheet/bottom-sheet.md +176 -0
- package/agents/components/bottom-sheet/bottom-sheet.spec.json +168 -0
- package/agents/components/bubble/bubble.family.json +29 -0
- package/agents/components/bubble/bubble.md +134 -0
- package/agents/components/bubble/bubble.spec.json +91 -0
- package/agents/components/button/button.family.json +76 -0
- package/agents/components/button/button.md +31 -0
- package/agents/components/button/check.md +138 -0
- package/agents/components/button/check.spec.json +161 -0
- package/agents/components/button/fab.md +161 -0
- package/agents/components/button/fab.spec.json +106 -0
- package/agents/components/button/icon.md +141 -0
- package/agents/components/button/icon.spec.json +164 -0
- package/agents/components/button/standard.md +219 -0
- package/agents/components/button/standard.spec.json +205 -0
- package/agents/components/button/text.md +186 -0
- package/agents/components/button/text.spec.json +215 -0
- package/agents/components/button/toggle.md +108 -0
- package/agents/components/button/toggle.spec.json +124 -0
- package/agents/components/button/toolbar.md +189 -0
- package/agents/components/button/toolbar.spec.json +109 -0
- package/agents/components/carousel/carousel.family.json +41 -0
- package/agents/components/carousel/carousel.md +40 -0
- package/agents/components/carousel/post.md +148 -0
- package/agents/components/carousel/post.spec.json +229 -0
- package/agents/components/carousel/profile.md +184 -0
- package/agents/components/carousel/profile.spec.json +219 -0
- package/agents/components/chip/chip.family.json +37 -0
- package/agents/components/chip/chip.md +10 -0
- package/agents/components/chip/filter.md +212 -0
- package/agents/components/chip/filter.spec.json +124 -0
- package/agents/components/chip/tag.md +137 -0
- package/agents/components/chip/tag.spec.json +104 -0
- package/agents/components/dialog/dialog.family.json +29 -0
- package/agents/components/dialog/dialog.md +113 -0
- package/agents/components/dialog/dialog.spec.json +156 -0
- package/agents/components/directory-list/directory-list.family.json +46 -0
- package/agents/components/directory-list/directory-list.md +87 -0
- package/agents/components/directory-list/directory-list.spec.json +104 -0
- package/agents/components/divider/divider.family.json +28 -0
- package/agents/components/divider/divider.md +78 -0
- package/agents/components/divider/divider.spec.json +51 -0
- package/agents/components/feed/ad.md +108 -0
- package/agents/components/feed/ad.spec.json +187 -0
- package/agents/components/feed/feed.family.json +48 -0
- package/agents/components/feed/feed.md +30 -0
- package/agents/components/feed/post.md +240 -0
- package/agents/components/feed/post.spec.json +361 -0
- package/agents/components/form-field/form-field.family.json +50 -0
- package/agents/components/form-field/form-field.md +11 -0
- package/agents/components/form-field/input.md +198 -0
- package/agents/components/form-field/input.spec.json +202 -0
- package/agents/components/form-field/search.md +81 -0
- package/agents/components/form-field/search.spec.json +135 -0
- package/agents/components/form-field/select.md +101 -0
- package/agents/components/form-field/select.spec.json +194 -0
- package/agents/components/form-field/textarea.md +89 -0
- package/agents/components/form-field/textarea.spec.json +176 -0
- package/agents/components/header/header.family.json +43 -0
- package/agents/components/header/header.md +18 -0
- package/agents/components/header/main.md +101 -0
- package/agents/components/header/main.spec.json +117 -0
- package/agents/components/header/sub.md +129 -0
- package/agents/components/header/sub.spec.json +81 -0
- package/agents/components/list/accordion.md +183 -0
- package/agents/components/list/accordion.spec.json +201 -0
- package/agents/components/list/entry.md +280 -0
- package/agents/components/list/entry.spec.json +237 -0
- package/agents/components/list/list.family.json +75 -0
- package/agents/components/list/list.md +24 -0
- package/agents/components/list/radio.md +144 -0
- package/agents/components/list/radio.spec.json +186 -0
- package/agents/components/list/standard.md +262 -0
- package/agents/components/list/standard.spec.json +221 -0
- package/agents/components/metadata/compact.md +69 -0
- package/agents/components/metadata/compact.spec.json +69 -0
- package/agents/components/metadata/metadata.family.json +42 -0
- package/agents/components/metadata/metadata.md +26 -0
- package/agents/components/metadata/standard.md +104 -0
- package/agents/components/metadata/standard.spec.json +152 -0
- package/agents/components/nav-card/nav-card.family.json +29 -0
- package/agents/components/nav-card/nav-card.md +179 -0
- package/agents/components/nav-card/nav-card.spec.json +161 -0
- package/agents/components/nav-list/nav-list.family.json +46 -0
- package/agents/components/nav-list/nav-list.md +91 -0
- package/agents/components/nav-list/nav-list.spec.json +107 -0
- package/agents/components/navigation-bar/main.md +201 -0
- package/agents/components/navigation-bar/main.spec.json +109 -0
- package/agents/components/navigation-bar/navigation-bar.family.json +44 -0
- package/agents/components/navigation-bar/navigation-bar.md +21 -0
- package/agents/components/navigation-bar/search.md +96 -0
- package/agents/components/navigation-bar/search.spec.json +142 -0
- package/agents/components/navigation-bar/sub.md +174 -0
- package/agents/components/navigation-bar/sub.spec.json +123 -0
- package/agents/components/page-shell/page-shell.family.json +22 -0
- package/agents/components/page-shell/page-shell.md +51 -0
- package/agents/components/profile-header/profile-header.family.json +29 -0
- package/agents/components/profile-header/profile-header.md +149 -0
- package/agents/components/profile-header/profile-header.spec.json +200 -0
- package/agents/components/progress/progress.family.json +27 -0
- package/agents/components/progress/progress.md +38 -0
- package/agents/components/progress/progress.spec.json +67 -0
- package/agents/components/side-sheet/side-sheet.family.json +30 -0
- package/agents/components/side-sheet/side-sheet.md +154 -0
- package/agents/components/side-sheet/side-sheet.spec.json +109 -0
- package/agents/components/skeleton/skeleton.family.json +28 -0
- package/agents/components/skeleton/skeleton.md +123 -0
- package/agents/components/skeleton/skeleton.spec.json +73 -0
- package/agents/components/status-tag/status-tag.family.json +26 -0
- package/agents/components/status-tag/status-tag.md +114 -0
- package/agents/components/status-tag/status-tag.spec.json +69 -0
- package/agents/components/suggestion-list/suggestion-list.family.json +46 -0
- package/agents/components/suggestion-list/suggestion-list.md +91 -0
- package/agents/components/suggestion-list/suggestion-list.spec.json +178 -0
- package/agents/components/switch/switch.family.json +27 -0
- package/agents/components/switch/switch.md +114 -0
- package/agents/components/switch/switch.spec.json +123 -0
- package/agents/components/tab-bar/tab-bar.family.json +27 -0
- package/agents/components/tab-bar/tab-bar.md +178 -0
- package/agents/components/tab-bar/tab-bar.spec.json +184 -0
- package/agents/components/tabs/rounded.md +150 -0
- package/agents/components/tabs/rounded.spec.json +140 -0
- package/agents/components/tabs/segmented.md +114 -0
- package/agents/components/tabs/segmented.spec.json +100 -0
- package/agents/components/tabs/tabs.family.json +59 -0
- package/agents/components/tabs/tabs.md +18 -0
- package/agents/components/tabs/underline.md +147 -0
- package/agents/components/tabs/underline.spec.json +139 -0
- package/agents/components/thumbnail/thumbnail.family.json +28 -0
- package/agents/components/thumbnail/thumbnail.md +152 -0
- package/agents/components/thumbnail/thumbnail.spec.json +172 -0
- package/agents/components/toast/toast.family.json +28 -0
- package/agents/components/toast/toast.md +133 -0
- package/agents/components/toast/toast.spec.json +89 -0
- package/agents/components/tooltip/tooltip.family.json +29 -0
- package/agents/components/tooltip/tooltip.md +139 -0
- package/agents/components/tooltip/tooltip.spec.json +110 -0
- package/agents/compose.md +240 -0
- package/agents/icons.json +831 -0
- package/agents/images.md +66 -0
- package/agents/manifest.json +87 -0
- package/agents/patterns/README.md +59 -0
- package/agents/patterns/actions.md +50 -0
- package/agents/patterns/browsing.md +52 -0
- package/agents/patterns/communications.md +56 -0
- package/agents/patterns/layout.md +72 -0
- package/agents/patterns/modals.md +50 -0
- package/agents/patterns/visual.md +55 -0
- package/agents/reconstruct.md +55 -0
- package/agents/scoped-adoption.md +111 -0
- package/agents/tokens.usage.json +1657 -0
- package/agents/usage.json +422 -0
- package/dist/icons/index.cjs +1332 -0
- package/dist/icons/index.cjs.map +1 -0
- package/dist/icons/index.d.cts +228 -0
- package/dist/icons/index.d.ts +228 -0
- package/dist/icons/index.js +1114 -0
- package/dist/icons/index.js.map +1 -0
- package/dist/index.cjs +5905 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +896 -0
- package/dist/index.d.ts +896 -0
- package/dist/index.js +5847 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +5765 -0
- package/eslint/README.md +79 -0
- package/eslint/index.js +78 -0
- package/eslint/rules.js +472 -0
- package/eslint/test.mjs +135 -0
- package/package.json +96 -0
- package/placeholder.png +0 -0
|
@@ -0,0 +1,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.
|