@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,212 @@
|
|
|
1
|
+
# Filter
|
|
2
|
+
|
|
3
|
+
The selectable chip — a capsule-shaped toggle for refining a set. Unselected is a transparent hairline-outlined pill that adopts whatever surface sits behind it; selected swaps to an inverse fill. Optional leading/trailing icons compose without changing footprint.
|
|
4
|
+
|
|
5
|
+
**Reach for this when** the user narrows a set by toggling one or more independent criteria — multiple chips can be on at once. **Skip when** the choices are mutually-exclusive view modes — use [Segmented](../tabs/segmented.md) instead.
|
|
6
|
+
|
|
7
|
+
**Layout inset.** inline — content-sized, inherits its surface's padding; rails compose via gap, not chrome.
|
|
8
|
+
|
|
9
|
+
## Default
|
|
10
|
+
|
|
11
|
+
At-rest — transparent fill with a hairline `outlineVariant` stroke so the chip sits on any surface without colliding with the surface ladder.
|
|
12
|
+
|
|
13
|
+
```preview
|
|
14
|
+
chip/filter/unselected
|
|
15
|
+
---
|
|
16
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
17
|
+
|
|
18
|
+
<Chip variant="filter">
|
|
19
|
+
All
|
|
20
|
+
</Chip>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use cases
|
|
24
|
+
|
|
25
|
+
### Selected
|
|
26
|
+
|
|
27
|
+
Active — inverse-toned fill. Toggle the `selected` flag on the same chip element.
|
|
28
|
+
|
|
29
|
+
```preview
|
|
30
|
+
chip/filter/selected
|
|
31
|
+
---
|
|
32
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
33
|
+
|
|
34
|
+
<Chip
|
|
35
|
+
variant="filter"
|
|
36
|
+
selected
|
|
37
|
+
>
|
|
38
|
+
All
|
|
39
|
+
</Chip>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### With icon
|
|
43
|
+
|
|
44
|
+
Facet glyph before the label — tag for category, magnifier for search, check on selection.
|
|
45
|
+
|
|
46
|
+
```preview
|
|
47
|
+
chip/filter/leading-icon
|
|
48
|
+
---
|
|
49
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
50
|
+
import { CheckedIcon } from '@teamblind-chorus/ui/icons';
|
|
51
|
+
|
|
52
|
+
<Chip
|
|
53
|
+
variant="filter"
|
|
54
|
+
leadingIcon={<CheckedIcon />}
|
|
55
|
+
>
|
|
56
|
+
Selected
|
|
57
|
+
</Chip>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### With trailing icon
|
|
61
|
+
|
|
62
|
+
Directional/dismiss glyph after the label — chevron-down to expand, *×* to clear.
|
|
63
|
+
|
|
64
|
+
```preview
|
|
65
|
+
chip/filter/trailing-icon
|
|
66
|
+
---
|
|
67
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
68
|
+
import { XIcon } from '@teamblind-chorus/ui/icons';
|
|
69
|
+
|
|
70
|
+
<Chip
|
|
71
|
+
variant="filter"
|
|
72
|
+
trailingIcon={<XIcon />}
|
|
73
|
+
>
|
|
74
|
+
Today
|
|
75
|
+
</Chip>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### With trailing action
|
|
79
|
+
|
|
80
|
+
Pair the chip rail with a trailing accent [Text Button](../button/text.md) (`size='small'`, `appearance='accent'`) for a destination outside the filter axis — managing the set, opening keyword settings. The button is *not* a filter toggle; the chip track scrolls horizontally with a trailing 48px `mask-image` fade painted only while overflowing, and the button stays pinned outside the scroll viewport at `sys.layout.inline.xl` gap.
|
|
81
|
+
|
|
82
|
+
```preview
|
|
83
|
+
chip/filter/with-trailing-action
|
|
84
|
+
---
|
|
85
|
+
import { Chip, Button } from '@teamblind-chorus/ui';
|
|
86
|
+
import { ArrowDownIcon } from '@teamblind-chorus/ui/icons';
|
|
87
|
+
|
|
88
|
+
<div
|
|
89
|
+
style={{
|
|
90
|
+
display: 'flex',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
gap: 'var(--sys-layout-inline-xl)',
|
|
93
|
+
width: '100%',
|
|
94
|
+
boxSizing: 'border-box',
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
style={{
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
gap: 'var(--sys-layout-inline-sm)',
|
|
102
|
+
flex: '1 1 auto',
|
|
103
|
+
minWidth: 0,
|
|
104
|
+
overflowX: 'auto',
|
|
105
|
+
scrollbarWidth: 'none',
|
|
106
|
+
WebkitMaskImage: 'linear-gradient(to right, black 0, black calc(100% - 48px), transparent 100%)',
|
|
107
|
+
maskImage: 'linear-gradient(to right, black 0, black calc(100% - 48px), transparent 100%)',
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<Chip variant="filter" selected trailingIcon={<ArrowDownIcon />}>
|
|
111
|
+
All keywords
|
|
112
|
+
</Chip>
|
|
113
|
+
<Chip variant="filter" selected trailingIcon={<ArrowDownIcon />}>
|
|
114
|
+
All channels
|
|
115
|
+
</Chip>
|
|
116
|
+
<Chip variant="filter">
|
|
117
|
+
Label
|
|
118
|
+
</Chip>
|
|
119
|
+
<Chip variant="filter">
|
|
120
|
+
Saved
|
|
121
|
+
</Chip>
|
|
122
|
+
</div>
|
|
123
|
+
<Button variant="text" size="small" appearance="accent" style={{ flex: '0 0 auto' }}>
|
|
124
|
+
Manage
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Group
|
|
130
|
+
|
|
131
|
+
Adjacent filter chips share a 4px gap (`sys.layout.inline.sm`), left-to-right; selection is independent per chip — Filter does not enforce single-select.
|
|
132
|
+
|
|
133
|
+
```preview
|
|
134
|
+
chip/filter/group
|
|
135
|
+
---
|
|
136
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
137
|
+
|
|
138
|
+
<div style={{ display: 'flex', gap: 4 }}>
|
|
139
|
+
<Chip variant="filter" selected>
|
|
140
|
+
All
|
|
141
|
+
</Chip>
|
|
142
|
+
<Chip variant="filter">
|
|
143
|
+
Open
|
|
144
|
+
</Chip>
|
|
145
|
+
<Chip variant="filter">
|
|
146
|
+
Closed
|
|
147
|
+
</Chip>
|
|
148
|
+
<Chip variant="filter">
|
|
149
|
+
Archived
|
|
150
|
+
</Chip>
|
|
151
|
+
</div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Focus indicator
|
|
155
|
+
|
|
156
|
+
Both selection states take the same standard ring; the case below shows unselected.
|
|
157
|
+
|
|
158
|
+
```preview
|
|
159
|
+
chip/filter/focused
|
|
160
|
+
---
|
|
161
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
162
|
+
|
|
163
|
+
<Chip variant="filter" state="focused">
|
|
164
|
+
All
|
|
165
|
+
</Chip>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Slots
|
|
169
|
+
|
|
170
|
+
- **label** — accessible name. Required, single line.
|
|
171
|
+
- **leadingIcon** (optional) — facet glyph before the label.
|
|
172
|
+
- **trailingIcon** (optional) — directional/dismiss glyph after the label.
|
|
173
|
+
|
|
174
|
+
## Sizes
|
|
175
|
+
|
|
176
|
+
Single fixed footprint, consistent across breakpoints.
|
|
177
|
+
|
|
178
|
+
| Property | Value | Token |
|
|
179
|
+
|-----------------------------------|----------------------|-------------------------------------|
|
|
180
|
+
| Min-height | 32px | `ref.space.400` ‡ |
|
|
181
|
+
| Padding (block × inline) | 4 × 12 | `sys.layout.container.2xs` × `sys.layout.container.sm` |
|
|
182
|
+
| Label inset (within label slot) | 4px (horizontal) | `sys.layout.container.2xs` |
|
|
183
|
+
| Slot gap (icon ↔ label) | 0 | — † |
|
|
184
|
+
| Radius | pill | `sys.radius.full` |
|
|
185
|
+
| Label | 12 / Semibold | `sys.typo.label.sm` |
|
|
186
|
+
| Icon | 16px | `sys.icon.md` |
|
|
187
|
+
|
|
188
|
+
‡ Footprint shared with [Toolbar button](../button/toolbar.md) and [Tabs segmented](../tabs/segmented.md).
|
|
189
|
+
|
|
190
|
+
† Visible icon-to-label rhythm comes from the label-slot inset.
|
|
191
|
+
|
|
192
|
+
## Variants
|
|
193
|
+
|
|
194
|
+
Single visual variant; the selected/unselected toggle swaps the container/label pair wholesale. On `selected`, the border colour goes transparent but its 1px width is held, so footprint never changes.
|
|
195
|
+
|
|
196
|
+
| State | Background | Border (1px `sys.borderWidth.hairline`) | Label / icon color |
|
|
197
|
+
|--------------|-------------------------------------|---------------------------------------------------------|-----------------------------------|
|
|
198
|
+
| unselected | `transparent` | `sys.color.outlineVariant` | `sys.color.onSurface` |
|
|
199
|
+
| selected | `sys.color.inverseSurface` | `transparent` | `sys.color.inverseOnSurface` |
|
|
200
|
+
|
|
201
|
+
## States
|
|
202
|
+
|
|
203
|
+
| State | Overlay opacity | Additional |
|
|
204
|
+
|------------|----------------------------|-----------------------------------------------------------------------------|
|
|
205
|
+
| `default` | — | Container + label at rest. |
|
|
206
|
+
| `hovered` | `sys.state.hover` (8%) | Pointer-driven via `:hover`. |
|
|
207
|
+
| `pressed` | `sys.state.pressed` (16%) | Pointer-driven via `:active`. |
|
|
208
|
+
| `disabled` | overlay suppressed | Container at `sys.state.disabled` (40%) opacity, focus ring suppressed, `cursor: not-allowed`. |
|
|
209
|
+
|
|
210
|
+
## Focus indicator
|
|
211
|
+
|
|
212
|
+
Standard outward ring on a `position: absolute` pseudo-element so it never affects layout; inside a [Tabs](../tabs/segmented.md) row the same layer is re-anchored inward. Trigger: `:focus-visible`. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../spec.schema.json",
|
|
3
|
+
"name": "Chip",
|
|
4
|
+
"family": "chip",
|
|
5
|
+
"subcomponent": "filter",
|
|
6
|
+
"description": "Selectable chip. Capsule-shaped toggle. Unselected = transparent fill + hairline outline so the chip adopts whatever surface it sits on; selected = inverse fill.",
|
|
7
|
+
"element": "button",
|
|
8
|
+
"props": {
|
|
9
|
+
"variant": {
|
|
10
|
+
"type": "literal",
|
|
11
|
+
"value": "filter"
|
|
12
|
+
},
|
|
13
|
+
"selected": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"default": false
|
|
16
|
+
},
|
|
17
|
+
"leadingIcon": {
|
|
18
|
+
"type": "node",
|
|
19
|
+
"optional": true
|
|
20
|
+
},
|
|
21
|
+
"trailingIcon": {
|
|
22
|
+
"type": "node",
|
|
23
|
+
"optional": true
|
|
24
|
+
},
|
|
25
|
+
"disabled": {
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": false
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"slots": {
|
|
31
|
+
"label": {
|
|
32
|
+
"required": true,
|
|
33
|
+
"description": "Required, single line.",
|
|
34
|
+
"accepts": [
|
|
35
|
+
"text"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"leadingIcon": {
|
|
39
|
+
"required": false,
|
|
40
|
+
"description": "Facet glyph before label.",
|
|
41
|
+
"accepts": [
|
|
42
|
+
"icon"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"trailingIcon": {
|
|
46
|
+
"required": false,
|
|
47
|
+
"description": "Directional / dismiss glyph after label.",
|
|
48
|
+
"accepts": [
|
|
49
|
+
"icon"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"sizing": {
|
|
54
|
+
"minHeight": "ref.space.400",
|
|
55
|
+
"paddingBlock": "sys.layout.container.2xs",
|
|
56
|
+
"paddingInline": "sys.layout.container.sm",
|
|
57
|
+
"labelInset": "sys.layout.container.2xs",
|
|
58
|
+
"slotGap": "0",
|
|
59
|
+
"radius": "sys.radius.full",
|
|
60
|
+
"labelTypo": "sys.typo.label.sm",
|
|
61
|
+
"iconSize": "sys.icon.md"
|
|
62
|
+
},
|
|
63
|
+
"selectionStates": {
|
|
64
|
+
"unselected": {
|
|
65
|
+
"background": "transparent",
|
|
66
|
+
"label": "sys.color.onSurface",
|
|
67
|
+
"border": {
|
|
68
|
+
"width": "sys.borderWidth.hairline",
|
|
69
|
+
"color": "sys.color.outlineVariant"
|
|
70
|
+
},
|
|
71
|
+
"note": "Transparent fill so the chip adopts whatever surface sits behind it (page, raised card, sheet) without pinning to a fixed neutral step."
|
|
72
|
+
},
|
|
73
|
+
"selected": {
|
|
74
|
+
"background": "sys.color.inverseSurface",
|
|
75
|
+
"label": "sys.color.inverseOnSurface",
|
|
76
|
+
"border": null
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"states": {
|
|
80
|
+
"default": {
|
|
81
|
+
"overlay": null
|
|
82
|
+
},
|
|
83
|
+
"hovered": {
|
|
84
|
+
"overlay": {
|
|
85
|
+
"color": "label",
|
|
86
|
+
"opacity": "sys.state.hover"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"pressed": {
|
|
90
|
+
"overlay": {
|
|
91
|
+
"color": "label",
|
|
92
|
+
"opacity": "sys.state.pressed"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"disabled": {
|
|
96
|
+
"overlay": null,
|
|
97
|
+
"containerOpacity": "sys.state.disabled",
|
|
98
|
+
"suppressFocusRing": true,
|
|
99
|
+
"cursor": "not-allowed"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"focusIndicator": {
|
|
103
|
+
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
|
|
104
|
+
"composition": "outward",
|
|
105
|
+
"compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
|
|
106
|
+
"overlay": {
|
|
107
|
+
"color": "label",
|
|
108
|
+
"opacity": "sys.state.focus"
|
|
109
|
+
},
|
|
110
|
+
"ring": {
|
|
111
|
+
"outerWidth": "sys.borderWidth.thin",
|
|
112
|
+
"outerColor": "sys.color.focus",
|
|
113
|
+
"insetWidth": "sys.borderWidth.hairline",
|
|
114
|
+
"insetColor": "sys.color.focusInset"
|
|
115
|
+
},
|
|
116
|
+
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
117
|
+
},
|
|
118
|
+
"forbidden": [
|
|
119
|
+
"radius below sys.radius.full — filter chip is always a fully-rounded pill",
|
|
120
|
+
"filled gray rest state with no `selected` semantics — rest carries hairline outline + onSurface label; selected carries inverseSurface fill + inverseOnSurface label",
|
|
121
|
+
"border declared via `border:` instead of inset box-shadow stroke",
|
|
122
|
+
"trailing chevron / icon as a separate hit area — the entire chip is the click target"
|
|
123
|
+
]
|
|
124
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Tag
|
|
2
|
+
|
|
3
|
+
The informational chip — square-cornered label naming attached metadata (categories, statuses, content labels). Shorter than Filter (24 vs 32 min-height) with `sys.radius.sm` corners. Two appearances: `default` paints a translucent `sys.color.scrimSubtle` overlay (~8% inverse-tone, adopts whatever surface sits behind it); `accent` paints a tonal pale-primary container with primary label.
|
|
4
|
+
|
|
5
|
+
**Reach for this when** you're naming attached metadata on rows, cards, or profiles. **Skip when** the marker signals unread / new activity on a host rather than describing it — use [Badge](../badge/badge.md) instead.
|
|
6
|
+
|
|
7
|
+
**Layout inset.** inline — Tag ships no padding outside its own pill chrome; the host row pays the surrounding gap and column padding. Inside a bounded surface (Card / Dialog / BottomSheet / Sheet), the host already owns the inset — see [`AGENTS.md` § Composition rules](../../../AGENTS.md#composition-rules).
|
|
8
|
+
|
|
9
|
+
## Default
|
|
10
|
+
|
|
11
|
+
Translucent overlay fill, label-only — the overlay tints the surface one step darker (light) or lighter (dark) rather than locking to a single container tone. Omit `appearance` or pass `appearance="default"`.
|
|
12
|
+
|
|
13
|
+
```preview
|
|
14
|
+
chip/tag/default
|
|
15
|
+
---
|
|
16
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
17
|
+
|
|
18
|
+
<Chip variant="tag">
|
|
19
|
+
Design
|
|
20
|
+
</Chip>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use cases
|
|
24
|
+
|
|
25
|
+
### Accent
|
|
26
|
+
|
|
27
|
+
Tonal pale-primary fill — `sys.color.primaryContainer` background, `sys.color.primary` label. Use when the tag should pop against the surface (Popular Tags in compose, highlighted hashtags); the default overlay is too quiet there.
|
|
28
|
+
|
|
29
|
+
```preview
|
|
30
|
+
chip/tag/accent
|
|
31
|
+
---
|
|
32
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
33
|
+
|
|
34
|
+
<Chip variant="tag" appearance="accent">
|
|
35
|
+
#sellside
|
|
36
|
+
</Chip>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Dismissable
|
|
40
|
+
|
|
41
|
+
Opt-out — same chip with a trailing *×* to remove the tag. Trailing icon inherits label color via `currentColor`.
|
|
42
|
+
|
|
43
|
+
```preview
|
|
44
|
+
chip/tag/dismissable
|
|
45
|
+
---
|
|
46
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
47
|
+
import { XIcon } from '@teamblind-chorus/ui/icons';
|
|
48
|
+
|
|
49
|
+
<Chip
|
|
50
|
+
variant="tag"
|
|
51
|
+
trailingIcon={<XIcon />}
|
|
52
|
+
>
|
|
53
|
+
Newsletter
|
|
54
|
+
</Chip>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Group
|
|
58
|
+
|
|
59
|
+
Adjacent tag chips share a 4px gap on both axes — `sys.layout.inline.sm` between siblings, `sys.layout.stack.2xs` between rows on wrap. Mixing with Filter is allowed — Tag's square + sunken tone vs Filter's pill + raised tone keeps roles legible. Tags are passive metadata, so collections exceeding the container's width **wrap** rather than scroll or truncate (set `display: flex; flex-wrap: wrap` on the container; do not use `overflow-x: auto` — horizontal scrolling belongs to tappable affordances).
|
|
60
|
+
|
|
61
|
+
```preview
|
|
62
|
+
chip/tag/group
|
|
63
|
+
---
|
|
64
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
65
|
+
|
|
66
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, maxWidth: 320 }}>
|
|
67
|
+
<Chip variant="tag">Design</Chip>
|
|
68
|
+
<Chip variant="tag">Engineering</Chip>
|
|
69
|
+
<Chip variant="tag">Research</Chip>
|
|
70
|
+
<Chip variant="tag">Product</Chip>
|
|
71
|
+
<Chip variant="tag">Marketing</Chip>
|
|
72
|
+
<Chip variant="tag">Operations</Chip>
|
|
73
|
+
<Chip variant="tag">Customer success</Chip>
|
|
74
|
+
</div>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Focus indicator
|
|
78
|
+
|
|
79
|
+
Only the dismissable tag is focusable; the case below shows that form. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
|
|
80
|
+
|
|
81
|
+
```preview
|
|
82
|
+
chip/tag/focused
|
|
83
|
+
---
|
|
84
|
+
import { Chip } from '@teamblind-chorus/ui';
|
|
85
|
+
import { XIcon } from '@teamblind-chorus/ui/icons';
|
|
86
|
+
|
|
87
|
+
<Chip variant="tag" state="focused" trailingIcon={<XIcon />}>
|
|
88
|
+
Newsletter
|
|
89
|
+
</Chip>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Slots
|
|
93
|
+
|
|
94
|
+
- **label** — accessible name. Required, single line.
|
|
95
|
+
- **trailingIcon** (optional) — dismiss/opt-out glyph after the label (typically *×*). Tag does not carry a leading icon.
|
|
96
|
+
|
|
97
|
+
## Sizes
|
|
98
|
+
|
|
99
|
+
Same min-height, vertical padding, label rung, and label-slot inset as Filter, but horizontal outer padding tightens from 12 to 8 (label-only Tag clears 12px each side; dismissable Tag reads 4px label-to-glyph then 8px to edge).
|
|
100
|
+
|
|
101
|
+
| Property | Value | Token |
|
|
102
|
+
|-----------------------------------|----------------------|-------------------------------------|
|
|
103
|
+
| Min-height | 24px | `ref.space.300` ‡ |
|
|
104
|
+
| Padding (block × inline) | 4 × 8 | `sys.layout.container.2xs` × `sys.layout.container.xs` |
|
|
105
|
+
| Label inset (within label slot) | 4px (horizontal) | `sys.layout.container.2xs` |
|
|
106
|
+
| Slot gap (icon ↔ label) | 0 | — † |
|
|
107
|
+
| Radius | 4px | `sys.radius.sm` |
|
|
108
|
+
| Label | 12 / Semibold | `sys.typo.label.sm` |
|
|
109
|
+
| Icon | 16px | `sys.icon.md` |
|
|
110
|
+
|
|
111
|
+
‡ `min-height` binds to raw `ref.space.*` — `sys.*` does not expose a 24px step.
|
|
112
|
+
|
|
113
|
+
† Slot gap is `0` — see [Filter → Sizes](./filter.md#sizes).
|
|
114
|
+
|
|
115
|
+
## Appearance
|
|
116
|
+
|
|
117
|
+
Two appearances; Tag never toggles.
|
|
118
|
+
|
|
119
|
+
| Appearance | Background | Label / icon color |
|
|
120
|
+
|------------|---------------------------------------------------------------------------------------------|------------------------------|
|
|
121
|
+
| `default` | `sys.color.scrimSubtle` (translucent inverse-tone overlay — black 8% light / white 8% dark) | `sys.color.onSurface` |
|
|
122
|
+
| `accent` | `sys.color.primaryContainer` (theme-aware) | `sys.color.primary` |
|
|
123
|
+
|
|
124
|
+
## States
|
|
125
|
+
|
|
126
|
+
Tag is interactive only when `trailingIcon` carries a click target; otherwise the chip is presentational and the chip body takes no state.
|
|
127
|
+
|
|
128
|
+
| State | Overlay opacity | Additional |
|
|
129
|
+
|------------|----------------------------|-----------------------------------------------------------------------------|
|
|
130
|
+
| `default` | — | Container + label at rest. |
|
|
131
|
+
| `hovered` | `sys.state.hover` (8%) | Pointer-driven via `:hover` when the chip is interactive. |
|
|
132
|
+
| `pressed` | `sys.state.pressed` (16%) | Pointer-driven via `:active` when the chip is interactive. |
|
|
133
|
+
| `disabled` | overlay suppressed | Container at `sys.state.disabled` (40%) opacity, focus ring suppressed, `cursor: not-allowed`. |
|
|
134
|
+
|
|
135
|
+
## Focus indicator
|
|
136
|
+
|
|
137
|
+
Standard ring on the dismissable form only. Trigger: `:focus-visible`. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../spec.schema.json",
|
|
3
|
+
"name": "Chip",
|
|
4
|
+
"family": "chip",
|
|
5
|
+
"subcomponent": "tag",
|
|
6
|
+
"description": "Informational chip. Square-cornered metadata label. Passive (or dismissable via trailing ×). No leading icon. Two appearances: `default` paints the translucent `sys.color.scrimSubtle` scrim (~8% inverse-tone overlay — black in light, white in dark) so the tag adopts whatever surface sits behind it; `accent` paints a tonal pale-primary container (`sys.color.primaryContainer`) with primary label for tags that need to pop against the surface.",
|
|
7
|
+
"element": "span",
|
|
8
|
+
"props": {
|
|
9
|
+
"variant": {
|
|
10
|
+
"type": "literal",
|
|
11
|
+
"value": "tag"
|
|
12
|
+
},
|
|
13
|
+
"trailingIcon": {
|
|
14
|
+
"type": "node",
|
|
15
|
+
"optional": true,
|
|
16
|
+
"description": "Dismiss glyph (×); when present, chip becomes interactive."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"slots": {
|
|
20
|
+
"label": {
|
|
21
|
+
"required": true,
|
|
22
|
+
"description": "Required, single line.",
|
|
23
|
+
"accepts": [
|
|
24
|
+
"text"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"trailingIcon": {
|
|
28
|
+
"required": false,
|
|
29
|
+
"description": "Dismiss / opt-out glyph after the label.",
|
|
30
|
+
"accepts": [
|
|
31
|
+
"icon"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"sizing": {
|
|
36
|
+
"minHeight": "ref.space.300",
|
|
37
|
+
"paddingBlock": "sys.layout.container.2xs",
|
|
38
|
+
"paddingInline": "sys.layout.container.xs",
|
|
39
|
+
"labelInset": "sys.layout.container.2xs",
|
|
40
|
+
"slotGap": "0",
|
|
41
|
+
"radius": "sys.radius.sm",
|
|
42
|
+
"labelTypo": "sys.typo.label.sm",
|
|
43
|
+
"iconSize": "sys.icon.md"
|
|
44
|
+
},
|
|
45
|
+
"appearances": {
|
|
46
|
+
"default": {
|
|
47
|
+
"background": "sys.color.scrimSubtle",
|
|
48
|
+
"label": "sys.color.onSurface",
|
|
49
|
+
"border": null,
|
|
50
|
+
"default": true,
|
|
51
|
+
"note": "Background is the translucent inverse-tone scrim (`sys.color.scrimSubtle` — black 8% in light mode, white 8% in dark) so the tag harmonises with whatever surface sits behind it — body, raised card, BottomSheet — instead of pinning to a fixed neutral step. Same Banner-style fill used by Progress track, StatusTag neutral, and Skeleton; sys-color is theme-aware so a single token resolves correctly in both modes."
|
|
52
|
+
},
|
|
53
|
+
"accent": {
|
|
54
|
+
"background": "sys.color.primaryContainer",
|
|
55
|
+
"label": "sys.color.primary",
|
|
56
|
+
"border": null,
|
|
57
|
+
"note": "Tonal accent: pale primary container background with primary label. Sys-color tokens are theme-aware, so no separate dark binding is needed — the resolved tokens pick the right values per theme. Use for tags that should pop against the surface (e.g. Popular Tags in compose, highlighted hashtags) where the default overlay is too quiet."
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"states": {
|
|
61
|
+
"default": {
|
|
62
|
+
"overlay": null
|
|
63
|
+
},
|
|
64
|
+
"hovered": {
|
|
65
|
+
"overlay": {
|
|
66
|
+
"color": "label",
|
|
67
|
+
"opacity": "sys.state.hover"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"pressed": {
|
|
71
|
+
"overlay": {
|
|
72
|
+
"color": "label",
|
|
73
|
+
"opacity": "sys.state.pressed"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"disabled": {
|
|
77
|
+
"overlay": null,
|
|
78
|
+
"containerOpacity": "sys.state.disabled",
|
|
79
|
+
"suppressFocusRing": true,
|
|
80
|
+
"cursor": "not-allowed"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"focusIndicator": {
|
|
84
|
+
"description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. Applies only when the chip is interactive (a dismiss trailingIcon is present). The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
|
|
85
|
+
"composition": "outward",
|
|
86
|
+
"compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
|
|
87
|
+
"overlay": {
|
|
88
|
+
"color": "label",
|
|
89
|
+
"opacity": "sys.state.focus"
|
|
90
|
+
},
|
|
91
|
+
"ring": {
|
|
92
|
+
"outerWidth": "sys.borderWidth.thin",
|
|
93
|
+
"outerColor": "sys.color.focus",
|
|
94
|
+
"insetWidth": "sys.borderWidth.hairline",
|
|
95
|
+
"insetColor": "sys.color.focusInset"
|
|
96
|
+
},
|
|
97
|
+
"trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
|
|
98
|
+
},
|
|
99
|
+
"forbidden": [
|
|
100
|
+
"radius set to sys.radius.full (pill) — tag is a sys.radius.sm square-cornered metadata pill, not a capsule chip",
|
|
101
|
+
"tag rendered as a clickable commit — tag is a metadata pill, not an action; the click target lives on the surrounding card / row",
|
|
102
|
+
"raw color override on a tag — tone is the surrounding container's secondary text color"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../family.schema.json",
|
|
3
|
+
"family": "dialog",
|
|
4
|
+
"name": "Dialog",
|
|
5
|
+
"description": "Centered modal surface used for confirmations, short forms, and image-led prompts that demand a focused commit. Single-spec family.",
|
|
6
|
+
"useCases": [
|
|
7
|
+
"confirmation",
|
|
8
|
+
"destructive confirmation",
|
|
9
|
+
"image-led prompt",
|
|
10
|
+
"centred modal",
|
|
11
|
+
"short form"
|
|
12
|
+
],
|
|
13
|
+
"visualReuse": "locked",
|
|
14
|
+
"layoutInset": "bounded-surface",
|
|
15
|
+
"wrapperGuidance": "Renders into a body portal (or owns its own surface chrome). Place the call as a sibling of the page shell \u2014 do NOT wrap in a layout container, padding div, or className=\"px-*\". Full-bleed children inside the surface body get the negative-margin opt-out \u2014 see AGENTS.md \u00a7 Composition rules.",
|
|
16
|
+
"usage": {
|
|
17
|
+
"note": "Actions are `primaryAction` / `secondaryAction` descriptor objects ({ label, onClick }) — there are NO Button children.",
|
|
18
|
+
"example": "<Dialog open={open} onClose={() => setOpen(false)} title=\"…\" body=\"…\" primaryAction={{ label, onClick }} secondaryAction={{ label, onClick }} />"
|
|
19
|
+
},
|
|
20
|
+
"spec": "dialog.md",
|
|
21
|
+
"subcomponents": [
|
|
22
|
+
{
|
|
23
|
+
"slug": "dialog",
|
|
24
|
+
"spec": "dialog.spec.json",
|
|
25
|
+
"md": "dialog.md",
|
|
26
|
+
"default": true
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|