@teamblind-chorus/ui 1.2.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 +4 -4
- package/agents/components/avatar-rail/avatar-rail.md +2 -4
- package/agents/components/avatar-rail/avatar-rail.spec.json +10 -14
- 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.md +16 -18
- package/agents/components/banner/banner.spec.json +14 -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 +8 -10
- package/agents/components/button/fab.md +7 -9
- package/agents/components/button/fab.spec.json +10 -12
- package/agents/components/button/group.spec.json +4 -4
- package/agents/components/button/icon.md +21 -23
- package/agents/components/button/icon.spec.json +12 -14
- package/agents/components/button/standard.md +40 -42
- package/agents/components/button/standard.spec.json +20 -22
- package/agents/components/button/text.md +21 -23
- package/agents/components/button/text.spec.json +13 -15
- package/agents/components/button/toggle.md +7 -9
- package/agents/components/button/toggle.spec.json +10 -12
- 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 +17 -13
- package/agents/components/chip/tag.md +22 -24
- package/agents/components/chip/tag.spec.json +19 -15
- 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.md +9 -9
- package/agents/components/empty-state/empty-state.spec.json +14 -14
- 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 +34 -33
- package/agents/components/form-field/search.md +2 -4
- package/agents/components/form-field/search.spec.json +19 -18
- package/agents/components/form-field/select.md +18 -20
- package/agents/components/form-field/select.spec.json +30 -29
- package/agents/components/form-field/textarea.md +3 -5
- package/agents/components/form-field/textarea.spec.json +32 -31
- 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 +20 -20
- package/agents/components/list/entry.md +59 -81
- package/agents/components/list/entry.spec.json +20 -23
- package/agents/components/list/list.md +2 -2
- package/agents/components/list/radio.md +13 -20
- package/agents/components/list/radio.spec.json +16 -20
- package/agents/components/list/standard.md +50 -72
- package/agents/components/list/standard.spec.json +18 -21
- 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 +19 -19
- 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/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 +1 -1
- package/agents/components/spinner/spinner.md +8 -10
- package/agents/components/spinner/spinner.spec.json +9 -9
- 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 +17 -18
- package/agents/components/tab-bar/tab-bar.md +9 -11
- package/agents/components/tab-bar/tab-bar.spec.json +25 -27
- package/agents/components/tabs/rounded.md +6 -8
- package/agents/components/tabs/rounded.spec.json +17 -15
- 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 +14 -16
- 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/tokens.usage.json +71 -226
- package/dist/index.cjs +212 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +212 -223
- package/dist/index.js.map +1 -1
- package/dist/styles.css +386 -387
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../spec.schema.json",
|
|
3
3
|
"name": "Divider",
|
|
4
4
|
"family": "divider",
|
|
5
|
-
"description": "Section-break band — a single full-bleed block painted with `sys.color.
|
|
5
|
+
"description": "Section-break band — a single full-bleed block painted with `sys.color.border.default` (~8% inverse-tone overlay — black scrim in light mode, white scrim in dark) at a fixed block thickness of `sys.layout.stack.xs` (8). The translucent fill stays visible on every host surface tier, so the band reads as a region boundary whether it sits on plain page surface, an elevated container, a hero band, or a coloured card — without retoning per-host. No appearance axis, no thickness prop, no orientation prop: Divider ships one canonical band. The host paints no margin / padding / gap around it — the 8 of border.default IS the breathing.",
|
|
6
6
|
"element": "hr",
|
|
7
7
|
"props": {
|
|
8
8
|
"aria-hidden": {
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
"slots": {
|
|
20
20
|
"container": {
|
|
21
21
|
"required": true,
|
|
22
|
-
"description": "The tonal band itself. `sys.color.
|
|
22
|
+
"description": "The tonal band itself. `sys.color.border.default` fill, `sys.layout.stack.xs` (8) block thickness, full inline width, no inline padding, no corner radius, no border. The native `<hr>` element with all browser defaults reset (no margin, no border, no background-image).",
|
|
23
23
|
"intrinsic": true
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"sizing": {
|
|
27
|
-
"background": "sys.color.
|
|
27
|
+
"background": "sys.color.border.default",
|
|
28
28
|
"thickness": "sys.layout.stack.xs",
|
|
29
29
|
"inlineWidth": "100%",
|
|
30
30
|
"border": "none",
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
"margin": "0"
|
|
33
33
|
},
|
|
34
34
|
"appearance": {
|
|
35
|
-
"background": "sys.color.
|
|
36
|
-
"note": "The only fill tone — Divider has no emphasis axis. Painted as a translucent ~8% inverse-tone overlay (black in light, white in dark) so the band stays visible on every host surface tier without colliding with a fixed neutral step. Mirrors the `
|
|
35
|
+
"background": "sys.color.border.default",
|
|
36
|
+
"note": "The only fill tone — Divider has no emphasis axis. Painted as a translucent ~8% inverse-tone overlay (black in light, white in dark) so the band stays visible on every host surface tier without colliding with a fixed neutral step. Mirrors the `border.default` contract used by Banner default, Chip / Tag default, Progress track, StatusTag neutral, and Skeleton."
|
|
37
37
|
},
|
|
38
38
|
"behavior": {
|
|
39
39
|
"ariaHidden": "Defaults to `aria-hidden=\"true\"` because the band is decorative chrome — screen-reader users navigate by headings and landmarks, not visual breaks.",
|
|
40
40
|
"fullBleed": "Inline width is 100% of the host's content box. Divider claims the page rail in full and must not be wrapped in a padding-inline container — the band reading edge-to-edge IS what makes it a region boundary.",
|
|
41
|
-
"thicknessFixed": "Block thickness is fixed at `sys.layout.stack.xs` (8) and is not a prop. Heavier bands would compete with content; thinner bands collapse to a hairline (use the host's own `
|
|
41
|
+
"thicknessFixed": "Block thickness is fixed at `sys.layout.stack.xs` (8) and is not a prop. Heavier bands would compete with content; thinner bands collapse to a hairline (use the host's own `border.default` border for row-level breaks, not Divider)."
|
|
42
42
|
},
|
|
43
43
|
"forbidden": [
|
|
44
|
-
"divider used as a row separator inside a List — the list's own `divider={true}` paints those as a hairline `
|
|
45
|
-
"divider painted with `
|
|
44
|
+
"divider used as a row separator inside a List — the list's own `divider={true}` paints those as a hairline `border.default` rule",
|
|
45
|
+
"divider painted with `border.default` / a fixed neutral step instead of `sys.color.border.default` — fixed steps collide with the surface ladder and the divider stops reading on raised surfaces",
|
|
46
46
|
"divider wrapped in a padding-inline div or `style={{ marginInline }}` — Divider is full-bleed and must touch the page edge to read as a region boundary",
|
|
47
47
|
"divider given outer `margin-block` / surrounding `padding-block` to fake more breathing — the 8 thickness is the breathing, and the parent column never pays a gap around Divider",
|
|
48
48
|
"divider painted with a corner radius or stroke — the band is a paint, not a card",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# EmptyState
|
|
2
2
|
|
|
3
|
+
> 🇰🇷 한국어: [`i18n/ko/schema/components/empty-state/empty-state.md`](../../../i18n/ko/schema/components/empty-state/empty-state.md)
|
|
4
|
+
|
|
3
5
|
The centered composition a surface paints when it holds no data yet — an optional monochrome illustration, a required headline, optional body copy, and an optional primary CTA that performs the one action that fills the surface.
|
|
4
6
|
|
|
5
7
|
**Reach for this when** a feed, list, inbox, or search result would otherwise paint blank — a fresh account's empty feed, a search returning nothing, a notifications surface with nothing unread. **Skip when** the data is still loading (use [Skeleton](../skeleton/skeleton.md) — an in-flight tonal placeholder) or when the message is a transient confirmation (use [Toast](../toast/toast.md)).
|
|
@@ -24,9 +26,7 @@ import { ChatIcon } from '@teamblind-chorus/ui/icons';
|
|
|
24
26
|
/>
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
### Without illustration
|
|
29
|
+
## No illustration
|
|
30
30
|
|
|
31
31
|
The illustration is optional — omit it for a tighter, text-led zero state where a glyph would add nothing. The headline still leads; the body and CTA follow the same stack rhythm.
|
|
32
32
|
|
|
@@ -45,9 +45,9 @@ import { EmptyState } from '@teamblind-chorus/ui';
|
|
|
45
45
|
## Slots
|
|
46
46
|
|
|
47
47
|
- **container** — centered flex column holding the whole composition. `align-items: center`, `text-align: center`. No surface fill of its own. `role="status"` so the empty state is announced without yanking focus.
|
|
48
|
-
- **illustration** *(optional)* — centered glyph / illustration above the headline. `ref.space.600` (48) box, painted monochrome in `sys.color.
|
|
49
|
-
- **headline** — required lead line. `sys.typo.heading.sm` / `sys.color.
|
|
50
|
-
- **body** *(optional)* — supporting line below the headline. `sys.typo.body.sm` / `sys.color.
|
|
48
|
+
- **illustration** *(optional)* — centered glyph / illustration above the headline. `ref.space.600` (48) box, painted monochrome in `sys.color.text.subtle`.
|
|
49
|
+
- **headline** — required lead line. `sys.typo.heading.sm` / `sys.color.text.default`.
|
|
50
|
+
- **body** *(optional)* — supporting line below the headline. `sys.typo.body.sm` / `sys.color.text.subtle`.
|
|
51
51
|
- **action** *(optional)* — the primary CTA, composed from the `action` object as a default-size primary [Button](../button/button.md). There is no free `cta` slot.
|
|
52
52
|
|
|
53
53
|
## Anatomy
|
|
@@ -55,9 +55,9 @@ import { EmptyState } from '@teamblind-chorus/ui';
|
|
|
55
55
|
| Slot | Token bindings |
|
|
56
56
|
|--------------|----------------|
|
|
57
57
|
| container | Centered flex column, `align-items: center`, `text-align: center`, no fill; `role="status"` |
|
|
58
|
-
| illustration | `ref.space.600` (48 × 48) box, `sys.color.
|
|
59
|
-
| headline | `sys.typo.heading.sm`, `sys.color.
|
|
60
|
-
| body | `sys.typo.body.sm`, `sys.color.
|
|
58
|
+
| illustration | `ref.space.600` (48 × 48) box, `sys.color.text.subtle` (`currentColor`), `sys.layout.stack.sm` (12) below it to the headline |
|
|
59
|
+
| headline | `sys.typo.heading.sm`, `sys.color.text.default` |
|
|
60
|
+
| body | `sys.typo.body.sm`, `sys.color.text.subtle`, `sys.layout.stack.2xs` (4) above it from the headline |
|
|
61
61
|
| action | Default-size primary [Button](../button/button.md), `sys.layout.stack.md` (16) above it from the body |
|
|
62
62
|
|
|
63
63
|
## Behavior
|
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
"illustration": {
|
|
9
9
|
"type": "node",
|
|
10
10
|
"optional": true,
|
|
11
|
-
"description": "Optional leading glyph or illustration, centered above the headline. Sized to a `ref.space.600` (48) box — larger than `sys.icon.lg` (24), realizing DESIGN.md's `icon.xl` or larger intent (no `icon.xl` icon-size rung exists; the icon scale stops at `lg`). Painted in `sys.color.
|
|
11
|
+
"description": "Optional leading glyph or illustration, centered above the headline. Sized to a `ref.space.600` (48) box — larger than `sys.icon.lg` (24), realizing DESIGN.md's `icon.xl` or larger intent (no `icon.xl` icon-size rung exists; the icon scale stops at `lg`). Painted in `sys.color.text.subtle` via `currentColor` so it reads as quiet, monochrome chrome — illustrations stay monochrome unless they carry deliberate brand-moment intent. Separated from the headline by `sys.layout.stack.sm` (12)."
|
|
12
12
|
},
|
|
13
13
|
"headline": {
|
|
14
14
|
"type": "node",
|
|
15
15
|
"required": true,
|
|
16
|
-
"description": "The required lead line. `sys.typo.heading.sm` in `sys.color.
|
|
16
|
+
"description": "The required lead line. `sys.typo.heading.sm` in `sys.color.text.default`. Names what the surface is for / why it is empty in one short line (e.g. 'No posts yet')."
|
|
17
17
|
},
|
|
18
18
|
"body": {
|
|
19
19
|
"type": "node",
|
|
20
20
|
"optional": true,
|
|
21
|
-
"description": "Optional supporting line below the headline. `sys.typo.body.sm` in `sys.color.
|
|
21
|
+
"description": "Optional supporting line below the headline. `sys.typo.body.sm` in `sys.color.text.subtle`, separated from the headline by `sys.layout.stack.2xs` (4). One sentence — the second of the three lines (e.g. 'Conversations you start or join will appear here')."
|
|
22
22
|
},
|
|
23
23
|
"action": {
|
|
24
24
|
"type": "object",
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
},
|
|
35
35
|
"illustration": {
|
|
36
36
|
"required": false,
|
|
37
|
-
"description": "Optional centered glyph / illustration above the headline. `ref.space.600` (48) box, painted in `sys.color.
|
|
37
|
+
"description": "Optional centered glyph / illustration above the headline. `ref.space.600` (48) box, painted in `sys.color.text.subtle` (monochrome). `sys.layout.stack.sm` (12) below it to the headline.",
|
|
38
38
|
"accepts": ["icon"]
|
|
39
39
|
},
|
|
40
40
|
"headline": {
|
|
41
41
|
"required": true,
|
|
42
|
-
"description": "Required headline line. `sys.typo.heading.sm` / `sys.color.
|
|
42
|
+
"description": "Required headline line. `sys.typo.heading.sm` / `sys.color.text.default`.",
|
|
43
43
|
"accepts": ["text"]
|
|
44
44
|
},
|
|
45
45
|
"body": {
|
|
46
46
|
"required": false,
|
|
47
|
-
"description": "Optional supporting line. `sys.typo.body.sm` / `sys.color.
|
|
47
|
+
"description": "Optional supporting line. `sys.typo.body.sm` / `sys.color.text.subtle`. `sys.layout.stack.2xs` (4) above it from the headline.",
|
|
48
48
|
"accepts": ["text"]
|
|
49
49
|
},
|
|
50
50
|
"action": {
|
|
@@ -56,20 +56,20 @@
|
|
|
56
56
|
"sizing": {
|
|
57
57
|
"containerAlign": "center",
|
|
58
58
|
"illustrationSize": "ref.space.600",
|
|
59
|
-
"illustrationColor": "sys.color.
|
|
59
|
+
"illustrationColor": "sys.color.text.subtle",
|
|
60
60
|
"illustrationGap": "sys.layout.stack.sm",
|
|
61
61
|
"headlineTypo": "sys.typo.heading.sm",
|
|
62
|
-
"headlineColor": "sys.color.
|
|
62
|
+
"headlineColor": "sys.color.text.default",
|
|
63
63
|
"bodyTypo": "sys.typo.body.sm",
|
|
64
|
-
"bodyColor": "sys.color.
|
|
64
|
+
"bodyColor": "sys.color.text.subtle",
|
|
65
65
|
"bodyGap": "sys.layout.stack.2xs",
|
|
66
66
|
"actionGap": "sys.layout.stack.md"
|
|
67
67
|
},
|
|
68
68
|
"appearance": {
|
|
69
|
-
"illustration": "sys.color.
|
|
70
|
-
"headline": "sys.color.
|
|
71
|
-
"body": "sys.color.
|
|
72
|
-
"note": "No emphasis axis — EmptyState has one quiet appearance. The illustration and body sit in the muted `onSurfaceVariant` tone; the headline steps up to `onSurface`. The only chromatic emphasis is the CTA, which is a primary `Button` (its own `sys.color.primary` fill) so the single fill-the-surface action reads as primary both in intent and visually."
|
|
69
|
+
"illustration": "sys.color.text.subtle",
|
|
70
|
+
"headline": "sys.color.text.default",
|
|
71
|
+
"body": "sys.color.text.subtle",
|
|
72
|
+
"note": "No emphasis axis — EmptyState has one quiet appearance. The illustration and body sit in the muted `onSurfaceVariant` tone; the headline steps up to `onSurface`. The only chromatic emphasis is the CTA, which is a primary `Button` (its own `sys.color.background.primary` fill) so the single fill-the-surface action reads as primary both in intent and visually."
|
|
73
73
|
},
|
|
74
74
|
"behavior": {
|
|
75
75
|
"centered": "The whole composition is centered (block + inline) inside the host surface that would otherwise hold the data. EmptyState owns no surface fill — the host supplies the surface tier and the bounding box; EmptyState only centers its column inside it.",
|
|
@@ -82,6 +82,6 @@
|
|
|
82
82
|
"more than three lines of copy — headline + body must read as at most three lines total (what the surface is for · why it is empty · the one action that fills it); longer prose belongs in a Banner or a help surface",
|
|
83
83
|
"the CTA rendered as anything but a default-size primary Button — the fill action is the surface's primary action and must read as primary; do not down-rank it to a text or outlined button, and do not render two competing CTAs",
|
|
84
84
|
"EmptyState used as a loading placeholder for data that is still arriving — that is `skeleton` (an in-flight tonal block). EmptyState is the durable no-data surface, not a transient one",
|
|
85
|
-
"the illustration painted in a chromatic tone for non-brand empty states — it stays monochrome `sys.color.
|
|
85
|
+
"the illustration painted in a chromatic tone for non-brand empty states — it stays monochrome `sys.color.text.subtle` unless it deliberately carries a brand moment"
|
|
86
86
|
]
|
|
87
87
|
}
|
|
@@ -34,9 +34,7 @@ import { FeedAd } from '@teamblind-chorus/ui';
|
|
|
34
34
|
/>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
### With dismiss
|
|
37
|
+
## Dismiss
|
|
40
38
|
|
|
41
39
|
Wire `onDismiss` to render the trailing close icon for placements that give the reader a dismiss path.
|
|
42
40
|
|
|
@@ -80,7 +78,7 @@ import { FeedAd } from '@teamblind-chorus/ui';
|
|
|
80
78
|
| title → body | 8px vertical gap (`sys.layout.stack.xs`) |
|
|
81
79
|
| title | `heading.sm` (16 / Semibold), `onSurface`, single-line truncate |
|
|
82
80
|
| body | 14 / Regular, `onSurfaceVariant`, two-line clamp |
|
|
83
|
-
| bottom divider | `sys.borderWidth.hairline` × `sys.color.
|
|
81
|
+
| bottom divider | `sys.borderWidth.hairline` × `sys.color.border.default` — `border-bottom` on the card so a sponsored placement drops into a Post stream without breaking the inter-card seam rhythm. |
|
|
84
82
|
| cta-group | `radius.md` (8px) clip wrapping media + CTA, `overflow: hidden`, no internal gap |
|
|
85
83
|
| media | Full inline width, 16:10 aspect ratio, no own radius (the group clips). Runtime fallback when `src` is missing or fails to load: `background-image: url('/placeholder.png')` over a `surfaceContainerHigh` base. |
|
|
86
84
|
| cta | [Standard Button](../button/standard.md) — `fullWidth`, `medium`, `primary` by default, **`border-radius: 0`** so the squared bottom edge meets the group's clip. `cta.color` overrides the surface fill with a free-form Hex. |
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
}
|
|
127
127
|
},
|
|
128
128
|
"sizing": {
|
|
129
|
-
"containerFill": "sys.color.surface",
|
|
129
|
+
"containerFill": "sys.color.surface.default",
|
|
130
130
|
"containerPaddingBlock": "sys.layout.container.lg",
|
|
131
131
|
"containerPaddingInline": "sys.layout.container.md",
|
|
132
132
|
"containerRadius": "sys.radius.md",
|
|
@@ -134,24 +134,24 @@
|
|
|
134
134
|
"brandRowGap": "sys.layout.inline.md",
|
|
135
135
|
"brandAvatarSize": 32,
|
|
136
136
|
"brandNameTypo": "sys.typo.label.md",
|
|
137
|
-
"brandNameColor": "sys.color.
|
|
137
|
+
"brandNameColor": "sys.color.text.default",
|
|
138
138
|
"brandSubtitleTypo": "sys.typo.label.sm",
|
|
139
|
-
"brandSubtitleColor": "sys.color.
|
|
139
|
+
"brandSubtitleColor": "sys.color.text.subtle",
|
|
140
140
|
"brandSubtitleDefault": "Sponsored",
|
|
141
141
|
"dismissIconSize": "sys.icon.md",
|
|
142
|
-
"dismissColor": "sys.color.
|
|
142
|
+
"dismissColor": "sys.color.text.subtle",
|
|
143
143
|
"dismissHitArea": 24,
|
|
144
144
|
"titleTypo": "sys.typo.heading.sm",
|
|
145
|
-
"titleColor": "sys.color.
|
|
145
|
+
"titleColor": "sys.color.text.default",
|
|
146
146
|
"containerBottomDividerWidth": "sys.borderWidth.hairline",
|
|
147
|
-
"containerBottomDividerColor": "sys.color.
|
|
147
|
+
"containerBottomDividerColor": "sys.color.border.default",
|
|
148
148
|
"bodyTypo": "sys.typo.body.sm",
|
|
149
|
-
"bodyColor": "sys.color.
|
|
149
|
+
"bodyColor": "sys.color.text.subtle",
|
|
150
150
|
"bodyLineClamp": 2,
|
|
151
151
|
"titleBodyGap": "sys.layout.stack.xs",
|
|
152
152
|
"ctaGroupRadius": "sys.radius.md",
|
|
153
153
|
"mediaAspectRatio": "16 / 10",
|
|
154
|
-
"mediaFallbackFill": "sys.color.
|
|
154
|
+
"mediaFallbackFill": "sys.color.surface.sunken",
|
|
155
155
|
"mediaFallbackImage": "/placeholder.png",
|
|
156
156
|
"mediaFallbackImageRendering": "background-image, cover, center — sits under the runtime <img>; visible only when the inline image is missing or fails to load, so the hero block still resolves to an image rather than an empty surface tone.",
|
|
157
157
|
"ctaAppearance": "primary",
|
|
@@ -168,13 +168,13 @@
|
|
|
168
168
|
"composition": "inward"
|
|
169
169
|
},
|
|
170
170
|
"behavior": {
|
|
171
|
-
"brandNameRequired": "Every FeedAd MUST carry an explicit `brand.name`.
|
|
171
|
+
"brandNameRequired": "Every FeedAd MUST carry an explicit `brand.name`. consumers and mock generators must never drop it — the row is the ad's legal attribution surface, and a placement without a brand name is not a valid FeedAd.",
|
|
172
172
|
"mediaRequired": "Every FeedAd MUST carry a hero `media` block with a non-empty `src`. The `surfaceContainerHigh` fallback is a runtime safety net for load failures, not a design-time omission — at scaffold time, use the bundled `/placeholder.png` rather than omitting `media`.",
|
|
173
173
|
"brandSubtitleAlwaysPresent": "The brand subtitle defaults to `Sponsored` so every placement reads as sponsored content. Consumers may override the copy but cannot omit the row.",
|
|
174
174
|
"ctaGroupBondsMediaAndCta": "Hero media and the CTA share a `radius.md` clip with no internal gap; the CTA's own `border-radius` is zeroed so its squared bottom edge meets the group's outer round.",
|
|
175
175
|
"ctaColorIsFreeFormHex": "`cta.color` accepts any Hex string supplied by the ad client. Only the button surface fill and border swap — typography, size, and full-width geometry stay on the Standard Button tokens.",
|
|
176
176
|
"slotOmissionCollapses": "`title`, `body`, and `dismiss` are opt-in — when any is absent the layout reflows without reserved whitespace. `brand.name` and `media` are required and may NOT be omitted.",
|
|
177
|
-
"containerBottomDivider": "Each Ad card carries the same hairline bottom divider as a Post (`sys.borderWidth.hairline` × `sys.color.
|
|
177
|
+
"containerBottomDivider": "Each Ad card carries the same hairline bottom divider as a Post (`sys.borderWidth.hairline` × `sys.color.border.default`) so a sponsored placement drops into a Post stream without breaking the inter-card seam rhythm.",
|
|
178
178
|
"truncationNotWrap": "`title` truncates; `body` clamps to two lines.",
|
|
179
179
|
"dismissOptIn": "Default placements omit the trailing X; it only renders when `onDismiss` is wired.",
|
|
180
180
|
"ctaCommitsAlone": "FeedAd has no engagement row. The cta-group, when present, carries a single full-width Standard Button — never two CTAs."
|
|
@@ -31,9 +31,7 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
31
31
|
/>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
### With flag
|
|
34
|
+
## Flag
|
|
37
35
|
|
|
38
36
|
Optional single-word editorial label (`HOT`, `NEW`, `PINNED`). Use sparingly.
|
|
39
37
|
|
|
@@ -57,9 +55,9 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
57
55
|
/>
|
|
58
56
|
```
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
## Poll
|
|
61
59
|
|
|
62
|
-
Inline poll module between body and mention/footer. Leading `PollFillIcon` + label paint in `sys.color.brand`; label constrained to the literal `Poll`.
|
|
60
|
+
Inline poll module between body and mention/footer. Leading `PollFillIcon` + label paint in `sys.color.text.brand`; label constrained to the literal `Poll`.
|
|
63
61
|
|
|
64
62
|
```preview
|
|
65
63
|
feed/post-with-poll
|
|
@@ -81,9 +79,9 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
81
79
|
/>
|
|
82
80
|
```
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
## Offer evaluation
|
|
85
83
|
|
|
86
|
-
Same chrome as `poll`; leading glyph swaps to `CompensationFillIcon` and both glyph and label paint in `sys.color.success`. Label constrained to the literal `Offer`.
|
|
84
|
+
Same chrome as `poll`; leading glyph swaps to `CompensationFillIcon` and both glyph and label paint in `sys.color.text.success`. Label constrained to the literal `Offer`.
|
|
87
85
|
|
|
88
86
|
```preview
|
|
89
87
|
feed/post-with-offer
|
|
@@ -105,7 +103,7 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
105
103
|
/>
|
|
106
104
|
```
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
## Citation
|
|
109
107
|
|
|
110
108
|
Citation module naming an external source. Hero image flush-left at 120px wide; title clamps to two lines.
|
|
111
109
|
|
|
@@ -128,9 +126,37 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
128
126
|
/>
|
|
129
127
|
```
|
|
130
128
|
|
|
131
|
-
|
|
129
|
+
## Full composition
|
|
130
|
+
|
|
131
|
+
Every optional slot present.
|
|
132
|
+
|
|
133
|
+
```preview
|
|
134
|
+
feed/post-full
|
|
135
|
+
---
|
|
136
|
+
import { Feed } from '@teamblind-chorus/ui';
|
|
137
|
+
|
|
138
|
+
<Feed
|
|
139
|
+
flag="HOT"
|
|
140
|
+
channel="Channel"
|
|
141
|
+
timestamp="Now"
|
|
142
|
+
followAction
|
|
143
|
+
meta={['Company', 'Job Function', 'Username']}
|
|
144
|
+
title="Title"
|
|
145
|
+
body="Body textBody textBody textBody textBody textBody textBody…"
|
|
146
|
+
thumbnail={{ alt: 'Cover', stacked: true }}
|
|
147
|
+
poll={{ label: 'Poll', participants: 'Number' }}
|
|
148
|
+
citation={{
|
|
149
|
+
title: 'Keep subject area text on two lines or less.',
|
|
150
|
+
source: 'Source',
|
|
151
|
+
}}
|
|
152
|
+
mention="@Mention"
|
|
153
|
+
engagement={{ likes: 999, comments: 999, views: 999 }}
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Group
|
|
132
158
|
|
|
133
|
-
Three (or more) Post cards bundled inside `<FeedGroup>` for thread-grouped or topic-bundled feeds. The wrapper adds no chrome — inner Posts keep their padding and divider; the wrapper carries intent (`role="region"` + optional `aria-label`).
|
|
159
|
+
Three (or more) Post cards bundled inside `<FeedGroup>` for thread-grouped or topic-bundled feeds — the realistic composition the single-variant sections below each isolate one facet of. The wrapper adds no chrome — inner Posts keep their padding and divider; the wrapper carries intent (`role="region"` + optional `aria-label`). The last Post's bottom divider closes the bundle the same way a standalone Post would.
|
|
134
160
|
|
|
135
161
|
```preview
|
|
136
162
|
feed/post-group
|
|
@@ -162,34 +188,6 @@ import { Feed, FeedGroup } from '@teamblind-chorus/ui';
|
|
|
162
188
|
</FeedGroup>
|
|
163
189
|
```
|
|
164
190
|
|
|
165
|
-
### Full composition
|
|
166
|
-
|
|
167
|
-
Every optional slot present.
|
|
168
|
-
|
|
169
|
-
```preview
|
|
170
|
-
feed/post-full
|
|
171
|
-
---
|
|
172
|
-
import { Feed } from '@teamblind-chorus/ui';
|
|
173
|
-
|
|
174
|
-
<Feed
|
|
175
|
-
flag="HOT"
|
|
176
|
-
channel="Channel"
|
|
177
|
-
timestamp="Now"
|
|
178
|
-
followAction
|
|
179
|
-
meta={['Company', 'Job Function', 'Username']}
|
|
180
|
-
title="Title"
|
|
181
|
-
body="Body textBody textBody textBody textBody textBody textBody…"
|
|
182
|
-
thumbnail={{ alt: 'Cover', stacked: true }}
|
|
183
|
-
poll={{ label: 'Poll', participants: 'Number' }}
|
|
184
|
-
citation={{
|
|
185
|
-
title: 'Keep subject area text on two lines or less.',
|
|
186
|
-
source: 'Source',
|
|
187
|
-
}}
|
|
188
|
-
mention="@Mention"
|
|
189
|
-
engagement={{ likes: 999, comments: 999, views: 999 }}
|
|
190
|
-
/>
|
|
191
|
-
```
|
|
192
|
-
|
|
193
191
|
## Slots
|
|
194
192
|
|
|
195
193
|
- **flag** *(optional)* — single-word editorial label.
|
|
@@ -202,7 +200,7 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
202
200
|
- **citation** *(optional)* — inline link-share card with leading hero and source mark.
|
|
203
201
|
- **mention** *(optional)* — tap-anywhere `@Mention` line under the body.
|
|
204
202
|
- **engagement** — footer row of `xsmall` [Text Buttons](../button/text.md) — Likes / Comments commit, Views non-interactive.
|
|
205
|
-
- **bottom divider** *(intrinsic)* — hairline `
|
|
203
|
+
- **bottom divider** *(intrinsic)* — hairline `border.default` seam at the card's bottom edge.
|
|
206
204
|
|
|
207
205
|
## Anatomy
|
|
208
206
|
|
|
@@ -211,7 +209,7 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
211
209
|
| container | `surface` fill, `sys.layout.container.lg` (24/32) block × `sys.layout.container.md` (16) inline padding, `sys.layout.stack.md` between blocks |
|
|
212
210
|
| flag | `label.sm` / Semibold, `brand` foreground |
|
|
213
211
|
| avatar | [Thumbnail](../thumbnail/thumbnail.md) `size={32}` — delegated verbatim |
|
|
214
|
-
| channel | 12 / Semibold, `onSurface`. `<a>`; hover underline; focus = hairline `sys.color.
|
|
212
|
+
| channel | 12 / Semibold, `onSurface`. `<a>`; hover underline; focus = hairline `sys.color.border.focused` at `sys.radius.xs` |
|
|
215
213
|
| timestamp | 12 / Semibold, `outline` |
|
|
216
214
|
| followAction | 12 / Semibold, `primary` (inactive) → `onSurfaceVariant` (active) |
|
|
217
215
|
| meta | 12 / Semibold, `onSurfaceVariant`. Each `<a>`; middot at `sys.layout.inline.sm` (4px), decorative |
|
|
@@ -221,8 +219,8 @@ import { Feed } from '@teamblind-chorus/ui';
|
|
|
221
219
|
| poll / offer | `surfaceVariant` fill, `radius.md`, 12 × 16 padding, 48px min-height, 14px body. Leading icon + label at 4px gap, 12px to divider, 12px to count. `poll` paints `brand`; `offer` paints `success` (`ref.palette.green.500`) |
|
|
222
220
|
| citation | Text-column `surfaceVariant`, `radius.md`, 120px-wide hero. 12px padding, 8px gap title↔source. All text 12px. Source mark 16 × 16 at 4px radius, 4px to source name |
|
|
223
221
|
| mention | `body.sm`, `primary`, italic |
|
|
224
|
-
| engagement | `xsmall` [Text Buttons](../button/text.md) + static `<span>` (Views). 16px glyph + 12 / Semibold label, 4 × 8 padding, 4px gap. Row gap 12px. Active Like retones label to `sys.color.brand` with `HeartFillIcon` |
|
|
225
|
-
| bottom divider | `sys.borderWidth.hairline` × `sys.color.
|
|
222
|
+
| engagement | `xsmall` [Text Buttons](../button/text.md) + static `<span>` (Views). 16px glyph + 12 / Semibold label, 4 × 8 padding, 4px gap. Row gap 12px. Active Like retones label to `sys.color.text.brand` with `HeartFillIcon` |
|
|
223
|
+
| bottom divider | `sys.borderWidth.hairline` × `sys.color.border.default` — `border-bottom` on the card |
|
|
226
224
|
|
|
227
225
|
## States
|
|
228
226
|
|
|
@@ -236,7 +234,7 @@ Feed itself is not a focus target; each focusable control paints its own ring pe
|
|
|
236
234
|
|
|
237
235
|
- **Slot omission collapses without a gap.** Optional blocks drop out entirely.
|
|
238
236
|
- **Truncation, not wrap.** `meta` / `title` truncate; `body` clamps to two lines; thumbnail is a flex sibling so the clamp computes against reduced inline width.
|
|
239
|
-
- **Like is a toggle.** Tapping swaps `HeartIcon` → `HeartFillIcon` in `sys.color.brand` via `--button-text-label` and increments the count. Controlled (`liked` + `onLikeChange`) or uncontrolled. Aligns via Text Button's [optical alignment](../button/text.md#optical-alignment).
|
|
237
|
+
- **Like is a toggle.** Tapping swaps `HeartIcon` → `HeartFillIcon` in `sys.color.text.brand` via `--button-text-label` and increments the count. Controlled (`liked` + `onLikeChange`) or uncontrolled. Aligns via Text Button's [optical alignment](../button/text.md#optical-alignment).
|
|
240
238
|
- **Comments commits; Views does not.** Views is a non-interactive `<span>`.
|
|
241
239
|
- **Channel and meta are independent links.** Middot separators decorative (`aria-hidden`), outside link hit areas.
|
|
242
240
|
- **`<FeedGroup>` bundles consecutive Posts.** Semantic wrapper only — inner Posts keep their own padding and bottom divider.
|