@sableui/react 0.9.0 → 0.10.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.md +1 -1
- package/CHANGELOG.md +124 -0
- package/CONTRIBUTING.md +2 -2
- package/DESIGN.md +55 -16
- package/README.md +12 -0
- package/dist/{_contract-C2M1SjBf.d.ts → _contract-CkqXDVww.d.ts} +25 -2
- package/dist/blocks/danger-zone-block.js +3 -3
- package/dist/blocks/danger-zone-block.meta.js +1 -1
- package/dist/blocks/faq-block.js +3 -3
- package/dist/blocks/goal-progress-block.js +1 -1
- package/dist/blocks/notification-list-block.js +1 -1
- package/dist/blocks/pricing-table-block.js +12 -12
- package/dist/blocks/product-card-block.js +3 -3
- package/dist/blocks/product-card-block.meta.js +3 -3
- package/dist/blocks/profile-card-block.js +1 -1
- package/dist/blocks/profile-form-block.js +3 -3
- package/dist/blocks/revenue-breakdown-block.js +5 -5
- package/dist/blocks/stat-row-block.js +3 -3
- package/dist/blocks/task-list-block.js +2 -2
- package/dist/blocks/team-members-block.js +1 -1
- package/dist/blocks/wallet-block.js +4 -4
- package/dist/blocks/wallet-block.meta.js +3 -3
- package/dist/components/accordion/accordion.meta.js +2 -2
- package/dist/components/app-header/app-header.meta.js +1 -1
- package/dist/components/app-shell/app-shell.meta.js +2 -2
- package/dist/components/area-chart/area-chart.meta.js +3 -3
- package/dist/components/badge/badge.js +13 -4
- package/dist/components/badge/badge.meta.js +24 -13
- package/dist/components/badge/badge.variants.js +37 -23
- package/dist/components/banner/banner.meta.js +8 -8
- package/dist/components/banner/banner.variants.js +3 -3
- package/dist/components/bar-chart/bar-chart.meta.js +4 -4
- package/dist/components/breadcrumbs/breadcrumbs.meta.js +1 -1
- package/dist/components/button/button.js +13 -2
- package/dist/components/button/button.meta.js +3 -3
- package/dist/components/card/card.js +16 -7
- package/dist/components/card/card.meta.js +4 -4
- package/dist/components/checkbox-card/checkbox-card.meta.js +10 -10
- package/dist/components/checkbox-card/checkbox-card.variants.js +15 -15
- package/dist/components/code-block/code-block.meta.js +1 -1
- package/dist/components/command-menu/command-menu.js +2 -0
- package/dist/components/command-menu/command-menu.meta.js +1 -1
- package/dist/components/context-card/context-card.meta.js +2 -2
- package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +1 -1
- package/dist/components/dialog/dialog.meta.js +2 -2
- package/dist/components/disclosure-picker/disclosure-picker.js +48 -2
- package/dist/components/disclosure-picker/disclosure-picker.meta.js +3 -2
- package/dist/components/empty-state/empty-state.meta.js +4 -4
- package/dist/components/form/form.js +10 -5
- package/dist/components/gradient-art/gradient-art.meta.js +3 -3
- package/dist/components/label/label.meta.js +1 -1
- package/dist/components/line-chart/line-chart.meta.js +3 -3
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.meta.js +2 -2
- package/dist/components/meter/meter.variants.js +1 -1
- package/dist/components/middle-truncate/middle-truncate.meta.js +4 -4
- package/dist/components/note/note.meta.js +3 -3
- package/dist/components/page-header/page-header.js +1 -1
- package/dist/components/pagination/pagination.meta.js +2 -2
- package/dist/components/phone/phone.meta.js +1 -1
- package/dist/components/pie-chart/pie-chart.meta.js +3 -3
- package/dist/components/pill/pill.js +4 -2
- package/dist/components/pill/pill.meta.js +19 -2
- package/dist/components/pill/pill.variants.js +23 -2
- package/dist/components/popover/popover.meta.js +2 -2
- package/dist/components/radar-chart/radar-chart.meta.js +3 -3
- package/dist/components/radial-chart/radial-chart.meta.js +3 -3
- package/dist/components/radio/radio.meta.js +2 -2
- package/dist/components/radio-card/radio-card.meta.js +6 -6
- package/dist/components/radio-card/radio-card.variants.js +15 -15
- package/dist/components/scroller/scroller.meta.js +1 -1
- package/dist/components/search-input/search-input.meta.js +1 -1
- package/dist/components/section-header/section-header.js +1 -1
- package/dist/components/segmented-bar/segmented-bar.js +1 -1
- package/dist/components/segmented-bar/segmented-bar.meta.js +6 -6
- package/dist/components/segmented-bar/segmented-bar.variants.js +4 -4
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.js +15 -1
- package/dist/components/sheet/sheet.meta.js +1 -1
- package/dist/components/side-nav/side-nav.meta.js +1 -1
- package/dist/components/sidebar/sidebar.meta.js +1 -1
- package/dist/components/sparkline/sparkline.meta.js +4 -4
- package/dist/components/spinner/spinner.meta.js +1 -1
- package/dist/components/status-dot/status-dot.js +4 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -16
- package/dist/components/status-dot/status-dot.variants.js +13 -4
- package/dist/components/stepper/stepper.js +11 -2
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/tabs/tabs.meta.js +1 -1
- package/dist/components/textarea/textarea.meta.js +2 -2
- package/dist/components/toast/toast.js +1 -0
- package/dist/components/toast/toast.meta.js +1 -1
- package/dist/components/tooltip/tooltip.meta.js +2 -2
- package/dist/file-hashes.json +127 -124
- package/dist/foundations/spacing.js +8 -8
- package/dist/index.d.ts +138 -73
- package/dist/index.js +12 -1
- package/dist/lib/chart/types.js +3 -3
- package/dist/lib/is-promise.js +5 -0
- package/dist/lib/palette.js +103 -0
- package/dist/lib/selection-card.js +4 -4
- package/dist/palette.css +669 -0
- package/dist/patterns/auth-page.js +6 -6
- package/dist/patterns/auth-page.meta.js +3 -3
- package/dist/patterns/brand-landing-page.js +2 -2
- package/dist/patterns/chat-panel.js +2 -2
- package/dist/patterns/dashboard-page.js +71 -71
- package/dist/patterns/dashboard-page.meta.js +5 -5
- package/dist/patterns/feed-page.js +25 -25
- package/dist/patterns/feed-page.meta.js +3 -3
- package/dist/patterns/landing-page.js +1 -1
- package/dist/patterns/list-detail-page.js +17 -17
- package/dist/patterns/settings-page.js +17 -17
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +17 -2
- package/dist/styles.css +163 -55
- package/dist/{theme-provider-S0NVJs2Y.d.ts → theme-provider-CdSicAad.d.ts} +1 -1
- package/dist/themes/_contract.js +22 -8
- package/dist/themes/atelier/atelier.css +13 -13
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/ember/ember.css +13 -13
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +13 -13
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +19 -16
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -15
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -22
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +15 -15
- package/llms/blocks/danger-zone-block.md +4 -4
- package/llms/blocks/faq-block.md +3 -3
- package/llms/blocks/goal-progress-block.md +1 -1
- package/llms/blocks/notification-list-block.md +1 -1
- package/llms/blocks/pricing-table-block.md +12 -12
- package/llms/blocks/product-card-block.md +6 -6
- package/llms/blocks/profile-card-block.md +1 -1
- package/llms/blocks/profile-form-block.md +3 -3
- package/llms/blocks/revenue-breakdown-block.md +5 -5
- package/llms/blocks/stat-row-block.md +3 -3
- package/llms/blocks/task-list-block.md +2 -2
- package/llms/blocks/team-members-block.md +1 -1
- package/llms/blocks/wallet-block.md +7 -7
- package/llms/components/accordion.md +2 -2
- package/llms/components/app-header.md +1 -1
- package/llms/components/app-shell.md +2 -2
- package/llms/components/area-chart.md +1 -1
- package/llms/components/badge.md +15 -7
- package/llms/components/banner.md +6 -6
- package/llms/components/bar-chart.md +2 -2
- package/llms/components/breadcrumbs.md +1 -1
- package/llms/components/button.md +3 -3
- package/llms/components/card.md +4 -4
- package/llms/components/checkbox-card.md +5 -5
- package/llms/components/code-block.md +1 -1
- package/llms/components/command-menu.md +1 -1
- package/llms/components/context-card.md +2 -2
- package/llms/components/destructive-action-modal.md +1 -1
- package/llms/components/dialog.md +2 -2
- package/llms/components/disclosure-picker.md +2 -1
- package/llms/components/empty-state.md +4 -4
- package/llms/components/gradient-art.md +3 -3
- package/llms/components/label.md +1 -1
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +2 -2
- package/llms/components/middle-truncate.md +5 -5
- package/llms/components/note.md +3 -3
- package/llms/components/pagination.md +2 -2
- package/llms/components/phone.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/pill.md +12 -2
- package/llms/components/popover.md +2 -2
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/radio-card.md +4 -4
- package/llms/components/radio.md +2 -2
- package/llms/components/scroller.md +1 -1
- package/llms/components/search-input.md +1 -1
- package/llms/components/segmented-bar.md +7 -7
- package/llms/components/select.md +2 -2
- package/llms/components/sheet.md +1 -1
- package/llms/components/side-nav.md +1 -1
- package/llms/components/sidebar.md +1 -1
- package/llms/components/sparkline.md +2 -2
- package/llms/components/spinner.md +1 -1
- package/llms/components/status-dot.md +4 -4
- package/llms/components/table.md +8 -8
- package/llms/components/tabs.md +1 -1
- package/llms/components/textarea.md +2 -2
- package/llms/components/toast.md +1 -1
- package/llms/components/tooltip.md +2 -2
- package/llms/foundations/spacing.md +7 -7
- package/llms/layout/grid.md +1 -1
- package/llms/layout/page.md +1 -1
- package/llms/layout/region.md +1 -1
- package/llms/layout/split-pane.md +1 -1
- package/llms/patterns/auth-page.md +11 -11
- package/llms/patterns/brand-landing-page.md +2 -2
- package/llms/patterns/chat-panel.md +2 -2
- package/llms/patterns/dashboard-page.md +78 -78
- package/llms/patterns/feed-page.md +31 -31
- package/llms/patterns/landing-page.md +1 -1
- package/llms/patterns/list-detail-page.md +18 -18
- package/llms/patterns/settings-page.md +18 -18
- package/llms/reference/token-intent.md +25 -11
- package/llms/reference/utilities.md +1 -1
- package/llms/tasks/adding-a-component.md +4 -1
- package/llms/tasks/building-a-page.md +40 -0
- package/llms/tasks/building-with-sable.md +4 -0
- package/llms/tasks/converting-an-app.md +3 -1
- package/llms/tasks/using-presets.md +4 -0
- package/llms-full.txt +433 -347
- package/llms.txt +7 -7
- package/package.json +4 -4
- package/registry.json +369 -233
- package/scripts/lint-rules.mjs +26 -5
- package/scripts/sources.mjs +4 -1
- package/skills/sable/SKILL.md +14 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FeedPage
|
|
2
2
|
|
|
3
|
-
Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an
|
|
3
|
+
Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an event post with price, capacity Meter and Register, a Skeleton for the next page) beside a sticky rail of who-to-follow and upcoming events that drops away below lg.
|
|
4
4
|
|
|
5
5
|
**Intent:** Reach for this for timelines, activity streams, and any reverse-chronological reading surface where people also act on what they read. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
10
10
|
- Feed scope is `Tabs` (underline, stretched to the column with `flex`): peer views of the same feed. The composer sits above them because posting applies to both.
|
|
11
11
|
- The composer is a Card holding `Textarea autoResize` plus ghost icon Buttons and a secondary `Post` — not ChatComposer, which submits on Enter and is for conversations.
|
|
12
12
|
- Each post is a Card: an Entity byline in a CardList (with an OverflowMenu for post actions), body copy, then media in an AspectBox at the asset's own ratio, inset with `rounded-concentric-lg/6` so its corners follow the card's in every theme.
|
|
13
|
-
- An
|
|
13
|
+
- An event post adds a `Badge appearance="overlay"` on the image, title and price, the one primary Button on the page (`Register`), and a `Meter` for capacity — seats taken against a known maximum is a level, not task progress.
|
|
14
14
|
- Actions are ghost sm Buttons (icon + count) in a Row pulled out by the button's own padding (`-ms-3`) so the first icon sits on the text edge.
|
|
15
15
|
- Loading the next page shows a Skeleton post in the shape the post will land in, above the explicit load-more Button in its `loading` state — predictable for keyboard and AT users, unlike infinite scroll.
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
19
19
|
- Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.
|
|
20
20
|
- Rail card titles render as h2 (CardTitle `render={<h2 />}`) under the page's one h1.
|
|
21
21
|
- Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.
|
|
22
|
-
- The
|
|
22
|
+
- The capacity Meter is role=meter with a getAriaValueText reading ("28 of 40 seats taken").
|
|
23
23
|
- The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").
|
|
24
24
|
|
|
25
25
|
## Source
|
|
@@ -36,7 +36,7 @@ type Post = {
|
|
|
36
36
|
following: boolean
|
|
37
37
|
body: string
|
|
38
38
|
media?: { src: string; alt: string }
|
|
39
|
-
|
|
39
|
+
event?: { title: string; price: string; registered: number; capacity: number }
|
|
40
40
|
likes: number
|
|
41
41
|
comments: number
|
|
42
42
|
}
|
|
@@ -49,9 +49,9 @@ const posts: Post[] = [
|
|
|
49
49
|
initials: "AL",
|
|
50
50
|
time: "2h",
|
|
51
51
|
following: true,
|
|
52
|
-
body: "
|
|
53
|
-
media: { src: "/media/edition-cover.svg", alt: "
|
|
54
|
-
|
|
52
|
+
body: "Registration for the spring workshop is open. Two days of plotter drawing in a small group. See you there.",
|
|
53
|
+
media: { src: "/media/edition-cover.svg", alt: "Workshop poster, a gold-leaf study in overlapping arcs" },
|
|
54
|
+
event: { title: "Spring plotter workshop", price: "$120", registered: 28, capacity: 40 },
|
|
55
55
|
likes: 128,
|
|
56
56
|
comments: 24,
|
|
57
57
|
},
|
|
@@ -74,7 +74,7 @@ const posts: Post[] = [
|
|
|
74
74
|
initials: "NS",
|
|
75
75
|
time: "8h",
|
|
76
76
|
following: false,
|
|
77
|
-
body: "
|
|
77
|
+
body: "Thanks to everyone at the Dusk listening session — the full recording is now in your library. Headphones on.",
|
|
78
78
|
likes: 214,
|
|
79
79
|
comments: 31,
|
|
80
80
|
},
|
|
@@ -86,8 +86,8 @@ const suggestions = [
|
|
|
86
86
|
]
|
|
87
87
|
|
|
88
88
|
const upcoming = [
|
|
89
|
-
{ id: "
|
|
90
|
-
{ id: "
|
|
89
|
+
{ id: "e_01", title: "Quiet Harbor talk", host: "Mary Shelley", initials: "MS", date: "Sat, Oct 12" },
|
|
90
|
+
{ id: "e_02", title: "Dusk, live", host: "Nina Simone", initials: "NS", date: "Tue, Oct 15" },
|
|
91
91
|
]
|
|
92
92
|
|
|
93
93
|
const renderPost = (post: Post) => (
|
|
@@ -113,35 +113,35 @@ const renderPost = (post: Post) => (
|
|
|
113
113
|
{post.media ? (
|
|
114
114
|
<AspectBox ratio={16 / 9} className="overflow-hidden rounded-concentric-lg/6 bg-muted">
|
|
115
115
|
<img src={post.media.src} alt={post.media.alt} className="size-full object-cover" />
|
|
116
|
-
{post.
|
|
116
|
+
{post.event ? (
|
|
117
117
|
<div className="absolute start-3 top-3">
|
|
118
118
|
<Badge appearance="overlay" size="sm" dot>
|
|
119
|
-
|
|
119
|
+
Open now
|
|
120
120
|
</Badge>
|
|
121
121
|
</div>
|
|
122
122
|
) : null}
|
|
123
123
|
</AspectBox>
|
|
124
124
|
) : null}
|
|
125
|
-
{post.
|
|
125
|
+
{post.event ? (
|
|
126
126
|
<Stack gap={1.5}>
|
|
127
127
|
<Row gap={2} align="center" justify="between">
|
|
128
128
|
<div className="min-w-0">
|
|
129
|
-
<p className="text-title-md text-foreground">{post.
|
|
129
|
+
<p className="text-title-md text-foreground">{post.event.title}</p>
|
|
130
130
|
<p className="text-body-sm text-muted-foreground">
|
|
131
|
-
{`${post.
|
|
131
|
+
{`${post.event.price} · ${post.event.capacity} seats`}
|
|
132
132
|
</p>
|
|
133
133
|
</div>
|
|
134
|
-
<Button>{`
|
|
134
|
+
<Button>{`Register for ${post.event.price}`}</Button>
|
|
135
135
|
</Row>
|
|
136
136
|
<Meter
|
|
137
|
-
value={post.
|
|
138
|
-
max={post.
|
|
137
|
+
value={post.event.registered}
|
|
138
|
+
max={post.event.capacity}
|
|
139
139
|
size="sm"
|
|
140
|
-
getAriaValueText={(_, value) => `${value} of ${post.
|
|
140
|
+
getAriaValueText={(_, value) => `${value} of ${post.event?.capacity} seats taken`}
|
|
141
141
|
>
|
|
142
142
|
<Row gap={2} align="center" justify="between" className="mb-2">
|
|
143
|
-
<MeterLabel>{`${post.
|
|
144
|
-
<MeterValue>{(_, value) => `${(post.
|
|
143
|
+
<MeterLabel>{`${post.event.registered} registered`}</MeterLabel>
|
|
144
|
+
<MeterValue>{(_, value) => `${(post.event?.capacity ?? 0) - value} left`}</MeterValue>
|
|
145
145
|
</Row>
|
|
146
146
|
<MeterTrack />
|
|
147
147
|
</Meter>
|
|
@@ -182,15 +182,15 @@ export function FeedPage() {
|
|
|
182
182
|
autoResize
|
|
183
183
|
rows={2}
|
|
184
184
|
aria-label="New post"
|
|
185
|
-
placeholder="Share a process shot or an upcoming
|
|
185
|
+
placeholder="Share a process shot or an upcoming event"
|
|
186
186
|
/>
|
|
187
187
|
<Row gap={1} align="center" justify="between">
|
|
188
188
|
<Row gap={0.5}>
|
|
189
189
|
<Button variant="ghost" size="icon-sm" aria-label="Add images">
|
|
190
190
|
<Icon name="image-stack" />
|
|
191
191
|
</Button>
|
|
192
|
-
<Button variant="ghost" size="icon-sm" aria-label="
|
|
193
|
-
<Icon name="
|
|
192
|
+
<Button variant="ghost" size="icon-sm" aria-label="Add an event">
|
|
193
|
+
<Icon name="calendar" />
|
|
194
194
|
</Button>
|
|
195
195
|
</Row>
|
|
196
196
|
<Button variant="secondary" size="sm">
|
|
@@ -257,17 +257,17 @@ export function FeedPage() {
|
|
|
257
257
|
</Card>
|
|
258
258
|
<Card>
|
|
259
259
|
<CardHeader>
|
|
260
|
-
<CardTitle render={<h2 />}>Upcoming
|
|
260
|
+
<CardTitle render={<h2 />}>Upcoming events</CardTitle>
|
|
261
261
|
</CardHeader>
|
|
262
262
|
<CardList>
|
|
263
|
-
{upcoming.map((
|
|
263
|
+
{upcoming.map((event) => (
|
|
264
264
|
<Entity
|
|
265
|
-
key={
|
|
266
|
-
leading={<Avatar variant="art" fallback={
|
|
267
|
-
title={
|
|
268
|
-
subtitle={`${
|
|
265
|
+
key={event.id}
|
|
266
|
+
leading={<Avatar variant="art" fallback={event.initials} alt={event.host} />}
|
|
267
|
+
title={event.title}
|
|
268
|
+
subtitle={`${event.host} · ${event.date}`}
|
|
269
269
|
action={
|
|
270
|
-
<Button variant="ghost" size="icon-sm" aria-label={`Remind me about ${
|
|
270
|
+
<Button variant="ghost" size="icon-sm" aria-label={`Remind me about ${event.title}`}>
|
|
271
271
|
<Icon name="bell" />
|
|
272
272
|
</Button>
|
|
273
273
|
}
|
|
@@ -285,7 +285,7 @@ export function LandingPage() {
|
|
|
285
285
|
<CardDescription>Team plan</CardDescription>
|
|
286
286
|
</CardHeader>
|
|
287
287
|
<CardContent>
|
|
288
|
-
<Meter value={64} tone="
|
|
288
|
+
<Meter value={64} tone="3">
|
|
289
289
|
<Row justify="between" wrap className="mb-2">
|
|
290
290
|
<MeterLabel>Used</MeterLabel>
|
|
291
291
|
<MeterValue>{(_, value) => `${(value * 30).toLocaleString("en-US")} of 3,000`}</MeterValue>
|
|
@@ -45,9 +45,9 @@ const roleLabel: Record<Role, string> = {
|
|
|
45
45
|
|
|
46
46
|
const roleSummary: Record<Role, string> = {
|
|
47
47
|
owner: "Full access, including payouts and billing. Transfer ownership in studio settings.",
|
|
48
|
-
admin: "Manages members,
|
|
49
|
-
editor: "Drafts,
|
|
50
|
-
viewer: "Sees
|
|
48
|
+
admin: "Manages members, projects and billing.",
|
|
49
|
+
editor: "Drafts, edits and publishes projects.",
|
|
50
|
+
viewer: "Sees projects and reports, but can't change them.",
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
type Activity = { id: string; icon: string; text: string; when: string }
|
|
@@ -62,7 +62,7 @@ type Member = {
|
|
|
62
62
|
handle?: string
|
|
63
63
|
joined?: string
|
|
64
64
|
lastActive?: string
|
|
65
|
-
|
|
65
|
+
projects?: number
|
|
66
66
|
invitedBy?: string
|
|
67
67
|
sent?: string
|
|
68
68
|
expires?: string
|
|
@@ -75,40 +75,40 @@ const seats = 10
|
|
|
75
75
|
const initialMembers: Member[] = [
|
|
76
76
|
{
|
|
77
77
|
id: "u_01", name: "Ada Lovelace", handle: "@ada", email: "ada@atelier.art", initials: "AL", role: "owner", pending: false,
|
|
78
|
-
joined: "Mar 4, 2024", lastActive: "Online now",
|
|
78
|
+
joined: "Mar 4, 2024", lastActive: "Online now", projects: 12,
|
|
79
79
|
activity: [
|
|
80
|
-
{ id: "a1", icon: "
|
|
80
|
+
{ id: "a1", icon: "file", text: "Published the Analytical Engine No. 3 roadmap", when: "2 hours ago" },
|
|
81
81
|
{ id: "a2", icon: "mail", text: "Invited louise@bourgeois.studio as Editor", when: "2 days ago" },
|
|
82
82
|
],
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
id: "u_02", name: "Grace Hopper", handle: "@grace", email: "grace@atelier.art", initials: "GH", role: "admin", pending: false,
|
|
86
|
-
joined: "Mar 9, 2024", lastActive: "20 minutes ago",
|
|
86
|
+
joined: "Mar 9, 2024", lastActive: "20 minutes ago", projects: 4,
|
|
87
87
|
activity: [
|
|
88
88
|
{ id: "a1", icon: "user", text: "Changed Frida Kahlo's role to Viewer", when: "Yesterday" },
|
|
89
|
-
{ id: "a2", icon: "edit", text: "Updated
|
|
90
|
-
{ id: "a3", icon: "
|
|
89
|
+
{ id: "a2", icon: "edit", text: "Updated the budget on First Compiler", when: "3 days ago" },
|
|
90
|
+
{ id: "a3", icon: "file", text: "Created the First Compiler project", when: "Sep 28" },
|
|
91
91
|
],
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
id: "u_03", name: "Mary Shelley", handle: "@mary", email: "mary@atelier.art", initials: "MS", role: "editor", pending: false,
|
|
95
|
-
joined: "Jun 18, 2024", lastActive: "3 hours ago",
|
|
95
|
+
joined: "Jun 18, 2024", lastActive: "3 hours ago", projects: 7,
|
|
96
96
|
activity: [
|
|
97
97
|
{ id: "a1", icon: "calendar", text: "Scheduled Frankenstein Studies for Oct 14", when: "3 hours ago" },
|
|
98
|
-
{ id: "a2", icon: "
|
|
98
|
+
{ id: "a2", icon: "file", text: "Published The Last Man report", when: "Sep 21" },
|
|
99
99
|
],
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
id: "u_04", name: "Nina Simone", handle: "@nina", email: "nina@atelier.art", initials: "NS", role: "editor", pending: false,
|
|
103
|
-
joined: "Jan 12, 2025", lastActive: "Yesterday",
|
|
103
|
+
joined: "Jan 12, 2025", lastActive: "Yesterday", projects: 3,
|
|
104
104
|
activity: [
|
|
105
|
-
{ id: "a1", icon: "upload", text: "Uploaded
|
|
106
|
-
{ id: "a2", icon: "
|
|
105
|
+
{ id: "a1", icon: "upload", text: "Uploaded files for Little Girl Blue", when: "Yesterday" },
|
|
106
|
+
{ id: "a2", icon: "file", text: "Published the Feeling Good brief", when: "Aug 30" },
|
|
107
107
|
],
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
id: "u_05", name: "Frida Kahlo", handle: "@frida", email: "frida@atelier.art", initials: "FK", role: "viewer", pending: false,
|
|
111
|
-
joined: "Jul 2, 2025", lastActive: "4 days ago",
|
|
111
|
+
joined: "Jul 2, 2025", lastActive: "4 days ago", projects: 0,
|
|
112
112
|
activity: [{ id: "a1", icon: "comment", text: "Commented on the Las Dos Fridas draft", when: "4 days ago" }],
|
|
113
113
|
},
|
|
114
114
|
{
|
|
@@ -341,8 +341,8 @@ export function ListDetailPage() {
|
|
|
341
341
|
<dd className={selected.lastActive === "Online now" ? "text-success" : undefined}>
|
|
342
342
|
{selected.lastActive}
|
|
343
343
|
</dd>
|
|
344
|
-
<dt>
|
|
345
|
-
<dd>{selected.
|
|
344
|
+
<dt>Projects owned</dt>
|
|
345
|
+
<dd>{selected.projects}</dd>
|
|
346
346
|
</dl>
|
|
347
347
|
)}
|
|
348
348
|
</div>
|
|
@@ -395,7 +395,7 @@ export function ListDetailPage() {
|
|
|
395
395
|
description={
|
|
396
396
|
selected.pending
|
|
397
397
|
? `${selected.email} won't be able to join with the link they were sent.`
|
|
398
|
-
: "They lose access to the
|
|
398
|
+
: "They lose access to the workspace right away. Projects they own stay with the team."
|
|
399
399
|
}
|
|
400
400
|
confirmLabel={selected.pending ? "Revoke invite" : "Remove member"}
|
|
401
401
|
onConfirm={() => remove(selected.id)}
|
|
@@ -46,17 +46,17 @@ const timezones = [
|
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
const notifications = [
|
|
49
|
-
{ name: "
|
|
50
|
-
{ name: "
|
|
51
|
-
{ name: "
|
|
52
|
-
{ name: "notifyDigest", title: "Weekly digest", description: "
|
|
49
|
+
{ name: "notifyComments", title: "New comments", description: "When someone comments on your work", on: true },
|
|
50
|
+
{ name: "notifyInvoicePaid", title: "Invoice paid", description: "When a customer pays an invoice", on: true },
|
|
51
|
+
{ name: "notifyMembers", title: "New members", description: "When someone joins your workspace", on: false },
|
|
52
|
+
{ name: "notifyDigest", title: "Weekly digest", description: "Activity and usage, every Monday", on: true },
|
|
53
53
|
{ name: "notifyProduct", title: "Product updates", description: "New Atelier features", on: false },
|
|
54
54
|
]
|
|
55
55
|
|
|
56
56
|
const payouts = [
|
|
57
57
|
{ label: "Next payout", value: "$1,284.60", note: "Friday, Oct 16" },
|
|
58
58
|
{ label: "Pending", value: "$412.00", note: "Clears in 7 days" },
|
|
59
|
-
{ label: "Paid this year", value: "$18,920.00", note: "Across 214
|
|
59
|
+
{ label: "Paid this year", value: "$18,920.00", note: "Across 214 invoices" },
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
export function SettingsPage() {
|
|
@@ -81,8 +81,8 @@ export function SettingsPage() {
|
|
|
81
81
|
bottomNav={
|
|
82
82
|
<BottomNav aria-label="Primary">
|
|
83
83
|
<BottomNavItem icon={<Icon name="grid" />} label="Overview" showLabel render={<a href="/" />} />
|
|
84
|
-
<BottomNavItem icon={<Icon name="
|
|
85
|
-
<BottomNavItem icon={<Icon name="user" />} label="
|
|
84
|
+
<BottomNavItem icon={<Icon name="folder" />} label="Accounts" showLabel render={<a href="/accounts" />} />
|
|
85
|
+
<BottomNavItem icon={<Icon name="user" />} label="Customers" showLabel render={<a href="/customers" />} />
|
|
86
86
|
<BottomNavItem icon={<Icon name="bell" />} label="Activity" showLabel render={<a href="/activity" />} />
|
|
87
87
|
<BottomNavItem icon={<Icon name="gear" />} label="Settings" showLabel active render={<a href="/settings" />} />
|
|
88
88
|
</BottomNav>
|
|
@@ -94,8 +94,8 @@ export function SettingsPage() {
|
|
|
94
94
|
</SidebarHeader>
|
|
95
95
|
<SidebarNav>
|
|
96
96
|
<SidebarItem icon={<Icon name="grid" />} label="Overview" render={<a href="/" />} />
|
|
97
|
-
<SidebarItem icon={<Icon name="
|
|
98
|
-
<SidebarItem icon={<Icon name="user" />} label="
|
|
97
|
+
<SidebarItem icon={<Icon name="folder" />} label="Accounts" render={<a href="/accounts" />} />
|
|
98
|
+
<SidebarItem icon={<Icon name="user" />} label="Customers" render={<a href="/customers" />} />
|
|
99
99
|
<SidebarItem
|
|
100
100
|
icon={<Icon name="bell" />}
|
|
101
101
|
label="Activity"
|
|
@@ -149,7 +149,7 @@ export function SettingsPage() {
|
|
|
149
149
|
<Card id="profile" className="scroll-mt-6">
|
|
150
150
|
<CardHeader>
|
|
151
151
|
<CardTitle render={<h2 />}>Profile</CardTitle>
|
|
152
|
-
<CardDescription>How
|
|
152
|
+
<CardDescription>How your team sees you on Atelier.</CardDescription>
|
|
153
153
|
</CardHeader>
|
|
154
154
|
{/* The one explicit save on the page: profile text is drafted, then
|
|
155
155
|
committed. Toggles below apply the moment they change. */}
|
|
@@ -180,14 +180,14 @@ export function SettingsPage() {
|
|
|
180
180
|
<Input defaultValue="ada" autoComplete="username" spellCheck={false} />
|
|
181
181
|
</Field>
|
|
182
182
|
</Grid>
|
|
183
|
-
<Field label="Bio" name="bio" description="Shown on your profile and
|
|
183
|
+
<Field label="Bio" name="bio" description="Shown on your profile and in comments.">
|
|
184
184
|
<Textarea
|
|
185
185
|
rows={3}
|
|
186
|
-
defaultValue="
|
|
186
|
+
defaultValue="Product designer working on tools for small teams. Writing about process every month."
|
|
187
187
|
/>
|
|
188
188
|
</Field>
|
|
189
189
|
<Grid cols={{ base: 1, sm: 2 }} gap={3}>
|
|
190
|
-
<Field label="Timezone" name="timezone" description="
|
|
190
|
+
<Field label="Timezone" name="timezone" description="Due dates use this zone.">
|
|
191
191
|
<Select defaultValue="europe-london" items={timezones}>
|
|
192
192
|
<SelectTrigger />
|
|
193
193
|
<SelectContent>
|
|
@@ -212,7 +212,7 @@ export function SettingsPage() {
|
|
|
212
212
|
<CardHeader>
|
|
213
213
|
<CardTitle render={<h2 />}>Notifications</CardTitle>
|
|
214
214
|
<CardDescription>
|
|
215
|
-
Email updates about your
|
|
215
|
+
Email updates about your projects. Changes save automatically.
|
|
216
216
|
</CardDescription>
|
|
217
217
|
</CardHeader>
|
|
218
218
|
<CardList>
|
|
@@ -275,7 +275,7 @@ export function SettingsPage() {
|
|
|
275
275
|
<Entity
|
|
276
276
|
leading={<Icon name="download" className="text-lg text-muted-foreground" />}
|
|
277
277
|
title="Export data"
|
|
278
|
-
subtitle="
|
|
278
|
+
subtitle="Projects, invoices, and members"
|
|
279
279
|
action={
|
|
280
280
|
<Button variant="outline" size="sm">
|
|
281
281
|
Export
|
|
@@ -289,8 +289,8 @@ export function SettingsPage() {
|
|
|
289
289
|
<CardHeader>
|
|
290
290
|
<CardTitle render={<h2 />}>Delete account</CardTitle>
|
|
291
291
|
<CardDescription>
|
|
292
|
-
Permanently remove your profile,
|
|
293
|
-
|
|
292
|
+
Permanently remove your profile, projects, and billing history. Files you shared
|
|
293
|
+
stay with your team.
|
|
294
294
|
</CardDescription>
|
|
295
295
|
</CardHeader>
|
|
296
296
|
<CardFooter className="justify-end">
|
|
@@ -307,7 +307,7 @@ export function SettingsPage() {
|
|
|
307
307
|
open={deleteOpen}
|
|
308
308
|
onOpenChange={setDeleteOpen}
|
|
309
309
|
title="Delete your account?"
|
|
310
|
-
description="This permanently removes your profile, 12
|
|
310
|
+
description="This permanently removes your profile, 12 projects, and billing history. Your final invoice is still sent. This can't be undone."
|
|
311
311
|
confirmLabel="Delete account"
|
|
312
312
|
confirmingLabel="Deleting…"
|
|
313
313
|
confirmText="ada"
|
|
@@ -50,23 +50,23 @@ Which token to reach for, and what it is most often confused with. Generated fro
|
|
|
50
50
|
- **Not** `tone-info`. Success asserts an outcome; info asserts a fact. 'Saved' is success, '3 items' is info.
|
|
51
51
|
- **`success-foreground`** — Ink paired with `success`. Evaluate the rendered contrast together; never choose it independently. *Use for:* Whenever the background is `success`. Use them as a pair — `bg-success text-success-foreground` — so a theme can retune the surface and the ink together.
|
|
52
52
|
- **Not** `foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.
|
|
53
|
-
- **`tone-
|
|
53
|
+
- **`tone-3`** — Extra colour 3: the third, and usually the quietest, category in a classified set. *Use for:* A third category, or the baseline one that means 'nothing special'.
|
|
54
54
|
- **Not** `muted`. A tone is *semantic classification* that a reader is meant to decode; `muted` is de-emphasis. If a legend would help the user, it is a tone.
|
|
55
|
-
- **`tone-
|
|
56
|
-
- **Not** `
|
|
57
|
-
- **`tone-
|
|
58
|
-
- **Not** `
|
|
55
|
+
- **`tone-2`** — Extra colour 2: the second category in a classified set. *Use for:* A second category, distinct from tone-1, in a badge, dot, card or chart.
|
|
56
|
+
- **Not** `tone-1`. Distinct categories must stay visually distinct.
|
|
57
|
+
- **`tone-1`** — Extra colour 1, the lead of the three a theme defines beyond its status colours. The numbered tones exist so a product can classify content (categories, plans, series) without inventing colours per feature; the product decides what each one means. *Use for:* The most prominent category in a classified set, or the first series of a chart.
|
|
58
|
+
- **Not** `primary`. A tone classifies the *thing*; primary drives an *action*. A category badge is not a button.
|
|
59
59
|
- **`tone-info`** — Domain tone: neutral, factual information. *Use for:* An informational note or banner, a neutral count, a 'did you know' surface.
|
|
60
60
|
- **Not** `success`. Info states a fact; success states an outcome. Also not `primary` — an info banner is not an action.
|
|
61
61
|
- **`tone-warning`** — Domain tone: proceed with care, nothing is broken yet. *Use for:* A caution banner, a nearing-limit indicator, a soloed/overridden state in a tool.
|
|
62
62
|
- **Not** `destructive`. Warning is *advisory*; destructive is *consequential*. If nothing is lost by continuing, it is a warning.
|
|
63
63
|
- **`highlight`** — A marker on content the user is meant to notice — search hits, diffs, annotations. *Use for:* Highlighting matched text, a changed line, a called-out passage.
|
|
64
64
|
- **Not** `accent`. `accent` is UI state that follows the pointer or selection; `highlight` marks the *content* and persists independently of interaction.
|
|
65
|
-
- **`tone-
|
|
65
|
+
- **`tone-3-foreground`** — Ink paired with `tone-3`. Evaluate the rendered contrast together; never choose it independently. *Use for:* Whenever the background is `tone-3`. Use them as a pair — `bg-tone-3 text-tone-3-foreground` — so a theme can retune the surface and the ink together.
|
|
66
66
|
- **Not** `foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.
|
|
67
|
-
- **`tone-
|
|
67
|
+
- **`tone-2-foreground`** — Ink paired with `tone-2`. Evaluate the rendered contrast together; never choose it independently. *Use for:* Whenever the background is `tone-2`. Use them as a pair — `bg-tone-2 text-tone-2-foreground` — so a theme can retune the surface and the ink together.
|
|
68
68
|
- **Not** `foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.
|
|
69
|
-
- **`tone-
|
|
69
|
+
- **`tone-1-foreground`** — Ink paired with `tone-1`. Evaluate the rendered contrast together; never choose it independently. *Use for:* Whenever the background is `tone-1`. Use them as a pair — `bg-tone-1 text-tone-1-foreground` — so a theme can retune the surface and the ink together.
|
|
70
70
|
- **Not** `foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.
|
|
71
71
|
- **`tone-info-foreground`** — Ink paired with `tone-info`. Evaluate the rendered contrast together; never choose it independently. *Use for:* Whenever the background is `tone-info`. Use them as a pair — `bg-tone-info text-tone-info-foreground` — so a theme can retune the surface and the ink together.
|
|
72
72
|
- **Not** `foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.
|
|
@@ -173,11 +173,11 @@ Which token to reach for, and what it is most often confused with. Generated fro
|
|
|
173
173
|
|
|
174
174
|
### layout (11)
|
|
175
175
|
|
|
176
|
-
- **`rhythm-unit`** — The vertical rhythm base —
|
|
176
|
+
- **`rhythm-unit`** — The vertical rhythm base — one baseline step (8px at the default scale). Every vertical measure is a multiple of it, and it follows the type and the density control. *Use for:* Through `Stack`/`Row`/`Grid` gap numbers, which are rhythm units rather than pixels. A gap of 2 is 16px at the default scale, and grows or tightens with density.
|
|
177
177
|
- **Not** arbitrary vertical spacing. The rhythm is what makes unrelated sections of a page feel like one document.
|
|
178
178
|
- **`grid-columns`** — The column count of the content grid — 12. *Use for:* `Grid`/`Columns`, WITHIN a region, for genuinely multi-column content.
|
|
179
179
|
- **Not** using a column span as the page's reading width. A span is not a measure — see `measure-prose`.
|
|
180
|
-
- **`grid-gutter`** — The gap between grid columns. *Use for:* `Grid`/`Columns` spacing.
|
|
180
|
+
- **`grid-gutter`** — The gap between grid columns and rows — 2 baseline steps on phones, 3 on desktop, snapped to the step. *Use for:* `Grid`/`Columns` spacing.
|
|
181
181
|
- **`page-inset`** — The page's outer breathing room, left and right. *Use for:* The gutter between content and the viewport edge, at every breakpoint.
|
|
182
182
|
- **Not** a global max-width. Sable caps width per REGION by role, never once for the whole page.
|
|
183
183
|
- **`measure-prose`** — The maximum comfortable line length for prose, in `ch`. *Use for:* Any long-form text column.
|
|
@@ -335,4 +335,18 @@ Which token to reach for, and what it is most often confused with. Generated fro
|
|
|
335
335
|
- **`hairline-strong`** — A hairline that must be seen — an input edge, a swatch ring. *Use for:* Bounding a control whose fill is arbitrary (a colour swatch) or user content.
|
|
336
336
|
- **Not** `hairline`, when the line would otherwise disappear against what it bounds.
|
|
337
337
|
- **`border-floating`** — The edge of a floating surface. Fainter than `border`: the shadow carries the elevation. *Use for:* Every floating surface — popover, menu, select, tooltip, dialog, sheet, toast, a floating panel — as `border-border-floating` or `ring-border-floating`, together with `bg-popover` and a `shadow-md`+ shadow. Derived for every theme (a faint alpha of the foreground); a theme may tune it with `--theme-border-floating`.
|
|
338
|
-
- **Not** `border`. A full hairline plus a shadow stacks into a heavy outline around anything that floats. `border` is for resting edges: inputs, cards, badges.
|
|
338
|
+
- **Not** `border`. A full hairline plus a shadow stacks into a heavy outline around anything that floats. `border` is for resting edges: inputs, cards, badges.
|
|
339
|
+
|
|
340
|
+
### Generated palette
|
|
341
|
+
|
|
342
|
+
More colour for apps, generated from the active theme: neutral and eight hue families, plus a 50–950 scale under every tone. Each family keeps its hue and leans toward the brand (half the gap, at most 15°; --theme-palette-harmony scales it, 0 = own hues). Neutral is the theme's --background mixed toward its --foreground. Steps follow the mode: low steps sit near the page and high ones far from it in light and dark alike, so a pair needs no dark: variant. For decorative and categorical colour (tags, avatars, labels, legends, illustration); surfaces, text and actions take roles. Components never name a step.
|
|
343
|
+
|
|
344
|
+
- **Families:** `neutral`, `red`, `orange`, `yellow`, `green`, `teal`, `blue`, `purple`, `pink`, each 50–950.
|
|
345
|
+
- **Tone scales:** `tone-1-*`, `tone-2-*`, `tone-3-*`, `tone-info-*`, `tone-warning-*`, `success-*`, `destructive-*`; step 500 is the tone itself.
|
|
346
|
+
- **Use as:** bg-blue-100, text-blue-800, border-neutral-200, bg-tone-1-100 — or var(--teal-500). Tailwind's other families (slate, gray, zinc, stone, amber, lime, emerald, cyan, sky, indigo, violet, fuchsia, rose) are switched off.
|
|
347
|
+
- **tag / label:** bg-<family>-100 text-<family>-800 (held to 4.5:1 in every theme and mode)
|
|
348
|
+
- **text on the page:** text-<family>-700
|
|
349
|
+
- **solid fill:** bg-<family>-700 text-<family>-50
|
|
350
|
+
- **hairline:** border-<family>-300
|
|
351
|
+
- **Components:** Badge, Pill and StatusDot take a family as tone (tone="blue"): a category with no meaning attached.
|
|
352
|
+
- **Slots:** data-palette="<family>" on an element fills these from that family, so one set of classes paints any family: bg-palette-wash text-palette-text.
|
|
@@ -101,7 +101,7 @@ The `@utility` and `@custom-variant` layer in `styles.css`: what each one is, wh
|
|
|
101
101
|
- `<ol className="sable-list-numbered"><li>Install</li><li>Import the styles</li></ol>`
|
|
102
102
|
- **`sable-list-dl`** — Label/value pairs on a native `<dl>`: muted label-tier `<dt>`s and body-tier `<dd>`s on a baseline-aligned two-column grid; long values wrap inside their column. Colour a status value with a `text-*` utility on its `<dd>`. *Use for:* Read-only details: an order summary, a record's metadata, a settings summary, profile stats.
|
|
103
103
|
- **Instead of** A grid of `<div>`s (screen readers lose the term/value pairing), or Table for a single record. For editable values use Form and Field.
|
|
104
|
-
- `<dl className="sable-list-dl"><dt>
|
|
104
|
+
- `<dl className="sable-list-dl"><dt>Plan</dt><dd>Studio</dd><dt>Status</dt><dd className="text-success">Active</dd></dl>`
|
|
105
105
|
- **`sable-list-dl-2`** — Two label/value pairs a row once the nearest `@container` ancestor is at least 28rem wide. Add to `sable-list-dl`. *Use for:* A compact metadata block in a wide card or panel. Put `@container` on a parent so it measures the room it actually has.
|
|
106
106
|
- **Instead of** A viewport breakpoint, which gives a narrow card on a wide screen four columns it cannot fit.
|
|
107
107
|
- `<div className="@container"><dl className="sable-list-dl sable-list-dl-2">…</dl></div>`
|
|
@@ -81,7 +81,10 @@ because the meta wins *whole* and silently discards the other.
|
|
|
81
81
|
|
|
82
82
|
## The hard rules
|
|
83
83
|
|
|
84
|
-
1. **Semantic tokens only** — never hex/oklch, never a raw palette step
|
|
84
|
+
1. **Semantic tokens only** — never hex/oklch, never a raw palette step, not
|
|
85
|
+
even the generated palette's (`bg-blue-500`; that is for apps). A component
|
|
86
|
+
that takes a palette family as `tone` sets `data-palette` to it and paints
|
|
87
|
+
from the slots that fills (`bg-palette-wash text-palette-text`), as Badge does.
|
|
85
88
|
2. **Paired typography utilities** — never `text-{size}` + `font-{weight}`.
|
|
86
89
|
3. **`primitives/` is behaviour, `components/` is the public API.** Base UI is
|
|
87
90
|
wrapped and re-exported; consumers import only from the root barrel.
|
|
@@ -71,6 +71,25 @@ Pick each route's `max` by what the person mainly does on it:
|
|
|
71
71
|
(a copy in `light` or `dark` wins over it); keep the role names and change
|
|
72
72
|
the values.
|
|
73
73
|
|
|
74
|
+
## Columns and rhythm
|
|
75
|
+
|
|
76
|
+
- **Flex or grid.** One direction is `Stack` (column) or `Row` (row). Content
|
|
77
|
+
that has to line up across rows *and* columns is `Grid`/`Columns`. A card's
|
|
78
|
+
single column is a `Stack`, never a one-track grid.
|
|
79
|
+
- **Columns are a choice per route.** Record it in the brief next to the width:
|
|
80
|
+
which content spans which of the 12 columns, and where it folds
|
|
81
|
+
(`<Col span={{ base: 12, lg: 8 }}>`). The grid does not fold on its own. A
|
|
82
|
+
route that is one reading measure has no columns, and the brief says so.
|
|
83
|
+
- **Vertical space is measured in lines.** The baseline is one line of body
|
|
84
|
+
text split into steps (8px at the default type). Gap numbers on `Stack`/
|
|
85
|
+
`Row`/`Grid` are steps; in Tailwind, even steps (`gap-4`, `py-6`, `mt-12`)
|
|
86
|
+
are whole steps and odd ones (`py-3`, `gap-5`) are half a step. Title to
|
|
87
|
+
description is one step, between groups one to two lines, between sections
|
|
88
|
+
three or more.
|
|
89
|
+
- **It is a rhythm, not a lock.** A card with a button or a code block is as
|
|
90
|
+
tall as its content. Keep the spacing between blocks in steps and let
|
|
91
|
+
components be; never pad or nudge them to pin text to lines.
|
|
92
|
+
|
|
74
93
|
## Typography: pick the tier by ROLE, not by size
|
|
75
94
|
|
|
76
95
|
| tier | for | not |
|
|
@@ -100,6 +119,27 @@ intent, and what it gets confused with, is in
|
|
|
100
119
|
Ink is never chosen independently of its surface: `bg-card text-card-foreground`,
|
|
101
120
|
as a pair.
|
|
102
121
|
|
|
122
|
+
### More colour: the generated palette
|
|
123
|
+
|
|
124
|
+
When a page needs colour that is not a role (tags, avatars, project labels, a
|
|
125
|
+
chart legend, illustration), reach for the generated palette, not hex and not a
|
|
126
|
+
theme's own ramp. Every theme has `neutral`, `red`, `orange`, `yellow`,
|
|
127
|
+
`green`, `teal`, `blue`, `purple` and `pink` (50–950), plus a scale under
|
|
128
|
+
every tone (`tone-1-100`, `success-700`). Each family leans toward the brand, so
|
|
129
|
+
the set belongs to the theme, and neutral is the theme's own page-to-ink ramp.
|
|
130
|
+
|
|
131
|
+
- On a component, the family is the tone: `<Badge tone="teal">Research</Badge>`,
|
|
132
|
+
`<Pill tone="blue">`, `<StatusDot tone="pink" />`. A family is a category with
|
|
133
|
+
no meaning attached; a status is still `success`, `warning` or `destructive`.
|
|
134
|
+
- On your own element, use the pairs: `bg-blue-100 text-blue-800` (a tag),
|
|
135
|
+
`text-blue-700` (text on the page), `bg-blue-700 text-blue-50` (a solid
|
|
136
|
+
fill), `border-blue-300` (a hairline). Steps follow the mode (a low step is
|
|
137
|
+
always near the page), so these need no `dark:`.
|
|
138
|
+
- Surfaces, body text and actions still take roles. The palette never replaces
|
|
139
|
+
`bg-card` or `bg-primary`.
|
|
140
|
+
|
|
141
|
+
The full reference is `llms/reference/token-intent.md` → Generated palette.
|
|
142
|
+
|
|
103
143
|
## The traps
|
|
104
144
|
|
|
105
145
|
- **A `Card` is a container, not a heading or a landmark.** If its title is the
|
|
@@ -43,6 +43,10 @@ for each recognizable element with `sable search "<need>" --json`; inspect the
|
|
|
43
43
|
matching `component <Name> --props` and `--examples` before implementation.
|
|
44
44
|
Use Sable's composed anatomy and supported variants. A hand-built button, field,
|
|
45
45
|
menu, dialog, table, or card that imitates a shipped component violates the contract.
|
|
46
|
+
That binds behaviour and parts, not layouts. Results marked ◇ (patterns, blocks,
|
|
47
|
+
and compositions such as MediaTile) are references: use one when its layout fits
|
|
48
|
+
the brief, otherwise compose your own from Sable's parts. The brief's signature
|
|
49
|
+
surfaces, where the product's identity lives, are designed from its direction.
|
|
46
50
|
|
|
47
51
|
Resolve styling through component variants, documented utilities/recipes, then
|
|
48
52
|
semantic tokens. App-owned CSS handles uncovered content and composition; it
|
|
@@ -35,7 +35,9 @@ loudest and least clearly ("invalid hook call").
|
|
|
35
35
|
|
|
36
36
|
The most reliable signal that a conversion is real: the app's own CSS variable
|
|
37
37
|
block shrinks to almost nothing. Anything that remains should be *domain* colour,
|
|
38
|
-
not UI colour.
|
|
38
|
+
not UI colour. Most domain colour has a home too: a tag or category colour maps
|
|
39
|
+
to a palette family (`<Badge tone="blue">`, `bg-blue-100 text-blue-800`), which
|
|
40
|
+
follows the theme where the app's own hex never did.
|
|
39
41
|
|
|
40
42
|
Grep for orphaned references afterwards. A CSS variable that no longer exists
|
|
41
43
|
resolves to nothing and the declaration is dropped — **silently**. A checkerboard
|
|
@@ -17,6 +17,10 @@ prints a complete starting record; the public `PRESET_TOKEN_NAMES` is authoritat
|
|
|
17
17
|
Font stacks are requirements, not downloaded fonts. Declare matching stacks in
|
|
18
18
|
the font metadata and tokens. Resolve internal palette variables before saving;
|
|
19
19
|
portable presets may reference public Sable tokens, not private palette names.
|
|
20
|
+
A preset does not carry the generated palette (`bg-blue-500` and the rest): it is
|
|
21
|
+
computed from the preset's own primary, page and ink. `theme-palette-harmony`
|
|
22
|
+
(0 keeps the families' own hues, 1 is the default lean toward the brand) is the
|
|
23
|
+
one palette token a preset may set.
|
|
20
24
|
The validator rejects unknown tokens and CSS that can escape its declaration.
|
|
21
25
|
`sable preset --check` also holds labels on brand and status fills to 4.5:1 and APCA Lc 60
|
|
22
26
|
and suggests a pair that reads; a preset may keep a pair deliberately through
|