@teamblind-chorus/ui 1.1.0 → 2.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/README.md +3 -3
- package/agents/AGENTS.md +6 -6
- package/agents/DESIGN.md +245 -244
- package/agents/LOVABLE.md +40 -11
- package/agents/catalog.md +10 -8
- package/agents/components/avatar-rail/avatar-rail.md +2 -4
- package/agents/components/avatar-rail/avatar-rail.spec.json +27 -12
- package/agents/components/badge/role.md +7 -9
- package/agents/components/badge/role.spec.json +6 -6
- package/agents/components/badge/update.md +6 -8
- package/agents/components/badge/update.spec.json +5 -5
- package/agents/components/banner/banner.family.json +3 -1
- package/agents/components/banner/banner.md +66 -15
- package/agents/components/banner/banner.spec.json +37 -14
- package/agents/components/bottom-sheet/bottom-sheet.md +4 -6
- package/agents/components/bottom-sheet/bottom-sheet.spec.json +5 -5
- package/agents/components/bubble/bubble.md +8 -10
- package/agents/components/bubble/bubble.spec.json +11 -11
- package/agents/components/button/button.md +1 -1
- package/agents/components/button/check.md +9 -11
- package/agents/components/button/check.spec.json +25 -8
- package/agents/components/button/fab.md +7 -9
- package/agents/components/button/fab.spec.json +27 -10
- package/agents/components/button/group.spec.json +4 -4
- package/agents/components/button/icon.md +21 -23
- package/agents/components/button/icon.spec.json +29 -12
- package/agents/components/button/standard.md +40 -42
- package/agents/components/button/standard.spec.json +37 -20
- package/agents/components/button/text.md +21 -23
- package/agents/components/button/text.spec.json +30 -13
- package/agents/components/button/toggle.md +7 -9
- package/agents/components/button/toggle.spec.json +27 -10
- package/agents/components/button/toolbar.md +24 -26
- package/agents/components/button/toolbar.spec.json +10 -12
- package/agents/components/carousel/carousel.md +1 -1
- package/agents/components/carousel/post.md +15 -21
- package/agents/components/carousel/post.spec.json +17 -17
- package/agents/components/carousel/profile.md +9 -45
- package/agents/components/carousel/profile.spec.json +17 -17
- package/agents/components/chip/chip.md +1 -1
- package/agents/components/chip/filter.md +22 -24
- package/agents/components/chip/filter.spec.json +34 -11
- package/agents/components/chip/tag.md +22 -24
- package/agents/components/chip/tag.spec.json +36 -13
- package/agents/components/dialog/dialog.md +1 -3
- package/agents/components/dialog/dialog.spec.json +3 -3
- package/agents/components/directory-list/directory-list.md +1 -3
- package/agents/components/directory-list/directory-list.spec.json +2 -2
- package/agents/components/divider/divider.family.json +1 -1
- package/agents/components/divider/divider.md +12 -14
- package/agents/components/divider/divider.spec.json +8 -8
- package/agents/components/empty-state/empty-state.family.json +28 -0
- package/agents/components/empty-state/empty-state.md +69 -0
- package/agents/components/empty-state/empty-state.spec.json +87 -0
- package/agents/components/feed/ad.md +2 -4
- package/agents/components/feed/ad.spec.json +10 -10
- package/agents/components/feed/post.md +41 -43
- package/agents/components/feed/post.spec.json +35 -39
- package/agents/components/form-field/form-field.md +1 -1
- package/agents/components/form-field/input.md +32 -34
- package/agents/components/form-field/input.spec.json +39 -31
- package/agents/components/form-field/search.md +2 -4
- package/agents/components/form-field/search.spec.json +24 -16
- package/agents/components/form-field/select.md +18 -20
- package/agents/components/form-field/select.spec.json +36 -27
- package/agents/components/form-field/textarea.md +3 -5
- package/agents/components/form-field/textarea.spec.json +37 -29
- package/agents/components/header/main.md +4 -6
- package/agents/components/header/main.spec.json +3 -3
- package/agents/components/header/sub.md +6 -8
- package/agents/components/header/sub.spec.json +3 -3
- package/agents/components/list/accordion.md +34 -45
- package/agents/components/list/accordion.spec.json +26 -17
- package/agents/components/list/entry.md +59 -81
- package/agents/components/list/entry.spec.json +37 -21
- package/agents/components/list/list.md +2 -2
- package/agents/components/list/radio.md +13 -20
- package/agents/components/list/radio.spec.json +33 -18
- package/agents/components/list/standard.md +88 -64
- package/agents/components/list/standard.spec.json +52 -20
- package/agents/components/metadata/compact.md +4 -6
- package/agents/components/metadata/compact.spec.json +6 -6
- package/agents/components/metadata/metadata.md +1 -1
- package/agents/components/metadata/standard.md +12 -14
- package/agents/components/metadata/standard.spec.json +10 -10
- package/agents/components/nav-card/nav-card.md +25 -27
- package/agents/components/nav-card/nav-card.spec.json +25 -16
- package/agents/components/nav-list/nav-list.md +2 -8
- package/agents/components/nav-list/nav-list.spec.json +3 -3
- package/agents/components/navigation-bar/main.md +9 -11
- package/agents/components/navigation-bar/main.spec.json +6 -6
- package/agents/components/navigation-bar/search.md +6 -8
- package/agents/components/navigation-bar/search.spec.json +9 -9
- package/agents/components/navigation-bar/sub.md +9 -11
- package/agents/components/navigation-bar/sub.spec.json +7 -7
- package/agents/components/page-shell/page-shell.family.json +1 -1
- package/agents/components/page-shell/page-shell.md +33 -0
- package/agents/components/page-shell/page-shell.spec.json +85 -0
- package/agents/components/pagination/pagination.family.json +1 -1
- package/agents/components/pagination/pagination.md +3 -3
- package/agents/components/pagination/pagination.spec.json +5 -5
- package/agents/components/profile-header/profile-header.md +9 -11
- package/agents/components/profile-header/profile-header.spec.json +9 -9
- package/agents/components/progress/progress.family.json +1 -1
- package/agents/components/progress/progress.md +5 -5
- package/agents/components/progress/progress.spec.json +8 -8
- package/agents/components/side-sheet/side-sheet.md +11 -13
- package/agents/components/side-sheet/side-sheet.spec.json +3 -3
- package/agents/components/skeleton/skeleton.md +7 -9
- package/agents/components/skeleton/skeleton.spec.json +5 -5
- package/agents/components/spinner/spinner.family.json +27 -0
- package/agents/components/spinner/spinner.md +96 -0
- package/agents/components/spinner/spinner.spec.json +82 -0
- package/agents/components/status-tag/status-tag.md +7 -9
- package/agents/components/status-tag/status-tag.spec.json +5 -5
- package/agents/components/suggestion-list/suggestion-list.md +3 -7
- package/agents/components/suggestion-list/suggestion-list.spec.json +8 -12
- package/agents/components/switch/switch.md +12 -14
- package/agents/components/switch/switch.spec.json +23 -15
- package/agents/components/tab-bar/tab-bar.md +9 -11
- package/agents/components/tab-bar/tab-bar.spec.json +37 -23
- package/agents/components/tabs/rounded.md +6 -8
- package/agents/components/tabs/rounded.spec.json +34 -13
- package/agents/components/tabs/segmented.md +4 -6
- package/agents/components/tabs/segmented.spec.json +4 -8
- package/agents/components/tabs/underline.md +9 -11
- package/agents/components/tabs/underline.spec.json +31 -14
- package/agents/components/thumbnail/thumbnail.md +5 -7
- package/agents/components/thumbnail/thumbnail.spec.json +8 -8
- package/agents/components/toast/toast.md +5 -7
- package/agents/components/toast/toast.spec.json +3 -3
- package/agents/components/tooltip/tooltip.md +6 -8
- package/agents/components/tooltip/tooltip.spec.json +4 -4
- package/agents/manifest.json +8 -6
- package/agents/tokens.usage.json +71 -226
- package/agents/usage.json +12 -0
- package/dist/index.cjs +531 -262
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -13
- package/dist/index.d.ts +57 -13
- package/dist/index.js +530 -263
- package/dist/index.js.map +1 -1
- package/dist/styles.css +560 -379
- package/eslint/rules.js +7 -7
- package/package.json +2 -3
- package/agents/anti-patterns.md +0 -533
- package/agents/compose.md +0 -240
- package/agents/images.md +0 -66
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"followers": {
|
|
38
38
|
"type": "string",
|
|
39
39
|
"required": true,
|
|
40
|
-
"description": "Follower count line — formatted by the consumer so the unit / locale stays in their hands (e.g. '999 followers', '12.4K followers', '999 팔로워'). Paints in `sys.typo.body.sm` / `sys.color.
|
|
40
|
+
"description": "Follower count line — formatted by the consumer so the unit / locale stays in their hands (e.g. '999 followers', '12.4K followers', '999 팔로워'). Paints in `sys.typo.body.sm` / `sys.color.text.subtle` to the right of a bullet separator on the meta row."
|
|
41
41
|
},
|
|
42
42
|
"followed": {
|
|
43
43
|
"type": "boolean",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"slots": {
|
|
94
94
|
"container": {
|
|
95
95
|
"required": true,
|
|
96
|
-
"description": "Outer `<section>`. Vertical stack — cover band first, identity row second. `sys.color.surface` fill; no outer padding (the cover is full-bleed; the identity row pays its own inline padding).",
|
|
96
|
+
"description": "Outer `<section>`. Vertical stack — cover band first, identity row second. `sys.color.surface.default` fill; no outer padding (the cover is full-bleed; the identity row pays its own inline padding).",
|
|
97
97
|
"intrinsic": true
|
|
98
98
|
},
|
|
99
99
|
"cover": {
|
|
@@ -137,12 +137,12 @@
|
|
|
137
137
|
},
|
|
138
138
|
"name": {
|
|
139
139
|
"required": true,
|
|
140
|
-
"description": "Entity name. Renders as `<h1>` at `sys.typo.heading.lg` (24 / Semibold) / `sys.color.
|
|
140
|
+
"description": "Entity name. Renders as `<h1>` at `sys.typo.heading.lg` (24 / Semibold) / `sys.color.text.default`. Single line; truncates with ellipsis. Sits inside the `heading` sub-group.",
|
|
141
141
|
"accepts": ["text"]
|
|
142
142
|
},
|
|
143
143
|
"meta": {
|
|
144
144
|
"required": true,
|
|
145
|
-
"description": "Visibility + follower meta row. Composed as: [visibility icon] [visibility label] [bullet separator] [follower count]. `sys.typo.body.sm` (14 / Regular) / `sys.color.
|
|
145
|
+
"description": "Visibility + follower meta row. Composed as: [visibility icon] [visibility label] [bullet separator] [follower count]. `sys.typo.body.sm` (14 / Regular) / `sys.color.text.subtle`. Single line. Sits inside the `heading` sub-group, 4px below the name.",
|
|
146
146
|
"intrinsic": true
|
|
147
147
|
},
|
|
148
148
|
"followAction": {
|
|
@@ -153,9 +153,9 @@
|
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
155
|
"sizing": {
|
|
156
|
-
"containerFill": "sys.color.surface",
|
|
156
|
+
"containerFill": "sys.color.surface.default",
|
|
157
157
|
"coverAspectRatio": "375 / 120 (W × H — the mobile-viewport / cover-band proportion; the band scales with the host column instead of locking to a hard pixel height)",
|
|
158
|
-
"coverFill": "sys.color.
|
|
158
|
+
"coverFill": "sys.color.surface.default (background underlay — the placeholder image paints on top via `object-fit: cover`)",
|
|
159
159
|
"coverImageSource": "Same `/placeholder.png` asset every Chorus image-area slot falls back to. Decorative — `aria-hidden`. Consumers override via the `cover.src` prop.",
|
|
160
160
|
"avatarSize": 56,
|
|
161
161
|
"avatarOverlap": "Avatar's vertical center sits on the cover band's bottom edge — the top half overlaps the cover, the bottom half sits on the identity surface (-28px margin-top). The 2-token `surface`-tone halo that separates the circle from the cover is owned by Thumbnail's `outlined={true}` case — see [Thumbnail § With surface outline](../thumbnail/thumbnail.md#with-surface-outline). The header does not paint a halo of its own on the wrapper; the Thumbnail's outset `box-shadow` is the contract.",
|
|
@@ -170,11 +170,11 @@
|
|
|
170
170
|
"followMarginTop": "sys.layout.stack.md",
|
|
171
171
|
"followMarginTopNote": "Follow Toggle Button sits 16px below the cover bottom edge so the affordance reads with its own breathing room rather than aligning to the avatar's lower edge.",
|
|
172
172
|
"nameTypo": "sys.typo.heading.lg",
|
|
173
|
-
"nameColor": "sys.color.
|
|
173
|
+
"nameColor": "sys.color.text.default",
|
|
174
174
|
"metaTypo": "sys.typo.body.sm",
|
|
175
|
-
"metaColor": "sys.color.
|
|
175
|
+
"metaColor": "sys.color.text.subtle",
|
|
176
176
|
"metaIconSize": "sys.icon.md",
|
|
177
|
-
"metaIconColor": "sys.color.
|
|
177
|
+
"metaIconColor": "sys.color.text.subtle",
|
|
178
178
|
"metaGap": "sys.layout.inline.sm",
|
|
179
179
|
"metaSeparator": "·",
|
|
180
180
|
"followActionRendersAs": "Button variant='toggle' — Toolbar-Button footprint. State tokens delegate entirely to the Toggle Button (Chip-toggle) contract."
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../family.schema.json",
|
|
3
3
|
"family": "progress",
|
|
4
4
|
"name": "Progress",
|
|
5
|
-
"description": "Linear progress bar — a slim horizontal track that previews how far a long-running task has advanced. Determinate only: a filled segment parks at the value's ratio. One appearance, no emphasis axis: the track paints `sys.color.
|
|
5
|
+
"description": "Linear progress bar — a slim horizontal track that previews how far a long-running task has advanced. Determinate only: a filled segment parks at the value's ratio. One appearance, no emphasis axis: the track paints `sys.color.background.neutral` (a faint inverse-tone scrim that reads on any host surface tier) and the indicator paints `sys.color.background.inverse` so the filled segment always contrasts against the bare track in either theme. A single 8px fully-rounded rung. If a screen needs a higher-attention progress mark, the emphasis belongs in the surrounding copy (e.g. a Banner), never in a chromatic indicator tone. Single-spec family.",
|
|
6
6
|
"useCases": [
|
|
7
7
|
"upload / download progress",
|
|
8
8
|
"onboarding step progress",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 🇰🇷 한국어: [`i18n/ko/schema/components/progress/progress.md`](../../../i18n/ko/schema/components/progress/progress.md)
|
|
4
4
|
|
|
5
|
-
A single visual rung — 8px tall, fully rounded — that previews how far a long-running task has advanced. Determinate only: a filled indicator parks at the value's ratio. No emphasis axis: track paints with `sys.color.
|
|
5
|
+
A single visual rung — 8px tall, fully rounded — that previews how far a long-running task has advanced. Determinate only: a filled indicator parks at the value's ratio. No emphasis axis: track paints with `sys.color.background.neutral` (the translucent inverse-tone scrim — ~8% black light, ~8% white dark); indicator paints in `inverseSurface` so the filled segment contrasts against the track regardless of theme.
|
|
6
6
|
|
|
7
7
|
**Reach for this when** a screen holds a task long enough that the user would otherwise wonder if anything is happening — file uploads, onboarding step counters, background syncs, account migrations. **Skip when** the task resolves under 300ms, the wait is purely opaque (use [Skeleton](../skeleton/skeleton.md) for content placeholders, busy spinners for short opaque waits), or the metric is primary content rather than chrome (use a chart).
|
|
8
8
|
|
|
@@ -22,15 +22,15 @@ import { Progress } from '@teamblind-chorus/ui';
|
|
|
22
22
|
|
|
23
23
|
## Slots
|
|
24
24
|
|
|
25
|
-
- **track** — fully-rounded background block. 8px tall, `sys.color.
|
|
26
|
-
- **indicator** *(decorative)* — inner filled segment painted in `sys.color.
|
|
25
|
+
- **track** — fully-rounded background block. 8px tall, `sys.color.background.neutral` fill (translucent inverse-tone scrim — black 8% light, white 8% dark), no stroke. Carries `role="progressbar"` and the aria-value attributes.
|
|
26
|
+
- **indicator** *(decorative)* — inner filled segment painted in `sys.color.background.inverse`, `translateX`'d so the trailing edge lands at the value's ratio.
|
|
27
27
|
|
|
28
28
|
## Anatomy
|
|
29
29
|
|
|
30
30
|
| Slot | Token bindings |
|
|
31
31
|
|--------------|----------------|
|
|
32
|
-
| track | `sys.color.
|
|
33
|
-
| indicator | `sys.color.
|
|
32
|
+
| track | `sys.color.background.neutral` fill (translucent inverse-tone scrim), `sys.radius.full`, 8px (`sys.layout.container.xs`) tall |
|
|
33
|
+
| indicator | `sys.color.background.inverse` fill, fully rounded, `transform: translateX(…)` driven |
|
|
34
34
|
| transition | 200ms `ease-out` on indicator transform as `value` changes |
|
|
35
35
|
|
|
36
36
|
## Behavior
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../spec.schema.json",
|
|
3
3
|
"name": "Progress",
|
|
4
4
|
"family": "progress",
|
|
5
|
-
"description": "Linear progress bar. A single visual rung — 8px tall, fully rounded — that previews how far a long-running task has advanced. The track paints with `sys.color.
|
|
5
|
+
"description": "Linear progress bar. A single visual rung — 8px tall, fully rounded — that previews how far a long-running task has advanced. The track paints with `sys.color.background.neutral` (the Banner-style inverse-tone scrim — ~8% black in light, ~8% white in dark) so the bar reads cleanly on any host surface tier without colliding with a fixed surface-container step. The indicator paints in `sys.color.background.inverse` so the filled segment always contrasts against the scrim track regardless of theme. The track owns no width of its own — it stretches to fill its host column. Determinate only — `role='progressbar'` with `aria-valuemin / max / now` reflects the value's ratio.",
|
|
6
6
|
"element": "div",
|
|
7
7
|
"props": {
|
|
8
8
|
"value": {
|
|
@@ -24,29 +24,29 @@
|
|
|
24
24
|
"slots": {
|
|
25
25
|
"track": {
|
|
26
26
|
"required": true,
|
|
27
|
-
"description": "Fully-rounded background block. 8px tall, `sys.color.
|
|
27
|
+
"description": "Fully-rounded background block. 8px tall, `sys.color.background.neutral` fill (translucent inverse-tone scrim — black 8% light / white 8% dark), no stroke. Carries `role='progressbar'` and the aria-value attributes.",
|
|
28
28
|
"intrinsic": true
|
|
29
29
|
},
|
|
30
30
|
"indicator": {
|
|
31
31
|
"required": true,
|
|
32
|
-
"description": "Inner filled segment painted in `sys.color.
|
|
32
|
+
"description": "Inner filled segment painted in `sys.color.background.inverse`, translated horizontally so its trailing edge sits at the value's ratio.",
|
|
33
33
|
"intrinsic": true
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"sizing": {
|
|
37
37
|
"height": "sys.layout.container.xs",
|
|
38
|
-
"trackBackground": "sys.color.
|
|
38
|
+
"trackBackground": "sys.color.background.neutral",
|
|
39
39
|
"trackRadius": "sys.radius.full",
|
|
40
|
-
"indicatorBackground": "sys.color.
|
|
40
|
+
"indicatorBackground": "sys.color.background.inverse",
|
|
41
41
|
"indicatorRadius": "inherit",
|
|
42
42
|
"transitionDuration": "200ms",
|
|
43
43
|
"transitionTiming": "ease-out"
|
|
44
44
|
},
|
|
45
45
|
"appearances": {
|
|
46
46
|
"default": {
|
|
47
|
-
"track": "sys.color.
|
|
48
|
-
"indicator": "sys.color.
|
|
49
|
-
"note": "The only appearance. Track paints `sys.color.
|
|
47
|
+
"track": "sys.color.background.neutral",
|
|
48
|
+
"indicator": "sys.color.background.inverse",
|
|
49
|
+
"note": "The only appearance. Track paints `sys.color.background.neutral` — a faint inverse-tone scrim (~8% black light / ~8% white dark) visible against every surface tier in either theme. Indicator paints in `inverseSurface` so the filled segment always contrasts against the bare track regardless of theme. Progress has no emphasis axis; if a screen needs a higher-attention progress mark, it belongs as a Banner copy nearby, not as a chromatic indicator tone."
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"states": {
|
|
@@ -59,21 +59,21 @@ import { StarFillIcon } from '@teamblind-chorus/ui/icons';
|
|
|
59
59
|
label: 'Sourdough Bakers',
|
|
60
60
|
count: <Badge size="small" count={12} />,
|
|
61
61
|
thumbnail: { alt: 'Sourdough Bakers' },
|
|
62
|
-
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorited" aria-pressed="true" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-yellow)' }} onClick={() => {}} />,
|
|
62
|
+
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorited" aria-pressed="true" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-accent-yellow-default)' }} onClick={() => {}} />,
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
value: 'stocks',
|
|
66
66
|
label: 'Stocks & Investing',
|
|
67
67
|
count: <Badge size="small" count={142} />,
|
|
68
68
|
thumbnail: { alt: 'Stocks & Investing' },
|
|
69
|
-
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorited" aria-pressed="true" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-yellow)' }} onClick={() => {}} />,
|
|
69
|
+
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorited" aria-pressed="true" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-accent-yellow-default)' }} onClick={() => {}} />,
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
value: 'movie-talk',
|
|
73
73
|
label: 'Movie Talk',
|
|
74
74
|
count: <Badge size="small" count={24} />,
|
|
75
75
|
thumbnail: { alt: 'Movie Talk' },
|
|
76
|
-
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorited" aria-pressed="true" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-yellow)' }} onClick={() => {}} />,
|
|
76
|
+
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorited" aria-pressed="true" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-accent-yellow-default)' }} onClick={() => {}} />,
|
|
77
77
|
},
|
|
78
78
|
]}
|
|
79
79
|
/>
|
|
@@ -90,20 +90,20 @@ import { StarFillIcon } from '@teamblind-chorus/ui/icons';
|
|
|
90
90
|
label: 'Career & Jobs',
|
|
91
91
|
count: <Badge size="small" count={24} />,
|
|
92
92
|
thumbnail: { alt: 'Career & Jobs' },
|
|
93
|
-
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorite" aria-pressed="false" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-
|
|
93
|
+
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorite" aria-pressed="false" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-subtle)' }} onClick={() => {}} />,
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
value: 'marketplace',
|
|
97
97
|
label: 'Marketplace',
|
|
98
98
|
count: <Badge size="small" count={12} />,
|
|
99
99
|
thumbnail: { alt: 'Marketplace' },
|
|
100
|
-
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorite" aria-pressed="false" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-
|
|
100
|
+
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorite" aria-pressed="false" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-subtle)' }} onClick={() => {}} />,
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
value: 'fashion',
|
|
104
104
|
label: 'Fashion & Beauty',
|
|
105
105
|
thumbnail: { alt: 'Fashion & Beauty' },
|
|
106
|
-
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorite" aria-pressed="false" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-
|
|
106
|
+
trailingIcon: <Button variant="icon" size="medium" aria-label="Favorite" aria-pressed="false" icon={<StarFillIcon />} style={{ color: 'var(--sys-color-icon-subtle)' }} onClick={() => {}} />,
|
|
107
107
|
},
|
|
108
108
|
]}
|
|
109
109
|
/>
|
|
@@ -111,13 +111,11 @@ import { StarFillIcon } from '@teamblind-chorus/ui/icons';
|
|
|
111
111
|
</SideSheet>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
##
|
|
115
|
-
|
|
116
|
-
### Single section
|
|
114
|
+
## Single section
|
|
117
115
|
|
|
118
116
|
One `SideSheetGroup` with a Header + List(entry) directory. Use for filter rails, settings groups, sub-navigation overlays.
|
|
119
117
|
|
|
120
|
-
|
|
118
|
+
## Pinned commit
|
|
121
119
|
|
|
122
120
|
Set the `footer` prop with a Text Button to pin a primary commit at the bottom (e.g. "Browse all channels", "Apply filters"). Footer stays flush while the body scrolls.
|
|
123
121
|
|
|
@@ -127,7 +125,7 @@ Set the `footer` prop with a Text Button to pin a primary commit at the bottom (
|
|
|
127
125
|
- **card** — off-canvas column. Fixed width, full viewport height, flush against the `anchor` edge. `sys.color.surface` fill + `sys.elevation.sheet` shadow.
|
|
128
126
|
- **body** — vertical scroll surface inside the card. **Full-bleed** — no inline gutter; each child pays its own `container.md` (16) rail. Block padding is `0` at the top (each group's leading Header supplies the 24 top inset itself) and `24px` at the bottom; the body adds no inter-child gap — the Header-driven block rhythm is the single source of truth.
|
|
129
127
|
- **group** *(SideSheetGroup)* — bundle of one Header + a directory primitive (canonical: an embedded [list/entry](../list/entry.md) stack) inside the body. The full-bleed Header drives the rhythm: its `24px` (`layout.stack.lg`) block-start is both the gap **between groups** and the body's top inset for the first group; its `16px` (`layout.stack.md`) block-end is the header↔directory gap. The group adds no sibling margin of its own. Because the body is full-bleed, the `<List variant="entry">` aligns at the same 16 rail as the Header with no opt-out.
|
|
130
|
-
- **footer** *(optional)* — pinned bottom action rail. Single Text Button or compact action node; separated by an `
|
|
128
|
+
- **footer** *(optional)* — pinned bottom action rail. Single Text Button or compact action node; separated by an `border.default` hairline.
|
|
131
129
|
|
|
132
130
|
## Trailing favorite-star contract
|
|
133
131
|
|
|
@@ -135,8 +133,8 @@ When a channel / directory row carries a favorite toggle on its trailing edge, u
|
|
|
135
133
|
|
|
136
134
|
| State | Icon | Colour | aria-pressed |
|
|
137
135
|
|----------|-----------------|---------------------------------|--------------|
|
|
138
|
-
| Active | `StarFillIcon` | `var(--sys-color-icon-yellow)` | `true` |
|
|
139
|
-
| Inactive | `StarFillIcon` | `var(--sys-color-icon-
|
|
136
|
+
| Active | `StarFillIcon` | `var(--sys-color-icon-accent-yellow-default)` | `true` |
|
|
137
|
+
| Inactive | `StarFillIcon` | `var(--sys-color-icon-subtle)` | `false` |
|
|
140
138
|
|
|
141
139
|
The shape stays constant so the trailing edge has a stable hit-target footprint; only the colour token flips. This is the canonical pattern across Side Sheet, channel lists, directory rows.
|
|
142
140
|
|
|
@@ -63,19 +63,19 @@
|
|
|
63
63
|
},
|
|
64
64
|
"footer": {
|
|
65
65
|
"required": false,
|
|
66
|
-
"description": "Pinned bottom action rail. Separated from the body by a `
|
|
66
|
+
"description": "Pinned bottom action rail. Separated from the body by a `border.default` hairline. `16px` inline + block padding."
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"sizing": {
|
|
70
70
|
"scrimColor": "ref.palette.black.600",
|
|
71
|
-
"cardBg": "sys.color.surface",
|
|
71
|
+
"cardBg": "sys.color.surface.default",
|
|
72
72
|
"cardElevation": "sys.elevation.sheet",
|
|
73
73
|
"cardDefaultWidth": "320px",
|
|
74
74
|
"bodyPadding": "sys.layout.container.lg (24) block × sys.layout.container.md (16) inline",
|
|
75
75
|
"bodyStackGap": "sys.layout.stack.lg (24) — between groups",
|
|
76
76
|
"groupStackGap": "sys.layout.stack.md (16) — within group, Header → List",
|
|
77
77
|
"footerPadding": "sys.layout.container.md (16)",
|
|
78
|
-
"footerDivider": "sys.borderWidth.hairline / sys.color.
|
|
78
|
+
"footerDivider": "sys.borderWidth.hairline / sys.color.border.default"
|
|
79
79
|
},
|
|
80
80
|
"states": {
|
|
81
81
|
"open": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 🇰🇷 한국어: [`i18n/ko/schema/components/skeleton/skeleton.md`](../../../i18n/ko/schema/components/skeleton/skeleton.md)
|
|
4
4
|
|
|
5
|
-
A tonal placeholder block that previews where real content will render. Paints with a translucent `
|
|
5
|
+
A tonal placeholder block that previews where real content will render. Paints with a translucent `background.neutral` overlay (~8% black in light mode, ~8% white in dark) and a slow opacity pulse — visible on every host surface tier without colliding with a fixed neutral step. Three shapes — `text` (default 16-line block), `block` (image / card body), `circle` (avatar). Compose multiple Skeletons inside `SkeletonGroup` to mirror the loading content's rhythm.
|
|
6
6
|
|
|
7
7
|
**Reach for this when** a list row, feed post, card cover, or avatar is being fetched and the host would otherwise paint empty. **Skip when** the wait is sub-300ms, the data is unavailable rather than loading (use an empty-state illustration), or the loading scope is the whole screen (use a centered progress indicator at page level).
|
|
8
8
|
|
|
@@ -20,9 +20,7 @@ import { Skeleton } from '@teamblind-chorus/ui';
|
|
|
20
20
|
<Skeleton />
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### Block
|
|
23
|
+
## Block
|
|
26
24
|
|
|
27
25
|
A rectangular tonal block. 80px high by default — reads as a card cover, image area, or card body placeholder. Pass `height` to match the real content's footprint.
|
|
28
26
|
|
|
@@ -34,7 +32,7 @@ import { Skeleton } from '@teamblind-chorus/ui';
|
|
|
34
32
|
<Skeleton shape="block" height={120} />
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
## Circle
|
|
38
36
|
|
|
39
37
|
An avatar placeholder. 40 × 40 by default — matches a 40-rung [Thumbnail](../thumbnail/thumbnail.md). Override `width` / `height` for a different rung.
|
|
40
38
|
|
|
@@ -46,7 +44,7 @@ import { Skeleton } from '@teamblind-chorus/ui';
|
|
|
46
44
|
<Skeleton shape="circle" />
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
## List row
|
|
50
48
|
|
|
51
49
|
A list-row loading state — leading 40-circle next to two stacked text lines. Use the same widths as the real row so the swap to live data doesn't reflow.
|
|
52
50
|
|
|
@@ -64,7 +62,7 @@ import { Skeleton, SkeletonGroup } from '@teamblind-chorus/ui';
|
|
|
64
62
|
</div>
|
|
65
63
|
```
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
## Feed post
|
|
68
66
|
|
|
69
67
|
A feed-post loading state — author row (avatar + name + meta), title, two body lines, and a 16:9 cover block. Mirrors the rhythm of a real feed/post.
|
|
70
68
|
|
|
@@ -90,13 +88,13 @@ import { Skeleton, SkeletonGroup } from '@teamblind-chorus/ui';
|
|
|
90
88
|
|
|
91
89
|
## Slots
|
|
92
90
|
|
|
93
|
-
- **container** — the tonal block. `sys.color.
|
|
91
|
+
- **container** — the tonal block. `sys.color.background.neutral` fill (translucent inverse-tone overlay — black in light, white in dark), shape-dependent radius. Carries the pulse animation. `role='status'` + `aria-live='polite'` so screen readers announce the loading state without yanking focus.
|
|
94
92
|
|
|
95
93
|
## Anatomy
|
|
96
94
|
|
|
97
95
|
| Slot | Token bindings |
|
|
98
96
|
|------------|----------------|
|
|
99
|
-
| container | `sys.color.
|
|
97
|
+
| container | `sys.color.background.neutral` fill, `sys.radius.xs` corners (`text` / `block`) or `sys.radius.full` (`circle`), no stroke |
|
|
100
98
|
| text | Default `ref.space.200` (16px) height × 100% width |
|
|
101
99
|
| block | Default `ref.space.1000` (80px) height × 100% width |
|
|
102
100
|
| circle | Default `ref.space.500` × `ref.space.500` (40 × 40) round |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../spec.schema.json",
|
|
3
3
|
"name": "Skeleton",
|
|
4
4
|
"family": "skeleton",
|
|
5
|
-
"description": "Single tonal placeholder block painted on `sys.color.
|
|
5
|
+
"description": "Single tonal placeholder block painted on `sys.color.background.neutral` (~8% inverse-tone overlay — black scrim in light mode, white scrim in dark) with a slow opacity pulse (`0.5 → 1 → 0.5` over 1.6s). The translucent fill stays visible on every host surface tier, so the placeholder reads cleanly whether it sits on a plain page surface, an elevated container, a hero band, or a coloured card. Three shapes select the default footprint and corner radius: `text` (`ref.space.200` / 16px high, full-width — single line of body copy), `block` (`ref.space.1000` / 80px high, full-width — image / card body) and `circle` (`ref.space.500` × `ref.space.500` / 40 × 40, fully rounded — avatar). Consumer-supplied `width` / `height` props override the default footprint. Compose multiple Skeletons inside `SkeletonGroup` to mirror the rhythm of the content being loaded.",
|
|
6
6
|
"element": "span",
|
|
7
7
|
"props": {
|
|
8
8
|
"shape": {
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"slots": {
|
|
33
33
|
"container": {
|
|
34
34
|
"required": true,
|
|
35
|
-
"description": "Tonal block. `sys.color.
|
|
35
|
+
"description": "Tonal block. `sys.color.background.neutral` fill (translucent black/white inverse-tone overlay — visible on every host surface), shape-dependent radius, no stroke. Carries the pulse animation. `role='status'` + `aria-live='polite'` so screen readers announce the loading state without yanking focus.",
|
|
36
36
|
"intrinsic": true
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"sizing": {
|
|
40
|
-
"background": "sys.color.
|
|
40
|
+
"background": "sys.color.background.neutral",
|
|
41
41
|
"radiusText": "sys.radius.xs",
|
|
42
42
|
"radiusBlock": "sys.radius.xs",
|
|
43
43
|
"radiusCircle": "sys.radius.full",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"appearances": {
|
|
50
50
|
"default": {
|
|
51
|
-
"background": "sys.color.
|
|
51
|
+
"background": "sys.color.background.neutral",
|
|
52
52
|
"note": "The only fill tone — Skeleton has no emphasis axis. Painted as a translucent ~8% inverse-tone overlay (black in light mode, white in dark) so the placeholder stays visible on every host surface tier (surface, surfaceContainer, surfaceContainerHigh, hero, …) without colliding with a fixed neutral step. The pulse modulates opacity, not hue, so the placeholder reads as anonymous chrome."
|
|
53
53
|
}
|
|
54
54
|
},
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"footprintOverrides": "`width` / `height` props win over the shape's default footprint, including for `circle` (a circle Skeleton at `width=60 height=60` stays a 60-rung round)."
|
|
65
65
|
},
|
|
66
66
|
"forbidden": [
|
|
67
|
-
"skeleton fill stepped to `sys.color.primary` or any chromatic tone — the placeholder must read as anonymous chrome, not as a content tone",
|
|
67
|
+
"skeleton fill stepped to `sys.color.background.primary` or any chromatic tone — the placeholder must read as anonymous chrome, not as a content tone",
|
|
68
68
|
"shimmer / gradient sweep animation — Chorus skeletons modulate opacity only (one motion axis keeps the tier readable under reduced-motion fallback)",
|
|
69
69
|
"skeleton stacked vertically without a SkeletonGroup wrapper — group via SkeletonGroup so the 8px gap stays consistent across compositions",
|
|
70
70
|
"skeleton kept on-screen after the real content resolves — the placeholder must swap out atomically, not cross-fade with the loaded data",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../family.schema.json",
|
|
3
|
+
"family": "spinner",
|
|
4
|
+
"name": "Spinner",
|
|
5
|
+
"description": "Indeterminate loading indicator — a rotating arc in `sys.color.background.primary` that signals a short, progress-unknown wait (under ~1 second of expected delay) on a neutral host surface. Two rungs ride the `icon.*` size ladder: `medium` (`sys.icon.lg` / 24px, default) and `small` (`sys.icon.md` / 16px). An optional `label` slot lets a single line of loading copy sit beside the arc. Reserved to one Spinner per view — for content-shaped waits use `skeleton`, for a known ratio use `progress`. Single-spec family.",
|
|
6
|
+
"useCases": [
|
|
7
|
+
"sub-second indeterminate wait (button submit, inline action)",
|
|
8
|
+
"small surface where a skeleton would be heavier than the wait",
|
|
9
|
+
"centered first-paint loader before a screen's data resolves",
|
|
10
|
+
"loading copy beside a rotating indicator"
|
|
11
|
+
],
|
|
12
|
+
"visualReuse": "open",
|
|
13
|
+
"layoutInset": "inline",
|
|
14
|
+
"spec": "spinner.md",
|
|
15
|
+
"usage": {
|
|
16
|
+
"note": "indeterminate only — never bind it to a progress ratio (use Progress). Reserve to one Spinner per view. aria-label defaults to 'Loading'; pass a label child for visible loading copy beside the arc.",
|
|
17
|
+
"example": "<Spinner aria-label=\"Loading\" />"
|
|
18
|
+
},
|
|
19
|
+
"subcomponents": [
|
|
20
|
+
{
|
|
21
|
+
"slug": "spinner",
|
|
22
|
+
"spec": "spinner.spec.json",
|
|
23
|
+
"md": "spinner.md",
|
|
24
|
+
"default": true
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Spinner
|
|
2
|
+
|
|
3
|
+
> 🇰🇷 한국어: [`i18n/ko/schema/components/spinner/spinner.md`](../../../i18n/ko/schema/components/spinner/spinner.md)
|
|
4
|
+
|
|
5
|
+
An indeterminate loading indicator — a rotating arc in `sys.color.background.primary` over a faint `background.neutral` ring that signals a short, progress-unknown wait on a neutral host surface. Two rungs ride the `icon.*` ladder — `medium` (24px, default) and `small` (16px). Pass a `label` for a single line of loading copy beside the arc.
|
|
6
|
+
|
|
7
|
+
**Reach for this when** a wait is brief and indeterminate (under ~1 second) — a button submit, an inline action, or a first-paint loader before a screen's data resolves — and a content-shaped placeholder would be heavier than the wait itself. **Skip when** the wait mirrors a known shape (use [Skeleton](../skeleton/skeleton.md)), the task has a measurable ratio (use [Progress](../progress/progress.md)), or the surface already shows another Spinner — reserve one per view.
|
|
8
|
+
|
|
9
|
+
**Layout inset.** `inline` — Spinner ships no padding or container chrome of its own. It sits as a leaf at the size of its rung; the host owns centering and surrounding rhythm. An optional label sits beside the arc with `sys.layout.inline.sm` between them.
|
|
10
|
+
|
|
11
|
+
## Default
|
|
12
|
+
|
|
13
|
+
A 24px rotating arc. `role='status'` announces the loading state; `aria-label` defaults to `'Loading'`.
|
|
14
|
+
|
|
15
|
+
```preview
|
|
16
|
+
spinner/default
|
|
17
|
+
---
|
|
18
|
+
import { Spinner } from '@teamblind-chorus/ui';
|
|
19
|
+
|
|
20
|
+
<Spinner aria-label="Loading" />
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Label
|
|
24
|
+
|
|
25
|
+
A single line of loading copy beside the arc. The label doubles as the accessible name, so `aria-label` is not needed.
|
|
26
|
+
|
|
27
|
+
```preview
|
|
28
|
+
spinner/with-label
|
|
29
|
+
---
|
|
30
|
+
import { Spinner } from '@teamblind-chorus/ui';
|
|
31
|
+
|
|
32
|
+
<Spinner label="Loading…" />
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Small
|
|
36
|
+
|
|
37
|
+
The 16px rung — for tight inline affordances (a button label, a form-field affix) where the 24px default would crowd.
|
|
38
|
+
|
|
39
|
+
```preview
|
|
40
|
+
spinner/small
|
|
41
|
+
---
|
|
42
|
+
import { Spinner } from '@teamblind-chorus/ui';
|
|
43
|
+
|
|
44
|
+
<Spinner size="small" aria-label="Loading" />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Centered
|
|
48
|
+
|
|
49
|
+
A first-paint loader centered inside the surface that will hold the data. One Spinner per view.
|
|
50
|
+
|
|
51
|
+
```preview
|
|
52
|
+
spinner/centered
|
|
53
|
+
---
|
|
54
|
+
import { Spinner } from '@teamblind-chorus/ui';
|
|
55
|
+
|
|
56
|
+
<div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--sys-layout-container-xl)' }}>
|
|
57
|
+
<Spinner label="Loading…" />
|
|
58
|
+
</div>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Slots
|
|
62
|
+
|
|
63
|
+
- **container** — inline-flex wrapper. Carries `role='status'` and the accessible name; holds the arc and the optional label, separated by `sys.layout.inline.sm`.
|
|
64
|
+
- **arc** — the rotating ring. `sys.color.background.primary` foreground arc over a `sys.color.background.neutral` track ring, fully rounded, no stroke. Carries the spin animation. Decorative (`aria-hidden`).
|
|
65
|
+
- **label** — optional single line of loading copy beside the arc. `sys.typo.body.sm`, `sys.color.text.subtle`.
|
|
66
|
+
|
|
67
|
+
## Anatomy
|
|
68
|
+
|
|
69
|
+
| Slot | Token bindings |
|
|
70
|
+
|------------|----------------|
|
|
71
|
+
| container | inline-flex, `sys.layout.inline.sm` gap between arc and label |
|
|
72
|
+
| arc | `sys.color.background.primary` foreground arc, `sys.color.background.neutral` track ring, `sys.radius.full`, no stroke |
|
|
73
|
+
| medium | `sys.icon.lg` (24px) diameter — the default rung |
|
|
74
|
+
| small | `sys.icon.md` (16px) diameter |
|
|
75
|
+
| label | `sys.typo.body.sm`, `sys.color.text.subtle` |
|
|
76
|
+
|
|
77
|
+
## Sizes
|
|
78
|
+
|
|
79
|
+
| Size | Diameter | When to reach |
|
|
80
|
+
|----------|---------------------|---------------|
|
|
81
|
+
| `medium` | `sys.icon.lg` (24px) | **Default**. Standalone or centered first-paint loaders. |
|
|
82
|
+
| `small` | `sys.icon.md` (16px) | Tight inline affordances — button labels, form-field affixes. |
|
|
83
|
+
|
|
84
|
+
## States
|
|
85
|
+
|
|
86
|
+
| State | Animation | Notes |
|
|
87
|
+
|----------------|---------------------------------|-------|
|
|
88
|
+
| `default` | `rotate 0.8s linear infinite` | The arc spins continuously. Indeterminate — the rotation never reflects a value. |
|
|
89
|
+
| reduced-motion | suppressed | Under `prefers-reduced-motion: reduce` the spin halts and the full ring shows statically. |
|
|
90
|
+
|
|
91
|
+
## Behavior
|
|
92
|
+
|
|
93
|
+
- **Indeterminate only.** The rotation never maps to a ratio — for a known percentage use [Progress](../progress/progress.md).
|
|
94
|
+
- **One per view.** Reserve a single Spinner per screen; concurrent regional waits use [Skeleton](../skeleton/skeleton.md) or lift to one screen-level Spinner.
|
|
95
|
+
- **Accessible name.** `role='status'` announces loading without interrupting focus. A visible `label` doubles as the name; otherwise `aria-label` (default `'Loading'`) carries it. The arc is decorative.
|
|
96
|
+
- **Reduced motion.** Under `prefers-reduced-motion: reduce` the spin is suppressed; the full ring shows statically as a quiet loading mark.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../spec.schema.json",
|
|
3
|
+
"name": "Spinner",
|
|
4
|
+
"family": "spinner",
|
|
5
|
+
"description": "A rotating arc that signals an indeterminate, sub-second wait on a neutral host surface. The arc paints in `sys.color.background.primary` as the foreground motion and spins continuously over a faint `sys.color.background.neutral` ring so the rotation reads on any surface tier. Two rungs ride the `icon.*` ladder — `medium` (`sys.icon.lg` / 24px, default) and `small` (`sys.icon.md` / 16px). An optional `label` slot places a single line of loading copy beside the arc. `role='status'` + an accessible name (`aria-label`, default `'Loading'`) announce the loading state. Indeterminate only — for a known ratio use Progress; for content-shaped waits use Skeleton.",
|
|
6
|
+
"element": "span",
|
|
7
|
+
"props": {
|
|
8
|
+
"size": {
|
|
9
|
+
"type": "enum",
|
|
10
|
+
"values": ["medium", "small"],
|
|
11
|
+
"default": "medium",
|
|
12
|
+
"description": "Selects the arc diameter off the `icon.*` ladder. `medium` paints at `sys.icon.lg` (24px); `small` at `sys.icon.md` (16px)."
|
|
13
|
+
},
|
|
14
|
+
"label": {
|
|
15
|
+
"type": "node",
|
|
16
|
+
"optional": true,
|
|
17
|
+
"description": "Optional loading copy rendered beside the arc in `sys.typo.body.sm` / `sys.color.text.subtle`. When present it also supplies the accessible name, so `aria-label` is not required."
|
|
18
|
+
},
|
|
19
|
+
"aria-label": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"optional": true,
|
|
22
|
+
"description": "Accessible label announced by screen readers. Defaults to `'Loading'`. Supply a more specific name (e.g. `'Signing in'`) when the wait scope is meaningful. Redundant when a visible `label` is passed."
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"slots": {
|
|
26
|
+
"container": {
|
|
27
|
+
"required": true,
|
|
28
|
+
"description": "Inline-flex wrapper carrying `role='status'` and the accessible name. Holds the arc and the optional label, separated by `sys.layout.inline.sm`.",
|
|
29
|
+
"intrinsic": true
|
|
30
|
+
},
|
|
31
|
+
"arc": {
|
|
32
|
+
"required": true,
|
|
33
|
+
"description": "The rotating ring. `sys.color.background.primary` foreground arc over a `sys.color.background.neutral` track ring, fully rounded, no stroke border. Carries the continuous spin animation. Decorative — `aria-hidden`.",
|
|
34
|
+
"intrinsic": true
|
|
35
|
+
},
|
|
36
|
+
"label": {
|
|
37
|
+
"required": false,
|
|
38
|
+
"description": "Optional single line of loading copy beside the arc. `sys.typo.body.sm`, `sys.color.text.subtle`.",
|
|
39
|
+
"omittedBehavior": "collapse"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"sizes": {
|
|
43
|
+
"medium": {
|
|
44
|
+
"diameter": "sys.icon.lg",
|
|
45
|
+
"labelTypo": "sys.typo.body.sm",
|
|
46
|
+
"gap": "sys.layout.inline.sm"
|
|
47
|
+
},
|
|
48
|
+
"small": {
|
|
49
|
+
"diameter": "sys.icon.md",
|
|
50
|
+
"labelTypo": "sys.typo.body.sm",
|
|
51
|
+
"gap": "sys.layout.inline.sm"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"appearances": {
|
|
55
|
+
"default": {
|
|
56
|
+
"arc": "sys.color.border.primary",
|
|
57
|
+
"track": "sys.color.background.neutral",
|
|
58
|
+
"label": "sys.color.text.subtle",
|
|
59
|
+
"note": "The only appearance. The foreground arc paints `sys.color.background.primary` for the motion accent; the track ring paints `sys.color.background.neutral` (a faint inverse-tone scrim — ~8% black light / ~8% white dark) so the rotation reads on any host surface tier. Spinner has no emphasis axis — for a higher-attention loading mark the emphasis belongs in adjacent copy, not in a chromatic arc swap."
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"states": {
|
|
63
|
+
"default": {
|
|
64
|
+
"animation": "chorus-spinner-rotate 0.8s linear infinite",
|
|
65
|
+
"note": "The arc rotates continuously at 0.8s per turn (well below the WCAG flash threshold — rotation modulates position, not luminance). Indeterminate: the rotation never reflects a value. The animation respects `prefers-reduced-motion: reduce` — the spin halts and the full ring is shown statically as a quiet loading mark."
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"behavior": {
|
|
69
|
+
"ariaStatus": "Container carries `role='status'` and the accessible name (visible `label` or `aria-label`, default `'Loading'`) so screen readers announce the loading state without yanking focus. The arc is decorative (`aria-hidden`).",
|
|
70
|
+
"reducedMotion": "Under `@media (prefers-reduced-motion: reduce)` the spin is suppressed; the full ring shows statically as a quiet loading mark.",
|
|
71
|
+
"singleInstance": "Reserve one Spinner per view. Concurrent regional waits stack visual noise — lift to a single screen-level Spinner or switch the inner waits to Skeleton.",
|
|
72
|
+
"labelComposition": "Pass `label` for visible loading copy beside the arc; it doubles as the accessible name. Without a label, `aria-label` (default `'Loading'`) carries the announcement."
|
|
73
|
+
},
|
|
74
|
+
"forbidden": [
|
|
75
|
+
"Spinner bound to a known progress ratio / percentage — indeterminate only; a determinate value belongs to `progress`",
|
|
76
|
+
"more than one Spinner visible in a single view — reserve to one per view; concurrent regional waits use `skeleton` or lift to a single screen-level Spinner",
|
|
77
|
+
"Spinner used as a content-shaped placeholder (list row, feed card, avatar) — that role is `skeleton`, which mirrors the footprint of the data being fetched",
|
|
78
|
+
"arc painted with a non-token hex / raw px diameter — the arc tone is `sys.color.background.primary` and the diameter rides the `icon.*` ladder (`sys.icon.md` / `sys.icon.lg`)",
|
|
79
|
+
"shimmer / gradient sweep on the arc — Spinner modulates rotation only (one motion axis keeps it readable under the reduced-motion fallback)",
|
|
80
|
+
"arc track or ring drawn with a `border:` — the ring draws as a `box-shadow` / conic fill, not a layout stroke"
|
|
81
|
+
]
|
|
82
|
+
}
|