@sableui/react 0.2.0 → 0.3.1
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 +3 -1
- package/CHANGELOG.md +84 -0
- package/CONTRIBUTING.md +8 -0
- package/README.md +1 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +68 -26
- package/dist/blocks/index.d.ts +396 -0
- package/dist/blocks/index.js +1997 -0
- package/dist/chunk-6LCIBMMG.js +4908 -0
- package/dist/{chunk-AOWBYEQ6.js → chunk-CKNM7KSB.js} +1598 -6464
- package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
- package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
- package/dist/chunk-WCFLQVK5.js +5097 -0
- package/dist/icon/index.js +2 -2
- package/dist/icon-sets/reicon.js +1 -1
- package/dist/index.d.ts +471 -34
- package/dist/index.js +588 -2589
- package/dist/ios/index.d.ts +8 -1
- package/dist/ios/index.js +6 -5
- package/dist/ios/preview.d.ts +11 -1
- package/dist/ios/preview.js +13 -8
- package/dist/ios/styles.css +14 -0
- package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
- package/dist/patterns/index.d.ts +1 -1
- package/dist/patterns/index.js +29 -30
- package/dist/presets/index.js +10 -1
- package/dist/themes/base/base.css +23 -0
- package/dist/themes/index.js +2 -2
- package/llms/blocks/api-keys-block.md +79 -0
- package/llms/blocks/chat-thread-block.md +51 -0
- package/llms/blocks/command-palette-block.md +48 -0
- package/llms/blocks/cta-band-block.md +62 -0
- package/llms/blocks/danger-zone-block.md +69 -0
- package/llms/blocks/device-comparison-block.md +91 -0
- package/llms/blocks/event-card-block.md +84 -0
- package/llms/blocks/faq-block.md +72 -0
- package/llms/blocks/feature-grid-block.md +75 -0
- package/llms/blocks/feedback-form-block.md +66 -0
- package/llms/blocks/goal-progress-block.md +54 -0
- package/llms/blocks/hero-block.md +65 -0
- package/llms/blocks/invite-team-block.md +61 -0
- package/llms/blocks/invoice-history-block.md +84 -0
- package/llms/blocks/kpi-sparkline-block.md +57 -0
- package/llms/blocks/media-player-block.md +100 -0
- package/llms/blocks/newsletter-block.md +64 -0
- package/llms/blocks/not-found-block.md +56 -0
- package/llms/blocks/notification-list-block.md +63 -0
- package/llms/blocks/notification-settings-block.md +54 -0
- package/llms/blocks/order-tracking-block.md +62 -0
- package/llms/blocks/pricing-card-block.md +82 -0
- package/llms/blocks/pricing-table-block.md +129 -0
- package/llms/blocks/product-card-block.md +69 -0
- package/llms/blocks/profile-card-block.md +103 -0
- package/llms/blocks/profile-form-block.md +114 -0
- package/llms/blocks/revenue-breakdown-block.md +70 -0
- package/llms/blocks/sign-in-block.md +64 -0
- package/llms/blocks/sign-up-block.md +78 -0
- package/llms/blocks/site-footer-block.md +96 -0
- package/llms/blocks/stat-row-block.md +59 -0
- package/llms/blocks/steps-block.md +73 -0
- package/llms/blocks/task-list-block.md +81 -0
- package/llms/blocks/team-members-block.md +91 -0
- package/llms/blocks/testimonial-block.md +78 -0
- package/llms/blocks/traffic-sources-block.md +63 -0
- package/llms/blocks/traffic-trend-block.md +95 -0
- package/llms/blocks/upload-list-block.md +66 -0
- package/llms/blocks/verify-code-block.md +59 -0
- package/llms/blocks/wallet-block.md +84 -0
- package/llms/components/area-chart.md +112 -0
- package/llms/components/bar-chart.md +131 -0
- package/llms/components/button.md +1 -1
- package/llms/components/card.md +2 -2
- package/llms/components/context-menu.md +1 -1
- package/llms/components/date-tile.md +79 -0
- package/llms/components/entity.md +4 -2
- package/llms/components/line-chart.md +101 -0
- package/llms/components/menu.md +1 -1
- package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
- package/llms/components/pie-chart.md +103 -0
- package/llms/components/radar-chart.md +106 -0
- package/llms/components/radial-chart.md +102 -0
- package/llms/components/segmented-bar.md +102 -0
- package/llms/components/settings-menu.md +2 -2
- package/llms/components/sparkline.md +80 -0
- package/llms/components/table.md +1 -1
- package/llms/patterns/auth-page.md +2 -2
- package/llms/patterns/dashboard-page.md +6 -7
- package/llms/patterns/feed-page.md +7 -7
- package/llms/patterns/list-detail-page.md +7 -15
- package/llms/patterns/settings-page.md +10 -21
- package/llms/tasks/building-an-ios-prototype.md +4 -1
- package/llms/tasks/choosing-a-component.md +2 -2
- package/llms-full.txt +4296 -277
- package/llms.txt +55 -3
- package/package.json +5 -1
- package/registry.json +6383 -3741
- package/targets/ios.json +90 -9
- package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Sparkline
|
|
2
|
+
|
|
3
|
+
**Status:** beta
|
|
4
|
+
|
|
5
|
+
A word-sized trend with no axes — a line, a line over a soft fill, or columns — sized by its container, for stat cards, table cells and sentences. Draws on as it scrolls into view.
|
|
6
|
+
|
|
7
|
+
**Keywords:** sparkline, trend, mini chart, inline chart, micro chart, kpi, stat
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Sparkline next to a number to show where it has been — a KPI's last 30 days in a stat card, a row's trend in a table. It has no axes, tooltip or values to read off; when the reader needs those, use AreaChart or LineChart.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Pair it with the current value and its change in text; the sparkline shows only the shape.
|
|
16
|
+
- Do: Give it a label that names the measure and the period — the spoken summary adds the range and latest value.
|
|
17
|
+
- Don't: Put two sparklines on one shared axis to compare them — each scales to its own data; use LineChart.
|
|
18
|
+
- Don't: Use it as the only place a number appears; a shape without a value cannot be read off.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| data | number[] | — | The values, oldest first. |
|
|
25
|
+
| label | string | — | What the line tracks ("Revenue, last 30 days"). The spoken summary adds the first and last values, low and high. |
|
|
26
|
+
| variant | "line" \| "area" \| "bar" | "line" | A line, a line over a soft fill, or columns. |
|
|
27
|
+
| tone | "edition" \| "collectible" \| "standard" \| "info" \| "warning" \| "primary" \| "neutral" \| "success" \| "destructive" | "primary" | Colour by role. |
|
|
28
|
+
| className | string | "h-8 w-24" | Sizes it; the drawing stretches to fit. |
|
|
29
|
+
| animate | boolean | true | Draw on (or rise) when it scrolls into view. |
|
|
30
|
+
| locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
|
|
31
|
+
|
|
32
|
+
## Variants
|
|
33
|
+
|
|
34
|
+
- **variant**: line, area, bar
|
|
35
|
+
|
|
36
|
+
## Tokens used
|
|
37
|
+
|
|
38
|
+
`primary`, `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `muted-foreground`, `success`, `destructive`
|
|
39
|
+
|
|
40
|
+
## Motion
|
|
41
|
+
|
|
42
|
+
- **arrival** · Line / bars · _custom (outside the recipe layer)_ — Once on screen the line draws on (or the bars rise, in sequence). On the motion tokens; reduced motion shows the finished drawing.
|
|
43
|
+
- **response** · Sparkline · _no motion_ — Not interactive: it has no tooltip or focus.
|
|
44
|
+
- **transition** · Sparkline · _no motion_ — New data redraws in place; a word-sized chart animating on every update would distract from the number beside it.
|
|
45
|
+
- **activity** · Sparkline · _no motion_ — No indeterminate state.
|
|
46
|
+
|
|
47
|
+
## Accessibility
|
|
48
|
+
|
|
49
|
+
- Rendered role=img with an aria-label built from `label` and the data: "Revenue, last 30 days: from 120 to 184, low 96, high 190".
|
|
50
|
+
- It is not focusable and has no tooltip, so the number it accompanies must be in text beside it.
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- Components: AreaChart, LineChart, Card, Table
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
### In a stat card
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { Card, CardAction, CardDescription, CardHeader, CardTitle, Sparkline } from "@sableui/react"
|
|
62
|
+
|
|
63
|
+
<Card>
|
|
64
|
+
<CardHeader>
|
|
65
|
+
<CardDescription>Revenue</CardDescription>
|
|
66
|
+
<CardTitle className="text-mono-xl">$18,240</CardTitle>
|
|
67
|
+
<CardAction>
|
|
68
|
+
<Sparkline label="Revenue, last 12 weeks" data={[12, 14, 13, 17, 16, 19, 18, 21, 20, 24, 23, 26]} variant="area" tone="success" />
|
|
69
|
+
</CardAction>
|
|
70
|
+
</CardHeader>
|
|
71
|
+
</Card>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Columns in a table cell
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { Sparkline } from "@sableui/react"
|
|
78
|
+
|
|
79
|
+
<Sparkline label="Daily signups, last 14 days" data={[4, 6, 3, 8, 7, 9, 5, 6, 10, 8, 7, 11, 9, 12]} variant="bar" className="h-6 w-20" />
|
|
80
|
+
```
|
package/llms/components/table.md
CHANGED
|
@@ -17,7 +17,7 @@ Use Table for structured data with consistent columns and rows. Use DataTable wh
|
|
|
17
17
|
- Do: Use the built-in horizontal frame for overflow instead of squeezing columns until content becomes unreadable.
|
|
18
18
|
- Do: Put a table in a card with `<CardContent flush>`. The first and last columns then take the card's own padding (`--table-edge-px`), so they line up with the CardHeader at any density. Never hand-set `pl-6`/`pr-6` on edge cells.
|
|
19
19
|
- Don't: Use Table for heterogeneous items with no stable columns. Use Card, Entity, or a list composition instead.
|
|
20
|
-
- Don't: Put every possible action in every row. Use one primary row action,
|
|
20
|
+
- Don't: Put every possible action in every row. Use one primary row action, an OverflowMenu, or row detail navigation.
|
|
21
21
|
|
|
22
22
|
### Anatomy
|
|
23
23
|
|
|
@@ -99,10 +99,10 @@ export function AuthPage() {
|
|
|
99
99
|
<Separator label="or" />
|
|
100
100
|
|
|
101
101
|
<Stack gap={1.5}>
|
|
102
|
-
<Button variant="outline" size="cta" className="w-full">
|
|
102
|
+
<Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="key" />}>
|
|
103
103
|
Sign in with a passkey
|
|
104
104
|
</Button>
|
|
105
|
-
<Button variant="outline" size="cta" className="w-full">
|
|
105
|
+
<Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="wallet" />}>
|
|
106
106
|
Connect a wallet
|
|
107
107
|
</Button>
|
|
108
108
|
</Stack>
|
|
@@ -13,7 +13,7 @@ App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader
|
|
|
13
13
|
- The table runs flush to the card edges with `<CardContent flush>`, which hands the card's padding to the table (`--table-edge-px`), so the first and last columns line up with the CardHeader at any card density — never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.
|
|
14
14
|
- Sold / supply is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance="plain" dot`, with the app's own states mapped to tones once at the top of the file.
|
|
15
15
|
- Lists are Entity rows in CardList, inset to the card padding and divided by hairlines — never CardContent with hand-tuned px. A card that shares a grid row with a taller one becomes `flex flex-col` and pins its CardFooter with `mt-auto`.
|
|
16
|
-
- Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with
|
|
16
|
+
- Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with an OverflowMenu. Sidebar rebinds the page ink tokens to its own, so the Entity and OverflowMenu read correctly even when a theme paints the rail dark on a light page.
|
|
17
17
|
- The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.
|
|
18
18
|
- Chrome (Sidebar) is outside the content grid; the Page region caps only content.
|
|
19
19
|
- Sidebar hides below lg, so the shell's header carries an AppHeader (brand + account) and its footer a BottomNav with the same destinations — never strand mobile users without navigation.
|
|
@@ -29,7 +29,7 @@ App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader
|
|
|
29
29
|
## Source
|
|
30
30
|
|
|
31
31
|
```tsx
|
|
32
|
-
import { AppHeader, AppShell, Avatar, Badge, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, DataTable,
|
|
32
|
+
import { AppHeader, AppShell, Avatar, Badge, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, DataTable, Entity, Grid, Icon, MenuItem, MenuSeparator, Meter, NotificationBadge, OverflowMenu, Page, PageHeader, RelativeTime, Row, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, Stack } from "@sableui/react"
|
|
33
33
|
|
|
34
34
|
// Edition status → Badge tone. Sable has no status vocabulary of its own;
|
|
35
35
|
// an app maps ITS states onto tones once, here, and every badge follows.
|
|
@@ -143,20 +143,19 @@ export function DashboardPage() {
|
|
|
143
143
|
/>
|
|
144
144
|
<SidebarItem icon={<Icon name="gear" />} label="Settings" render={<a href="/settings" />} />
|
|
145
145
|
</SidebarNav>
|
|
146
|
-
|
|
147
|
-
dark on a light page, so this subtree reads the rail's ink instead. */}
|
|
148
|
-
<SidebarFooter className="[--foreground:var(--sidebar-foreground)]">
|
|
146
|
+
<SidebarFooter>
|
|
149
147
|
<Entity
|
|
150
148
|
className="[--entity-py:0]"
|
|
151
149
|
leading={<Avatar size="sm" fallback="AL" alt="" />}
|
|
152
150
|
title="Ada Lovelace"
|
|
151
|
+
subtitle="@ada"
|
|
153
152
|
action={
|
|
154
|
-
<
|
|
153
|
+
<OverflowMenu label="Account" size="icon-sm">
|
|
155
154
|
<MenuItem render={<a href="/@ada" />}>View profile</MenuItem>
|
|
156
155
|
<MenuItem render={<a href="/settings" />}>Settings</MenuItem>
|
|
157
156
|
<MenuSeparator />
|
|
158
157
|
<MenuItem>Sign out</MenuItem>
|
|
159
|
-
</
|
|
158
|
+
</OverflowMenu>
|
|
160
159
|
}
|
|
161
160
|
/>
|
|
162
161
|
</SidebarFooter>
|
|
@@ -9,7 +9,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
9
9
|
- The main column is capped at `--region-feed` (640px) inside a `wide` page, so posts keep a reading measure while the rail fills the desktop width. The rail is a fixed `w-80` aside, `sticky` at lg and `hidden` below it — its content is supplementary, so nothing is lost on mobile.
|
|
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
|
-
- Each post is a Card: an Entity byline in a CardList (with
|
|
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
13
|
- An edition drop adds a `Badge appearance="overlay"` on the artwork, title and price, the one primary Button on the page (`Collect`), and a `Meter` for supply — collected 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.
|
|
@@ -17,7 +17,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
17
17
|
## Accessibility
|
|
18
18
|
|
|
19
19
|
- Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.
|
|
20
|
-
- Rail card titles
|
|
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
22
|
- The supply Meter is role=meter with a getAriaValueText reading ("62 of 100 collected").
|
|
23
23
|
- The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").
|
|
@@ -25,7 +25,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
25
25
|
## Source
|
|
26
26
|
|
|
27
27
|
```tsx
|
|
28
|
-
import { AspectBox, Avatar, Badge, Button, Card, CardContent, CardHeader, CardList, CardTitle,
|
|
28
|
+
import { AspectBox, Avatar, Badge, Button, Card, CardContent, CardHeader, CardList, CardTitle, Entity, Icon, MenuItem, MenuSeparator, Meter, MeterLabel, MeterTrack, MeterValue, OverflowMenu, Page, PageHeader, Row, Skeleton, Stack, Tabs, TabsList, TabsPanel, TabsTrigger, Textarea } from "@sableui/react"
|
|
29
29
|
|
|
30
30
|
type Post = {
|
|
31
31
|
id: string
|
|
@@ -99,12 +99,12 @@ const renderPost = (post: Post) => (
|
|
|
99
99
|
title={post.author}
|
|
100
100
|
subtitle={`${post.handle} · ${post.time}`}
|
|
101
101
|
action={
|
|
102
|
-
<
|
|
102
|
+
<OverflowMenu label={`Post options for ${post.author}`} size="icon-sm">
|
|
103
103
|
<MenuItem>Copy link</MenuItem>
|
|
104
104
|
<MenuItem>Mute {post.handle}</MenuItem>
|
|
105
105
|
<MenuSeparator />
|
|
106
106
|
<MenuItem variant="destructive">Report post</MenuItem>
|
|
107
|
-
</
|
|
107
|
+
</OverflowMenu>
|
|
108
108
|
}
|
|
109
109
|
/>
|
|
110
110
|
</CardList>
|
|
@@ -237,7 +237,7 @@ export function FeedPage() {
|
|
|
237
237
|
<Stack gap={3}>
|
|
238
238
|
<Card>
|
|
239
239
|
<CardHeader>
|
|
240
|
-
<CardTitle
|
|
240
|
+
<CardTitle render={<h2 />}>Who to follow</CardTitle>
|
|
241
241
|
</CardHeader>
|
|
242
242
|
<CardList>
|
|
243
243
|
{suggestions.map((person) => (
|
|
@@ -257,7 +257,7 @@ export function FeedPage() {
|
|
|
257
257
|
</Card>
|
|
258
258
|
<Card>
|
|
259
259
|
<CardHeader>
|
|
260
|
-
<CardTitle
|
|
260
|
+
<CardTitle render={<h2 />}>Upcoming drops</CardTitle>
|
|
261
261
|
</CardHeader>
|
|
262
262
|
<CardList>
|
|
263
263
|
{upcoming.map((drop) => (
|
|
@@ -7,7 +7,7 @@ Searchable list beside the selected record's detail: interactive Entity rows in
|
|
|
7
7
|
## Notes
|
|
8
8
|
|
|
9
9
|
- Region cap is `wide`; Columns 12 → Col 5 (list) + Col 7 (detail). Below lg the list and detail share one column as a drill-in: selecting a row shows the detail with an `All members` back button (lg:hidden), and the back button returns to the list. The panes swap with `max-lg:hidden`, so desktop always shows both.
|
|
10
|
-
- Rows are interactive Entity inside one Card + CardList. Selection is `
|
|
10
|
+
- Rows are interactive Entity inside one Card + CardList. Selection is Entity `selected`; `sable-surface` on the list card makes its selected step 12% ink off the card, so a selected row stays distinct from its muted avatar.
|
|
11
11
|
- Search filters the list in place (name, email, role). No matches swap the CardList for an EmptyState with a Clear search action; the selected detail stays open.
|
|
12
12
|
- A pending invite lives in the same list: a mail glyph in the Avatar, an `Invited` Badge, and an invite-specific detail (role on joining, sent, expires, invited by; Resend and Revoke actions).
|
|
13
13
|
- The role Select edits the record in place; its Field description states what the role can do. The owner's role is read-only and the owner has no remove action.
|
|
@@ -17,14 +17,14 @@ Searchable list beside the selected record's detail: interactive Entity rows in
|
|
|
17
17
|
|
|
18
18
|
## Accessibility
|
|
19
19
|
|
|
20
|
-
- Each row is a real button (Entity `interactive` + `onSelect`): Tab moves through rows, Enter/Space selects. The selected row
|
|
20
|
+
- Each row is a real button (Entity `interactive` + `onSelect`): Tab moves through rows, Enter/Space selects. The selected row is Entity `selected`, which paints the selected fill and sets aria-current; list avatars are aria-hidden so initials are not read before the name.
|
|
21
21
|
- The detail pane is a labelled region (`Member details`) whose title is a level-2 heading.
|
|
22
22
|
- On a phone, drilling in moves focus to the back button; going back returns focus to the row that was open. After a remove, focus lands on the newly selected row; after Clear search, on the search field.
|
|
23
23
|
|
|
24
24
|
## Source
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
|
-
import { useState, useEffect } from "react"
|
|
27
|
+
import { useState, useEffect, useRef } from "react"
|
|
28
28
|
import { Avatar, Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Col, Columns, DestructiveActionModal, EmptyState, Entity, Field, Icon, Page, PageHeader, Row, SearchInput, Select, SelectContent, SelectItem, SelectTrigger, Stack, StatusDot } from "@sableui/react"
|
|
29
29
|
|
|
30
30
|
type Role = "owner" | "admin" | "editor" | "viewer"
|
|
@@ -208,11 +208,8 @@ export function ListDetailPage() {
|
|
|
208
208
|
<Entity
|
|
209
209
|
key={m.id}
|
|
210
210
|
data-member-id={m.id}
|
|
211
|
-
data-selected={isSelected ? "" : undefined}
|
|
212
|
-
// No selected variant on Entity yet: the relative-surface recipe paints
|
|
213
|
-
// data-selected one step off the card.
|
|
214
|
-
className="sable-surface-interactive"
|
|
215
211
|
interactive
|
|
212
|
+
selected={isSelected}
|
|
216
213
|
onSelect={() => select(m.id)}
|
|
217
214
|
leading={
|
|
218
215
|
<Avatar
|
|
@@ -222,12 +219,7 @@ export function ListDetailPage() {
|
|
|
222
219
|
aria-hidden
|
|
223
220
|
/>
|
|
224
221
|
}
|
|
225
|
-
title={
|
|
226
|
-
<>
|
|
227
|
-
{m.name}
|
|
228
|
-
{isSelected ? <span className="sr-only"> (selected)</span> : null}
|
|
229
|
-
</>
|
|
230
|
-
}
|
|
222
|
+
title={m.name}
|
|
231
223
|
subtitle={
|
|
232
224
|
m.pending
|
|
233
225
|
? `${roleLabel[m.role]} · invite sent ${m.sent?.toLowerCase()}`
|
|
@@ -289,7 +281,7 @@ export function ListDetailPage() {
|
|
|
289
281
|
alt={selected.pending ? "Pending invite" : selected.name}
|
|
290
282
|
/>
|
|
291
283
|
<Stack gap={0} className="min-w-0">
|
|
292
|
-
<CardTitle
|
|
284
|
+
<CardTitle render={<h2 />} className="truncate">
|
|
293
285
|
{selected.name}
|
|
294
286
|
</CardTitle>
|
|
295
287
|
<CardDescription className="truncate">
|
|
@@ -378,7 +370,7 @@ export function ListDetailPage() {
|
|
|
378
370
|
{selected.activity.length > 0 ? (
|
|
379
371
|
<Card>
|
|
380
372
|
<CardHeader>
|
|
381
|
-
<CardTitle
|
|
373
|
+
<CardTitle render={<h3 />}>
|
|
382
374
|
Recent activity
|
|
383
375
|
</CardTitle>
|
|
384
376
|
</CardHeader>
|
|
@@ -28,7 +28,7 @@ Account settings inside the app shell: a sticky SideNav section rail beside a co
|
|
|
28
28
|
|
|
29
29
|
```tsx
|
|
30
30
|
import { useState } from "react"
|
|
31
|
-
import { AppHeader, AppShell, Avatar, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Col, Columns, DestructiveActionModal,
|
|
31
|
+
import { AppHeader, AppShell, Avatar, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Col, Columns, DestructiveActionModal, Entity, Field, Form, Grid, Icon, Input, MenuItem, MenuSeparator, NotificationBadge, OverflowMenu, Page, PageHeader, Row, Select, SelectContent, SelectItem, SelectTrigger, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, SideNav, SideNavItem, Stack, Switch, Textarea } from "@sableui/react"
|
|
32
32
|
|
|
33
33
|
const sections = [
|
|
34
34
|
{ id: "profile", label: "Profile" },
|
|
@@ -107,20 +107,19 @@ export function SettingsPage() {
|
|
|
107
107
|
/>
|
|
108
108
|
<SidebarItem icon={<Icon name="gear" />} label="Settings" active render={<a href="/settings" />} />
|
|
109
109
|
</SidebarNav>
|
|
110
|
-
|
|
111
|
-
dark on a light page, so this subtree reads the rail's ink instead. */}
|
|
112
|
-
<SidebarFooter className="[--foreground:var(--sidebar-foreground)]">
|
|
110
|
+
<SidebarFooter>
|
|
113
111
|
<Entity
|
|
114
112
|
className="[--entity-py:0]"
|
|
115
113
|
leading={<Avatar size="sm" fallback="AL" alt="" />}
|
|
116
114
|
title="Ada Lovelace"
|
|
115
|
+
subtitle="@ada"
|
|
117
116
|
action={
|
|
118
|
-
<
|
|
117
|
+
<OverflowMenu label="Account" size="icon-sm">
|
|
119
118
|
<MenuItem render={<a href="/@ada" />}>View profile</MenuItem>
|
|
120
119
|
<MenuItem render={<a href="/settings" />}>Settings</MenuItem>
|
|
121
120
|
<MenuSeparator />
|
|
122
121
|
<MenuItem>Sign out</MenuItem>
|
|
123
|
-
</
|
|
122
|
+
</OverflowMenu>
|
|
124
123
|
}
|
|
125
124
|
/>
|
|
126
125
|
</SidebarFooter>
|
|
@@ -152,9 +151,7 @@ export function SettingsPage() {
|
|
|
152
151
|
<Stack gap={3}>
|
|
153
152
|
<Card id="profile" className="scroll-mt-6">
|
|
154
153
|
<CardHeader>
|
|
155
|
-
<CardTitle>
|
|
156
|
-
<h2>Profile</h2>
|
|
157
|
-
</CardTitle>
|
|
154
|
+
<CardTitle render={<h2 />}>Profile</CardTitle>
|
|
158
155
|
<CardDescription>How collectors see you on Atelier.</CardDescription>
|
|
159
156
|
</CardHeader>
|
|
160
157
|
{/* The one explicit save on the page: profile text is drafted, then
|
|
@@ -216,9 +213,7 @@ export function SettingsPage() {
|
|
|
216
213
|
|
|
217
214
|
<Card id="notifications" className="scroll-mt-6">
|
|
218
215
|
<CardHeader>
|
|
219
|
-
<CardTitle>
|
|
220
|
-
<h2>Notifications</h2>
|
|
221
|
-
</CardTitle>
|
|
216
|
+
<CardTitle render={<h2 />}>Notifications</CardTitle>
|
|
222
217
|
<CardDescription>
|
|
223
218
|
Email updates about your editions. Changes save automatically.
|
|
224
219
|
</CardDescription>
|
|
@@ -243,9 +238,7 @@ export function SettingsPage() {
|
|
|
243
238
|
|
|
244
239
|
<Card id="payouts" className="scroll-mt-6">
|
|
245
240
|
<CardHeader>
|
|
246
|
-
<CardTitle>
|
|
247
|
-
<h2>Payouts</h2>
|
|
248
|
-
</CardTitle>
|
|
241
|
+
<CardTitle render={<h2 />}>Payouts</CardTitle>
|
|
249
242
|
<CardDescription>Paid in USD every Friday to Chase checking ••4821.</CardDescription>
|
|
250
243
|
<CardAction>
|
|
251
244
|
<Button variant="outline" size="sm" render={<a href="/settings/payouts" />}>
|
|
@@ -268,9 +261,7 @@ export function SettingsPage() {
|
|
|
268
261
|
|
|
269
262
|
<Card id="account" className="scroll-mt-6">
|
|
270
263
|
<CardHeader>
|
|
271
|
-
<CardTitle>
|
|
272
|
-
<h2>Account</h2>
|
|
273
|
-
</CardTitle>
|
|
264
|
+
<CardTitle render={<h2 />}>Account</CardTitle>
|
|
274
265
|
<CardDescription>Sign-in details and your data.</CardDescription>
|
|
275
266
|
</CardHeader>
|
|
276
267
|
<CardList>
|
|
@@ -299,9 +290,7 @@ export function SettingsPage() {
|
|
|
299
290
|
|
|
300
291
|
<Card>
|
|
301
292
|
<CardHeader>
|
|
302
|
-
<CardTitle>
|
|
303
|
-
<h2>Delete account</h2>
|
|
304
|
-
</CardTitle>
|
|
293
|
+
<CardTitle render={<h2 />}>Delete account</CardTitle>
|
|
305
294
|
<CardDescription>
|
|
306
295
|
Permanently remove your profile, editions, and sales history. Collectors keep
|
|
307
296
|
the editions they own.
|
|
@@ -74,7 +74,10 @@ Entity rows in a Card) or `redesign` (desktop workspace tools).
|
|
|
74
74
|
Keep the app's `IconProvider`.
|
|
75
75
|
4. Frame it on a host page with `PhoneFrame` from `@sableui/react/ios/preview`
|
|
76
76
|
and `@sableui/react/ios/frame.css` (Sable's Phone around an iframe at
|
|
77
|
-
402×874, scaled as a whole device).
|
|
77
|
+
402×874, scaled as a whole device). `fit="contain"` with a height in
|
|
78
|
+
`className` keeps the whole device on screen. Shown on a real phone with no
|
|
79
|
+
frame, pass `systemChrome={false}` to `NativeApp`: the device draws its own
|
|
80
|
+
status bar and home indicator, and the safe areas come from it. To follow a host's live look, call
|
|
78
81
|
`useIosPreviewBridge(frameRef, { mode })` on the host and
|
|
79
82
|
`useIosPreviewTheme()` in the prototype.
|
|
80
83
|
5. Verify light and dark, keyboard focus, reduced motion, back and edge swipe,
|
|
@@ -28,8 +28,8 @@ The question to ask is always **what the user is doing**, not what it looks like
|
|
|
28
28
|
| The user is… | Use | Not |
|
|
29
29
|
|---|---|---|
|
|
30
30
|
| Choosing one action from a list under a button | `Menu` | `Select` (that picks a *value*) |
|
|
31
|
-
| Reaching secondary actions for a row or card | `
|
|
32
|
-
| Acting on the thing under the pointer (right-click or long press) | `ContextMenu`, always mirrored in a visible `
|
|
31
|
+
| Reaching secondary actions for a row or card | `OverflowMenu` | a bare `Menu` with a hand-made icon trigger |
|
|
32
|
+
| Acting on the thing under the pointer (right-click or long press) | `ContextMenu`, always mirrored in a visible `OverflowMenu` | `ContextMenu` alone: it is undiscoverable |
|
|
33
33
|
| Changing a few settings that each show their current value | `SettingsMenu` | a `Menu` of toggles that hides each current state |
|
|
34
34
|
| Running a main action that has close variations | a `Button` beside an icon `Button` that opens a `Menu` | a hand-fused split control |
|
|
35
35
|
|