@teamblind-chorus/ui 1.0.1 → 1.2.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/agents/AGENTS.md +4 -6
- package/agents/DESIGN.md +2 -0
- package/agents/LOVABLE.md +167 -373
- package/agents/anti-patterns.md +2 -2
- package/agents/catalog.md +12 -6
- package/agents/components/avatar-rail/avatar-rail.md +2 -0
- package/agents/components/avatar-rail/avatar-rail.spec.json +19 -0
- package/agents/components/badge/badge.md +2 -0
- package/agents/components/badge/role.md +2 -0
- package/agents/components/badge/update.md +2 -0
- package/agents/components/banner/banner.family.json +3 -1
- package/agents/components/banner/banner.md +125 -9
- package/agents/components/banner/banner.spec.json +64 -3
- package/agents/components/bottom-sheet/bottom-sheet.md +2 -0
- package/agents/components/bubble/bubble.md +2 -0
- package/agents/components/button/button.family.json +8 -2
- package/agents/components/button/button.md +2 -0
- package/agents/components/button/check.md +2 -0
- package/agents/components/button/check.spec.json +19 -0
- package/agents/components/button/fab.md +2 -0
- package/agents/components/button/fab.spec.json +19 -0
- package/agents/components/button/group.spec.json +65 -0
- package/agents/components/button/icon.md +2 -0
- package/agents/components/button/icon.spec.json +19 -0
- package/agents/components/button/standard.md +45 -19
- package/agents/components/button/standard.spec.json +19 -0
- package/agents/components/button/text.md +2 -0
- package/agents/components/button/text.spec.json +19 -0
- package/agents/components/button/toggle.md +2 -0
- package/agents/components/button/toggle.spec.json +19 -0
- package/agents/components/button/toolbar.md +2 -0
- package/agents/components/carousel/carousel.md +2 -0
- package/agents/components/carousel/post.md +5 -3
- package/agents/components/carousel/post.spec.json +4 -6
- package/agents/components/carousel/profile.md +4 -2
- package/agents/components/carousel/profile.spec.json +4 -6
- package/agents/components/chip/chip.md +2 -0
- package/agents/components/chip/filter.md +2 -0
- package/agents/components/chip/filter.spec.json +19 -0
- package/agents/components/chip/tag.md +2 -0
- package/agents/components/chip/tag.spec.json +19 -0
- package/agents/components/dialog/dialog.md +2 -0
- package/agents/components/directory-list/directory-list.md +2 -0
- package/agents/components/divider/divider.md +2 -0
- 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 -0
- package/agents/components/feed/feed.md +2 -0
- package/agents/components/feed/post.md +2 -0
- package/agents/components/form-field/form-field.md +3 -1
- package/agents/components/form-field/input.md +2 -0
- package/agents/components/form-field/input.spec.json +10 -2
- package/agents/components/form-field/search.md +2 -0
- package/agents/components/form-field/search.spec.json +10 -2
- package/agents/components/form-field/select.md +2 -0
- package/agents/components/form-field/select.spec.json +9 -1
- package/agents/components/form-field/textarea.md +2 -0
- package/agents/components/form-field/textarea.spec.json +10 -2
- package/agents/components/header/header.md +2 -0
- package/agents/components/header/main.md +2 -0
- package/agents/components/header/sub.md +2 -0
- package/agents/components/list/accordion.md +2 -0
- package/agents/components/list/accordion.spec.json +9 -0
- package/agents/components/list/entry.md +2 -0
- package/agents/components/list/entry.spec.json +21 -1
- package/agents/components/list/list.md +3 -1
- package/agents/components/list/radio.md +2 -0
- package/agents/components/list/radio.spec.json +19 -0
- package/agents/components/list/standard.md +48 -0
- package/agents/components/list/standard.spec.json +39 -3
- package/agents/components/metadata/compact.md +13 -7
- package/agents/components/metadata/compact.spec.json +19 -6
- package/agents/components/metadata/metadata.family.json +3 -3
- package/agents/components/metadata/metadata.md +4 -2
- package/agents/components/metadata/standard.md +24 -0
- package/agents/components/nav-card/nav-card.md +2 -0
- package/agents/components/nav-card/nav-card.spec.json +9 -0
- package/agents/components/nav-list/nav-list.md +2 -0
- package/agents/components/navigation-bar/main.md +2 -0
- package/agents/components/navigation-bar/navigation-bar.md +2 -0
- package/agents/components/navigation-bar/search.md +2 -0
- package/agents/components/navigation-bar/sub.md +2 -0
- package/agents/components/page-shell/page-shell.family.json +1 -1
- package/agents/components/page-shell/page-shell.md +35 -0
- package/agents/components/page-shell/page-shell.spec.json +85 -0
- package/agents/components/pagination/pagination.family.json +26 -0
- package/agents/components/pagination/pagination.md +40 -0
- package/agents/components/pagination/pagination.spec.json +54 -0
- package/agents/components/profile-header/profile-header.md +2 -0
- package/agents/components/progress/progress.md +2 -0
- package/agents/components/side-sheet/side-sheet.md +2 -0
- package/agents/components/skeleton/skeleton.md +2 -0
- package/agents/components/spinner/spinner.family.json +27 -0
- package/agents/components/spinner/spinner.md +98 -0
- package/agents/components/spinner/spinner.spec.json +82 -0
- package/agents/components/status-tag/status-tag.md +2 -0
- package/agents/components/suggestion-list/suggestion-list.md +2 -0
- package/agents/components/switch/switch.md +2 -0
- package/agents/components/switch/switch.spec.json +9 -0
- package/agents/components/tab-bar/tab-bar.md +2 -0
- package/agents/components/tab-bar/tab-bar.spec.json +16 -0
- package/agents/components/tabs/rounded.md +2 -0
- package/agents/components/tabs/rounded.spec.json +19 -0
- package/agents/components/tabs/segmented.md +2 -0
- package/agents/components/tabs/tabs.md +2 -0
- package/agents/components/tabs/underline.md +2 -0
- package/agents/components/tabs/underline.spec.json +19 -0
- package/agents/components/thumbnail/thumbnail.md +2 -0
- package/agents/components/toast/toast.md +2 -0
- package/agents/components/tooltip/tooltip.md +2 -0
- package/agents/compose.md +3 -3
- package/agents/manifest.json +9 -6
- package/agents/patterns/README.md +2 -0
- package/agents/patterns/actions.md +2 -0
- package/agents/patterns/browsing.md +2 -0
- package/agents/patterns/communications.md +2 -0
- package/agents/patterns/layout.md +2 -0
- package/agents/patterns/modals.md +2 -0
- package/agents/patterns/visual.md +2 -0
- package/agents/usage.json +27 -3
- package/dist/index.cjs +433 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -3
- package/dist/index.d.ts +74 -3
- package/dist/index.js +430 -98
- package/dist/index.js.map +1 -1
- package/dist/styles.css +365 -41
- package/package.json +1 -2
- package/agents/reconstruct.md +0 -55
- package/agents/scoped-adoption.md +0 -111
package/dist/styles.css
CHANGED
|
@@ -222,6 +222,65 @@ textarea {
|
|
|
222
222
|
opacity: 0;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/* ------------------------------------------------------------
|
|
226
|
+
ButtonGroup — Buttons composed as one block. The row carries the
|
|
227
|
+
family's 8px (`sys.layout.inline.md`) gap; an optional label stacks
|
|
228
|
+
above it. The docked form adds the footer-bar chrome (surface fill,
|
|
229
|
+
16px inset, upward `sys.elevation.sheet` shadow). See ButtonGroup.jsx
|
|
230
|
+
and schema/components/button/standard.md.
|
|
231
|
+
------------------------------------------------------------ */
|
|
232
|
+
.chorus-button-group {
|
|
233
|
+
display: flex;
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.chorus-button-group__row {
|
|
238
|
+
display: flex;
|
|
239
|
+
gap: var(--sys-layout-inline-md);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Vertical inline group — full-width Buttons stacked (primary over
|
|
243
|
+
secondary). Same 8px gap, now on the block axis. */
|
|
244
|
+
.chorus-button-group--vertical .chorus-button-group__row {
|
|
245
|
+
flex-direction: column;
|
|
246
|
+
align-items: stretch;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Optional caption above the row — `sys.typo.body.md` (16px) in the muted
|
|
250
|
+
on-surface tone, centered, 16px (`sys.layout.stack.md`) above the row. An
|
|
251
|
+
inline <strong> reads as the emphasized value in the full-strength tone. */
|
|
252
|
+
.chorus-button-group__label {
|
|
253
|
+
text-align: center;
|
|
254
|
+
margin-bottom: var(--sys-layout-stack-md);
|
|
255
|
+
color: var(--sys-color-onSurfaceVariant);
|
|
256
|
+
font-size: var(--sys-typo-body-md-size);
|
|
257
|
+
font-weight: var(--sys-typo-body-md-weight);
|
|
258
|
+
line-height: var(--sys-typo-body-md-line);
|
|
259
|
+
letter-spacing: var(--sys-typo-body-md-tracking);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.chorus-button-group__label strong {
|
|
263
|
+
color: var(--sys-color-onSurface);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Docked footer bar — full-bleed surface pinned to the bottom of the app.
|
|
267
|
+
No top stroke; the upward `sys.elevation.sheet` shadow (same one
|
|
268
|
+
BottomSheet / SideSheet cast) lifts it off the scrolling body so content
|
|
269
|
+
passing behind reads as a separate region. Renders in flow — PageShell
|
|
270
|
+
owns the pin, so the bar must NOT set position: sticky/fixed itself. */
|
|
271
|
+
.chorus-button-group--docked {
|
|
272
|
+
background: var(--sys-color-surface);
|
|
273
|
+
padding: var(--sys-layout-container-md);
|
|
274
|
+
box-shadow: var(--sys-elevation-sheet);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* In the docked bar the two Buttons split the row equally, so the consumer
|
|
278
|
+
drops in two `<Button size="large">` without wiring `fullWidth`. */
|
|
279
|
+
.chorus-button-group--docked .chorus-button-group__row > * {
|
|
280
|
+
flex: 1 1 0;
|
|
281
|
+
min-width: 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
225
284
|
/* ============================================================
|
|
226
285
|
FAB
|
|
227
286
|
============================================================ */
|
|
@@ -1057,6 +1116,8 @@ a.chorus-metadata__name:focus-visible {
|
|
|
1057
1116
|
timestamp. Single text line — the row never wraps; identity links
|
|
1058
1117
|
truncate before the timestamp gives way. */
|
|
1059
1118
|
.chorus-metadata--compact .chorus-metadata__meta {
|
|
1119
|
+
flex: 1 1 auto;
|
|
1120
|
+
min-width: 0;
|
|
1060
1121
|
flex-wrap: nowrap;
|
|
1061
1122
|
white-space: nowrap;
|
|
1062
1123
|
}
|
|
@@ -1074,6 +1135,9 @@ a.chorus-metadata__name:focus-visible {
|
|
|
1074
1135
|
|
|
1075
1136
|
.chorus-metadata--compact .chorus-metadata__timestamp {
|
|
1076
1137
|
flex: 0 0 auto;
|
|
1138
|
+
/* No leading middot — pay the name↔time gap directly (matches the
|
|
1139
|
+
Standard head's 8px primary-line gap). */
|
|
1140
|
+
margin-inline-start: var(--sys-layout-inline-md);
|
|
1077
1141
|
}
|
|
1078
1142
|
|
|
1079
1143
|
/* ============================================================
|
|
@@ -1248,7 +1312,7 @@ a.chorus-metadata__name:focus-visible {
|
|
|
1248
1312
|
gap: var(--sys-layout-inline-sm);
|
|
1249
1313
|
}
|
|
1250
1314
|
.chorus-feed__poll-glyph { color: var(--sys-color-brand); display: inline-flex; }
|
|
1251
|
-
.chorus-feed__poll-label { color: var(--sys-color-brand); font-weight:
|
|
1315
|
+
.chorus-feed__poll-label { color: var(--sys-color-brand); font-weight: var(--ref-fontWeight-bold); }
|
|
1252
1316
|
|
|
1253
1317
|
/* Offer-evaluation tone — same chrome as the poll banner, but the
|
|
1254
1318
|
leading glyph and label paint in `sys.color.success` (resolves to
|
|
@@ -1266,7 +1330,7 @@ a.chorus-metadata__name:focus-visible {
|
|
|
1266
1330
|
background: var(--sys-color-outlineVariant);
|
|
1267
1331
|
}
|
|
1268
1332
|
.chorus-feed__poll-participants { color: var(--sys-color-onSurface); }
|
|
1269
|
-
.chorus-feed__poll-participants strong { font-weight:
|
|
1333
|
+
.chorus-feed__poll-participants strong { font-weight: var(--ref-fontWeight-bold); }
|
|
1270
1334
|
|
|
1271
1335
|
.chorus-feed__citation {
|
|
1272
1336
|
/* Citation is one bordered surface: the hero and the text column sit
|
|
@@ -1831,23 +1895,12 @@ a.chorus-metadata__name:focus-visible {
|
|
|
1831
1895
|
color: currentColor;
|
|
1832
1896
|
}
|
|
1833
1897
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
.chorus-post-carousel__dot {
|
|
1842
|
-
width: var(--ref-space-75);
|
|
1843
|
-
height: var(--ref-space-75);
|
|
1844
|
-
border-radius: var(--sys-radius-full);
|
|
1845
|
-
background: var(--sys-color-outlineVariant);
|
|
1846
|
-
display: block;
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
.chorus-post-carousel__dot--active {
|
|
1850
|
-
background: var(--sys-color-onSurface);
|
|
1898
|
+
/* Pagination dots below the pager are the shared Pagination component
|
|
1899
|
+
(.chorus-pagination) — no carousel-local dot rules. The component is
|
|
1900
|
+
an intrinsic-width inline element, so the carousel (column flex,
|
|
1901
|
+
align-items stretch) centers it itself. */
|
|
1902
|
+
.chorus-post-carousel > .chorus-pagination {
|
|
1903
|
+
align-self: center;
|
|
1851
1904
|
}
|
|
1852
1905
|
|
|
1853
1906
|
/* ============================================================
|
|
@@ -2078,23 +2131,12 @@ a.chorus-metadata__name:focus-visible {
|
|
|
2078
2131
|
justify-content: center;
|
|
2079
2132
|
}
|
|
2080
2133
|
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
.chorus-profile-carousel__dot {
|
|
2089
|
-
width: var(--ref-space-75);
|
|
2090
|
-
height: var(--ref-space-75);
|
|
2091
|
-
border-radius: var(--sys-radius-full);
|
|
2092
|
-
background: var(--sys-color-outlineVariant);
|
|
2093
|
-
display: block;
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
.chorus-profile-carousel__dot--active {
|
|
2097
|
-
background: var(--sys-color-onSurface);
|
|
2134
|
+
/* Pagination dots below the pager are the shared Pagination component
|
|
2135
|
+
(.chorus-pagination) — no carousel-local dot rules. The component is
|
|
2136
|
+
an intrinsic-width inline element, so the carousel (column flex,
|
|
2137
|
+
align-items stretch) centers it itself. */
|
|
2138
|
+
.chorus-profile-carousel > .chorus-pagination {
|
|
2139
|
+
align-self: center;
|
|
2098
2140
|
}
|
|
2099
2141
|
|
|
2100
2142
|
/* ============================================================
|
|
@@ -2437,11 +2479,20 @@ a.chorus-metadata__name:focus-visible {
|
|
|
2437
2479
|
pointer-events: none;
|
|
2438
2480
|
}
|
|
2439
2481
|
|
|
2440
|
-
|
|
2482
|
+
/* Nested-action scope — a trailing slot carrying an independent action
|
|
2483
|
+
(a Follow / mute / favorite button, an overflow control) is marked
|
|
2484
|
+
`data-nested-action` and already stops click/key propagation so it never
|
|
2485
|
+
commits the row's primary action. The row's hover / press overlay is
|
|
2486
|
+
suppressed while the pointer sits on that action so the large row does
|
|
2487
|
+
NOT read as hovered / pressed at the same time the small control does —
|
|
2488
|
+
the visual state boundary matches the event boundary. The decorative
|
|
2489
|
+
nav chevron carries no `data-nested-action`, so hovering it still lights
|
|
2490
|
+
the row (it IS the row's drill-in affordance). */
|
|
2491
|
+
.chorus-list__row:hover:not(:has([data-nested-action]:hover)) {
|
|
2441
2492
|
background: color-mix(in srgb, var(--sys-color-onSurface) calc(var(--sys-state-hover) * 100%), transparent);
|
|
2442
2493
|
}
|
|
2443
2494
|
|
|
2444
|
-
.chorus-list__row:active {
|
|
2495
|
+
.chorus-list__row:active:not(:has([data-nested-action]:active)) {
|
|
2445
2496
|
background: color-mix(in srgb, var(--sys-color-onSurface) calc(var(--sys-state-pressed) * 100%), transparent);
|
|
2446
2497
|
}
|
|
2447
2498
|
|
|
@@ -2564,6 +2615,31 @@ a.chorus-metadata__name:focus-visible {
|
|
|
2564
2615
|
margin-left: var(--sys-layout-inline-md);
|
|
2565
2616
|
}
|
|
2566
2617
|
|
|
2618
|
+
/* Embedded-Banner row — a Standard row whose text group stacks over a
|
|
2619
|
+
Banner that spans the row's full content width. The row flips from a
|
|
2620
|
+
single horizontal line to a vertical stack: the normal leading + label
|
|
2621
|
+
+ trailing line (`__row-main`) sits on top, the Banner `stack.xs` (8)
|
|
2622
|
+
below it. The stack fills the row's content box so the Banner aligns to
|
|
2623
|
+
the same 16px inline inset as the text group above it. The Banner owns
|
|
2624
|
+
its own tinted fill / radius / padding; the slot only spaces it and
|
|
2625
|
+
marks it a nested-action region (its controls never commit the row). */
|
|
2626
|
+
.chorus-list__stack {
|
|
2627
|
+
display: flex;
|
|
2628
|
+
flex-direction: column;
|
|
2629
|
+
gap: var(--sys-layout-stack-xs);
|
|
2630
|
+
flex: 1 1 auto;
|
|
2631
|
+
min-width: 0;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
.chorus-list__row-main {
|
|
2635
|
+
display: flex;
|
|
2636
|
+
align-items: center;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
.chorus-list__banner {
|
|
2640
|
+
display: block;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2567
2643
|
/* Entry variant — directory-entry row with selectable leading Thumbnail
|
|
2568
2644
|
(32 / 40 / 48 / 56 via `data-size="small|medium|large|xlarge"`), an
|
|
2569
2645
|
identity group (label + optional inline count Badge + optional stacked
|
|
@@ -3227,6 +3303,65 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3227
3303
|
rule already excludes `[data-divider="false"]`, so no extra CSS
|
|
3228
3304
|
is needed to hide it here. */
|
|
3229
3305
|
|
|
3306
|
+
/* ============================================================
|
|
3307
|
+
EmptyState — centered no-data composition. Painted inside the
|
|
3308
|
+
surface that would otherwise hold the data: an optional monochrome
|
|
3309
|
+
illustration, a required headline, optional body copy, and an
|
|
3310
|
+
optional primary-Button CTA. Ships no surface fill of its own —
|
|
3311
|
+
the host supplies the surface tier and the bounding box; EmptyState
|
|
3312
|
+
only centers its column inside it. Inter-slot rhythm follows the
|
|
3313
|
+
stack tokens (illustration→headline 12, headline→body 4, body→CTA
|
|
3314
|
+
16). Distinct from Skeleton (an in-flight loading placeholder).
|
|
3315
|
+
============================================================ */
|
|
3316
|
+
.chorus-empty-state {
|
|
3317
|
+
display: flex;
|
|
3318
|
+
flex-direction: column;
|
|
3319
|
+
align-items: center;
|
|
3320
|
+
text-align: center;
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
/* Optional illustration — a glyph / illustration sized to a 48-box
|
|
3324
|
+
(larger than `sys.icon.lg`, realizing the 'icon.xl or larger' intent
|
|
3325
|
+
since no `sys.icon.xl` rung exists), painted monochrome in
|
|
3326
|
+
`onSurfaceVariant` via `currentColor` so it reads as quiet chrome,
|
|
3327
|
+
not a brand moment. 12 below it to the headline. */
|
|
3328
|
+
.chorus-empty-state__illustration {
|
|
3329
|
+
display: inline-flex;
|
|
3330
|
+
align-items: center;
|
|
3331
|
+
justify-content: center;
|
|
3332
|
+
width: var(--empty-state-illustration-size);
|
|
3333
|
+
height: var(--empty-state-illustration-size);
|
|
3334
|
+
margin-bottom: var(--empty-state-illustration-gap);
|
|
3335
|
+
color: var(--empty-state-illustration-color);
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
.chorus-empty-state__illustration img,
|
|
3339
|
+
.chorus-empty-state__illustration svg {
|
|
3340
|
+
width: var(--empty-state-illustration-size);
|
|
3341
|
+
height: var(--empty-state-illustration-size);
|
|
3342
|
+
display: block;
|
|
3343
|
+
color: currentColor;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
/* Required headline — heading.sm in `onSurface`. Typography vars are
|
|
3347
|
+
emitted inline by the React file (typoStyles). */
|
|
3348
|
+
.chorus-empty-state__headline {
|
|
3349
|
+
margin: 0;
|
|
3350
|
+
color: var(--empty-state-headline-color);
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
/* Optional body — body.sm in `onSurfaceVariant`, 4 below the headline. */
|
|
3354
|
+
.chorus-empty-state__body {
|
|
3355
|
+
margin: 0;
|
|
3356
|
+
margin-top: var(--empty-state-body-gap);
|
|
3357
|
+
color: var(--empty-state-body-color);
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/* Optional CTA — a default-size primary Button, 16 below the body. */
|
|
3361
|
+
.chorus-empty-state__action {
|
|
3362
|
+
margin-top: var(--empty-state-action-gap);
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3230
3365
|
/* ============================================================
|
|
3231
3366
|
Banner — in-body explanation block (info / neutral)
|
|
3232
3367
|
============================================================ */
|
|
@@ -3241,6 +3376,7 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3241
3376
|
.chorus-banner--accent {
|
|
3242
3377
|
background: var(--sys-color-primaryContainer);
|
|
3243
3378
|
color: var(--sys-color-onPrimaryContainer);
|
|
3379
|
+
--banner-outline-color: color-mix(in srgb, var(--sys-color-primary) 40%, transparent);
|
|
3244
3380
|
}
|
|
3245
3381
|
|
|
3246
3382
|
/* Destructive — error-tinted banner. Reach for it when the aside is a
|
|
@@ -3251,6 +3387,7 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3251
3387
|
.chorus-banner--destructive {
|
|
3252
3388
|
background: var(--sys-color-errorContainer);
|
|
3253
3389
|
color: var(--sys-color-onErrorContainer);
|
|
3390
|
+
--banner-outline-color: color-mix(in srgb, var(--sys-color-error) 40%, transparent);
|
|
3254
3391
|
}
|
|
3255
3392
|
|
|
3256
3393
|
/* Default uses `sys.color.scrimSubtle` (~8% inverse-tone overlay —
|
|
@@ -3264,6 +3401,21 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3264
3401
|
.chorus-banner--default {
|
|
3265
3402
|
background: var(--sys-color-scrimSubtle);
|
|
3266
3403
|
color: var(--sys-color-onSurface);
|
|
3404
|
+
--banner-outline-color: var(--sys-color-outlineVariant);
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
/* Optional outline — a hairline inset stroke toned to the appearance's
|
|
3408
|
+
color family (each appearance block above sets
|
|
3409
|
+
`--banner-outline-color`), kept deliberately faint so the edge reads
|
|
3410
|
+
as a soft boundary of the same tint, not a frame: the subtle gray
|
|
3411
|
+
hairline (`sys.color.outlineVariant`) on default's gray-tinted
|
|
3412
|
+
scrim; `primary` / `error` at 40% over transparent (the Skeleton
|
|
3413
|
+
fill recipe) on accent / destructive. Painted as an inset
|
|
3414
|
+
box-shadow, never a `border`, so toggling it can't change the
|
|
3415
|
+
banner's footprint — same idiom as Chip (see DESIGN.md → Border &
|
|
3416
|
+
Stroke). */
|
|
3417
|
+
.chorus-banner--outlined {
|
|
3418
|
+
box-shadow: inset 0 0 0 var(--sys-borderWidth-hairline) var(--banner-outline-color);
|
|
3267
3419
|
}
|
|
3268
3420
|
|
|
3269
3421
|
/* Leading icon slot — a 16 × 16 glyph (`sys.icon.md`) that paints in
|
|
@@ -3306,6 +3458,23 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3306
3458
|
gap: var(--sys-layout-stack-xs);
|
|
3307
3459
|
}
|
|
3308
3460
|
|
|
3461
|
+
/* Optional heading line above the body — label.md (14 / Semibold) in
|
|
3462
|
+
the container's foreground. Title↔body wants the tighter
|
|
3463
|
+
`sys.layout.stack.2xs` (4) so the pair reads as one passage, while
|
|
3464
|
+
the content column's gap stays `stack.xs` (8) for body↔action; the
|
|
3465
|
+
negative margin nets the column gap down to 4 for this pair only. */
|
|
3466
|
+
.chorus-banner__title {
|
|
3467
|
+
margin: 0;
|
|
3468
|
+
font-size: var(--sys-typo-label-md-size);
|
|
3469
|
+
line-height: var(--sys-typo-label-md-line);
|
|
3470
|
+
font-weight: var(--sys-typo-label-md-weight);
|
|
3471
|
+
color: inherit;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
.chorus-banner__title + .chorus-banner__body {
|
|
3475
|
+
margin-top: calc(var(--sys-layout-stack-2xs) - var(--sys-layout-stack-xs));
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3309
3478
|
.chorus-banner__body {
|
|
3310
3479
|
margin: 0;
|
|
3311
3480
|
font-size: var(--sys-typo-body-sm-size);
|
|
@@ -3332,6 +3501,60 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3332
3501
|
color: var(--sys-color-primary);
|
|
3333
3502
|
}
|
|
3334
3503
|
|
|
3504
|
+
/* neutral-body — keeps the `accent` fill but lays the Default
|
|
3505
|
+
appearance's neutral foreground over it: title + body re-tone to
|
|
3506
|
+
`onSurface` (quiet, high-legibility body copy) while the action
|
|
3507
|
+
steps to `primary`, exactly as on `default`. The accent tint still
|
|
3508
|
+
pulls the eye; the text just stops shouting in the primary family.
|
|
3509
|
+
Only meaningful combined with `--accent` (default is already
|
|
3510
|
+
`onSurface`; destructive must carry the warning tone through copy). */
|
|
3511
|
+
.chorus-banner--accent.chorus-banner--neutral-body {
|
|
3512
|
+
color: var(--sys-color-onSurface);
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
.chorus-banner--accent.chorus-banner--neutral-body .chorus-banner__action {
|
|
3516
|
+
color: var(--sys-color-primary);
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
/* Trailing icon slot — a 16 × 16 glyph (`sys.icon.md`) at the trailing
|
|
3520
|
+
edge, vertically centered against the whole block (`align-self:
|
|
3521
|
+
center` overrides the container's flex-start). Paints in the
|
|
3522
|
+
banner's foreground (`currentColor`); typically a forward affordance
|
|
3523
|
+
(e.g. ForwardCircleFillIcon) signaling the aside leads somewhere. */
|
|
3524
|
+
.chorus-banner__trailing-icon {
|
|
3525
|
+
flex: 0 0 var(--sys-icon-md);
|
|
3526
|
+
align-self: center;
|
|
3527
|
+
display: inline-flex;
|
|
3528
|
+
align-items: center;
|
|
3529
|
+
justify-content: center;
|
|
3530
|
+
color: currentColor;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
.chorus-banner__trailing-icon img,
|
|
3534
|
+
.chorus-banner__trailing-icon svg {
|
|
3535
|
+
width: var(--sys-icon-md);
|
|
3536
|
+
height: var(--sys-icon-md);
|
|
3537
|
+
display: block;
|
|
3538
|
+
color: currentColor;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
/* Trailing action slot — hosts an inline Text Button (`<Button
|
|
3542
|
+
variant="text">`) at the trailing edge, vertically centered against
|
|
3543
|
+
the whole block (`align-self: center` overrides the container's
|
|
3544
|
+
flex-start) and footprint-preserving (`flex: 0 0 auto`). The Button
|
|
3545
|
+
owns its own size + appearance per the button/text spec; by default
|
|
3546
|
+
pick the appearance whose color family matches the banner fill
|
|
3547
|
+
(accent → accent, default → default, destructive → destructive
|
|
3548
|
+
flavor) so the commit reads as part of the tinted block. Unlike the
|
|
3549
|
+
trailing icon it is a real interactive control, so it is not
|
|
3550
|
+
aria-hidden. The Text Button's optical-alignment margin lets its
|
|
3551
|
+
visible label sit flush against the container's trailing padding. */
|
|
3552
|
+
.chorus-banner__trailing-action {
|
|
3553
|
+
flex: 0 0 auto;
|
|
3554
|
+
align-self: center;
|
|
3555
|
+
display: inline-flex;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3335
3558
|
/* ============================================================
|
|
3336
3559
|
Divider — section-break band between adjacent regions that
|
|
3337
3560
|
don't share an enclosing container. Single full-bleed block
|
|
@@ -3715,14 +3938,21 @@ a.chorus-metadata__name:focus-visible {
|
|
|
3715
3938
|
/* Hover / pressed re-tone the 1px stroke to `borderHover` (`outline`,
|
|
3716
3939
|
or `error` on the error appearance). `:active` also lights the
|
|
3717
3940
|
state-overlay `::before`. */
|
|
3718
|
-
|
|
3941
|
+
/* The trailing clear button is an independent nested action (it wipes the
|
|
3942
|
+
value, distinct from focusing / pressing the field) — marked
|
|
3943
|
+
`data-nested-action`. While the pointer hovers / presses it, the field's
|
|
3944
|
+
own hover stroke and pressed overlay are suppressed so the large field
|
|
3945
|
+
does NOT read as pressed at the same time the small "×" does. The Select
|
|
3946
|
+
chevron is NOT marked: it fires the same action as clicking the field
|
|
3947
|
+
(open), so it correctly lights the field. */
|
|
3719
3948
|
.chorus-field[data-force-state="hovered"],
|
|
3720
3949
|
.chorus-field[data-force-state="pressed"],
|
|
3721
|
-
.chorus-field:not(
|
|
3950
|
+
.chorus-field:hover:not(:has([data-nested-action]:hover)),
|
|
3951
|
+
.chorus-field:not(.is-disabled):active:not(:has([data-nested-action]:active)) {
|
|
3722
3952
|
box-shadow: inset 0 0 0 var(--field-border-width) var(--field-border-hover);
|
|
3723
3953
|
}
|
|
3724
3954
|
|
|
3725
|
-
.chorus-field:not(.is-disabled):active::before,
|
|
3955
|
+
.chorus-field:not(.is-disabled):active:not(:has([data-nested-action]:active))::before,
|
|
3726
3956
|
.chorus-field[data-force-state="pressed"]::before {
|
|
3727
3957
|
opacity: var(--field-overlay-pressed);
|
|
3728
3958
|
}
|
|
@@ -5225,6 +5455,34 @@ a.chorus-metadata__name:focus-visible {
|
|
|
5225
5455
|
transform: rotate(45deg);
|
|
5226
5456
|
}
|
|
5227
5457
|
|
|
5458
|
+
/* ============================================================
|
|
5459
|
+
Pagination — decorative dot-position indicator
|
|
5460
|
+
============================================================ */
|
|
5461
|
+
/* One 6px dot per page in an inline.sm row. An inline element —
|
|
5462
|
+
inline-flex, intrinsic width (no stretch, no self-centering); the
|
|
5463
|
+
host owns horizontal placement. Active dot paints `onSurface`, the
|
|
5464
|
+
rest `outlineVariant`. Non-interactive (`aria-hidden` on the root) —
|
|
5465
|
+
the host pager owns the active index and keyboard reach.
|
|
5466
|
+
See schema/components/pagination/pagination.md. */
|
|
5467
|
+
|
|
5468
|
+
.chorus-pagination {
|
|
5469
|
+
display: inline-flex;
|
|
5470
|
+
align-items: center;
|
|
5471
|
+
gap: var(--sys-layout-inline-sm);
|
|
5472
|
+
}
|
|
5473
|
+
|
|
5474
|
+
.chorus-pagination__dot {
|
|
5475
|
+
width: var(--ref-space-75);
|
|
5476
|
+
height: var(--ref-space-75);
|
|
5477
|
+
border-radius: var(--sys-radius-full);
|
|
5478
|
+
background: var(--sys-color-outlineVariant);
|
|
5479
|
+
display: block;
|
|
5480
|
+
}
|
|
5481
|
+
|
|
5482
|
+
.chorus-pagination__dot--active {
|
|
5483
|
+
background: var(--sys-color-onSurface);
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5228
5486
|
/* ============================================================
|
|
5229
5487
|
Progress — linear progress bar (determinate)
|
|
5230
5488
|
============================================================ */
|
|
@@ -5363,6 +5621,72 @@ a.chorus-metadata__name:focus-visible {
|
|
|
5363
5621
|
}
|
|
5364
5622
|
}
|
|
5365
5623
|
|
|
5624
|
+
/* ============================================================
|
|
5625
|
+
Spinner — indeterminate rotating-arc loading indicator
|
|
5626
|
+
============================================================ */
|
|
5627
|
+
/* A rotating ring for short, progress-unknown waits (< ~1s) on a
|
|
5628
|
+
neutral host surface. The arc paints `sys.color.primary` as the
|
|
5629
|
+
foreground motion over a faint `sys.color.scrimSubtle` track ring
|
|
5630
|
+
(inverse-tone ~8% tint) so the rotation reads on any surface tier.
|
|
5631
|
+
The ring is drawn with a conic gradient masked to an annulus — no
|
|
5632
|
+
`border:` (per the no-layout-strokes rule). Diameter rides the
|
|
5633
|
+
icon.* ladder via the `--spinner-diameter` plumbing var (sys.icon.lg
|
|
5634
|
+
medium / sys.icon.md small). Rotation modulates position, not
|
|
5635
|
+
luminance, and is suppressed under `prefers-reduced-motion: reduce`,
|
|
5636
|
+
leaving the full ring static as a quiet loading mark. */
|
|
5637
|
+
@keyframes chorus-spinner-rotate {
|
|
5638
|
+
to { transform: rotate(360deg); }
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5641
|
+
.chorus-spinner {
|
|
5642
|
+
display: inline-flex;
|
|
5643
|
+
align-items: center;
|
|
5644
|
+
gap: var(--spinner-gap);
|
|
5645
|
+
}
|
|
5646
|
+
|
|
5647
|
+
.chorus-spinner__arc {
|
|
5648
|
+
display: block;
|
|
5649
|
+
flex: none;
|
|
5650
|
+
width: var(--spinner-diameter);
|
|
5651
|
+
height: var(--spinner-diameter);
|
|
5652
|
+
border-radius: var(--sys-radius-full);
|
|
5653
|
+
/* Foreground arc sweeps from primary to transparent over the track. */
|
|
5654
|
+
background:
|
|
5655
|
+
conic-gradient(var(--sys-color-primary), color-mix(in srgb, var(--sys-color-primary) 0%, transparent) 75%, color-mix(in srgb, var(--sys-color-primary) 0%, transparent));
|
|
5656
|
+
/* Annulus mask: punch out the centre so it reads as a ring, 2px thick.
|
|
5657
|
+
The mask fill is alpha-only — the opaque keep-region of the mask is
|
|
5658
|
+
not a design color, so it is not a token binding. */
|
|
5659
|
+
/* chorus-token-exempt */
|
|
5660
|
+
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
|
|
5661
|
+
/* chorus-token-exempt */
|
|
5662
|
+
mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
|
|
5663
|
+
animation: chorus-spinner-rotate 0.8s linear infinite;
|
|
5664
|
+
}
|
|
5665
|
+
|
|
5666
|
+
/* Faint full track ring under the rotating arc, painted via box-shadow
|
|
5667
|
+
inset so the bare ring stays visible on any surface tier. */
|
|
5668
|
+
.chorus-spinner__arc::before {
|
|
5669
|
+
content: '';
|
|
5670
|
+
position: absolute;
|
|
5671
|
+
inset: 0;
|
|
5672
|
+
border-radius: inherit;
|
|
5673
|
+
box-shadow: inset 0 0 0 2px var(--sys-color-scrimSubtle);
|
|
5674
|
+
}
|
|
5675
|
+
|
|
5676
|
+
.chorus-spinner__arc {
|
|
5677
|
+
position: relative;
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
.chorus-spinner__label {
|
|
5681
|
+
color: var(--sys-color-onSurfaceVariant);
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5684
|
+
@media (prefers-reduced-motion: reduce) {
|
|
5685
|
+
.chorus-spinner__arc {
|
|
5686
|
+
animation: none;
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
|
|
5366
5690
|
/* ============================================================
|
|
5367
5691
|
Switch — binary active/inactive pill with translating thumb
|
|
5368
5692
|
============================================================ */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamblind-chorus/ui",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Chorus React components. Ships prebuilt ESM + CJS bundles (`dist/`) and a single `styles.css`; import `@teamblind-chorus/tokens/tokens.css` + `@teamblind-chorus/ui/styles.css` once at the app entry. The contract every component honors lives in schema/components/<family>/<sub>.spec.json; see schema/manifest.json for the inventory.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Teamblind, Inc.",
|
|
@@ -54,7 +54,6 @@
|
|
|
54
54
|
"./agents/usage.json": "./agents/usage.json",
|
|
55
55
|
"./agents/DESIGN.md": "./agents/DESIGN.md",
|
|
56
56
|
"./agents/LOVABLE.md": "./agents/LOVABLE.md",
|
|
57
|
-
"./agents/reconstruct.md": "./agents/reconstruct.md",
|
|
58
57
|
"./agents/images.md": "./agents/images.md",
|
|
59
58
|
"./agents/patterns/": "./agents/patterns/",
|
|
60
59
|
"./agents/components/": "./agents/components/",
|
package/agents/reconstruct.md
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Chorus reconstruction prompt
|
|
2
|
-
|
|
3
|
-
Paste the block below into a Chorus-aware agent (Lovable etc.) to rebuild ad-hoc,
|
|
4
|
-
agent-invented UI as pure Chorus. Strongest with `LOVABLE.md` loaded as the system
|
|
5
|
-
prompt — this block is a thin **driver**; the full rules live in `LOVABLE.md` and the
|
|
6
|
-
guides it tells the agent to read, so the prompt stays short (lower token cost).
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
```text
|
|
11
|
-
Chorus reconstruction pass — rebuild every ad-hoc component as pure Chorus.
|
|
12
|
-
|
|
13
|
-
GOAL: every hand-built component (raw div/button, Tailwind, shadcn, inline styles, hex)
|
|
14
|
-
becomes pure Chorus. No mixed renders — a screen is 100% Chorus or not done. The old UI
|
|
15
|
-
is the source of a migration to @teamblind-chorus/ui + @teamblind-chorus/tokens; don't preserve or
|
|
16
|
-
"match" it — the old style is the bug.
|
|
17
|
-
|
|
18
|
-
FIRST read node_modules/@teamblind-chorus/ui/agents/: catalog.md, manifest.json, compose.md,
|
|
19
|
-
anti-patterns.md. The component name is not the contract — bindings are in
|
|
20
|
-
components/<family>/<sub>.spec.json.
|
|
21
|
-
|
|
22
|
-
DO, in order:
|
|
23
|
-
1. INVENTORY every non-Chorus component in src/ (one line each: file → what it is).
|
|
24
|
-
2. MAP each intent via catalog.md to a family + sub. If none fits, climb the ladder
|
|
25
|
-
(recompose slots → LEGO-combine → new primitive with every value a var(--sys-*)),
|
|
26
|
-
then flag a one-line "Chorus gap". Never fall back to raw HTML/Tailwind/shadcn.
|
|
27
|
-
3. REBUILD with the real import from @teamblind-chorus/ui (icons from @teamblind-chorus/ui/icons),
|
|
28
|
-
honoring the spec; then delete the old component + its CSS.
|
|
29
|
-
|
|
30
|
-
NON-NEGOTIABLE (else discard + regenerate):
|
|
31
|
-
- Tokens only: no hex, no Tailwind colors, no off-scale px; card edge = inset shadow,
|
|
32
|
-
never border:.
|
|
33
|
-
- Typography = className="sys-typo-<role>-<rung>" (NO `font: var(--sys-typo-*)` token).
|
|
34
|
-
- Compound children: <Tabs> needs <Tab>; List/SuggestionList/AvatarRail take an items
|
|
35
|
-
array — never bare text.
|
|
36
|
-
- One gutter at the shell; full-bleed children (Tabs/Feed/List/Carousel/bars) never
|
|
37
|
-
wrapped in a padded div. Floating action = <Button variant="fab">, not a pinned
|
|
38
|
-
standard button. Fixed bars via .page-shell (height:100dvh + <main> overflow-y:auto).
|
|
39
|
-
|
|
40
|
-
VERIFY: eslint.config.js extends @teamblind-chorus/ui/eslint — fix every chorus/* error (never
|
|
41
|
-
suppress); run the §E pre-flight checklist + rail self-diagnostic (anti-patterns.md).
|
|
42
|
-
|
|
43
|
-
SCOPE (optional): "SCOPE: <area/route globs>" limits the pass to that boundary —
|
|
44
|
-
inventory, rebuild, and lint only inside it; outside files are reported, never edited
|
|
45
|
-
(boundary rules: agents/scoped-adoption.md).
|
|
46
|
-
|
|
47
|
-
DELIVER: a before→after table (component → Chorus family/sub, or "gap") and screens with
|
|
48
|
-
zero mixed renders. If scope is large, reconstruct the entry screen first, list the rest.
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
The ESLint preset (`@teamblind-chorus/ui/eslint`, wired in the VERIFY step) is the safety net:
|
|
54
|
-
even if the model drops a rule mid-session, `chorus/*` errors fail the build and its own
|
|
55
|
-
fix loop catches the drift.
|