@sableui/react 0.17.0 → 0.18.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 +10 -3
- package/CHANGELOG.md +106 -0
- package/README.md +11 -4
- package/bin/sable.mjs +160 -18
- package/dist/activity-DT8VK9fx.d.ts +25 -0
- package/dist/adapters/github.d.ts +41 -0
- package/dist/adapters/github.js +24 -0
- package/dist/components/activity-heatmap/activity-heatmap.js +420 -0
- package/dist/components/activity-heatmap/activity-heatmap.meta.js +142 -0
- package/dist/components/activity-heatmap/index.js +9 -0
- package/dist/components/code-block/code-block.js +5 -3
- package/dist/components/code-block/code-block.meta.js +6 -0
- package/dist/components/dialog/dialog.js +25 -5
- package/dist/components/dialog/dialog.meta.js +26 -5
- package/dist/components/media-image/media-image.js +3 -1
- package/dist/components/media-image/media-image.meta.js +15 -1
- package/dist/components/media-tile/media-tile.js +13 -1
- package/dist/components/media-tile/media-tile.meta.js +10 -0
- package/dist/components/page-header/page-header.js +9 -4
- package/dist/components/page-header/page-header.meta.js +26 -1
- package/dist/components/pill/pill.meta.js +15 -6
- package/dist/components/pill/pill.variants.js +1 -0
- package/dist/components/search-input/search-input.js +30 -5
- package/dist/components/search-input/search-input.meta.js +29 -4
- package/dist/components/section-header/section-header.js +27 -17
- package/dist/components/section-header/section-header.meta.js +27 -5
- package/dist/components/sheet/sheet.js +15 -3
- package/dist/components/sheet/sheet.meta.js +2 -2
- package/dist/file-hashes.json +46 -36
- package/dist/icon-sets/reicon.js +23 -1
- package/dist/index.d.ts +270 -27
- package/dist/index.js +8 -1
- package/dist/layout/AspectBox.js +2 -5
- package/dist/layout/Bleed.js +10 -6
- package/dist/layout/Center.js +11 -3
- package/dist/layout/Columns.js +6 -3
- package/dist/layout/Grid.js +14 -6
- package/dist/layout/GridOverlay.js +7 -7
- package/dist/layout/Region.js +18 -3
- package/dist/layout/Row.js +27 -20
- package/dist/layout/Spacer.js +13 -2
- package/dist/layout/SplitPane.js +1 -1
- package/dist/layout/Stack.js +25 -18
- package/dist/layout/index.d.ts +57 -6
- package/dist/lib/chart/activity.js +32 -0
- package/dist/lib/chart/calendar.js +67 -0
- package/dist/lib/chart/chart-tooltip.js +2 -2
- package/dist/lib/chart/heat.js +55 -0
- package/dist/lib/focus-return.js +53 -0
- package/dist/lib/taxonomy.js +1 -0
- package/dist/styles.css +122 -114
- package/llms/components/activity-heatmap.md +133 -0
- package/llms/components/code-block.md +1 -0
- package/llms/components/dialog.md +34 -3
- package/llms/components/media-image.md +12 -0
- package/llms/components/media-tile.md +21 -0
- package/llms/components/page-header.md +16 -1
- package/llms/components/pill.md +24 -5
- package/llms/components/search-input.md +37 -4
- package/llms/components/section-header.md +28 -4
- package/llms/components/sheet.md +2 -2
- package/llms/layout/aspect-box.md +1 -1
- package/llms/layout/bleed.md +1 -1
- package/llms/layout/center.md +1 -1
- package/llms/layout/columns.md +1 -1
- package/llms/layout/grid.md +1 -1
- package/llms/layout/region.md +1 -1
- package/llms/layout/row.md +9 -1
- package/llms/layout/spacer.md +1 -1
- package/llms/layout/split-pane.md +1 -1
- package/llms/layout/stack.md +1 -1
- package/llms/tasks/auditing-an-app.md +31 -0
- package/llms/tasks/getting-started.md +10 -3
- package/llms/tasks/using-presets.md +1 -2
- package/llms-full.txt +372 -35
- package/llms.txt +3 -2
- package/package.json +6 -1
- package/registry.json +532 -36
- package/scripts/adoption.mjs +239 -0
- package/scripts/agent-setup.mjs +110 -18
- package/scripts/grade.mjs +17 -0
- package/scripts/wiring.mjs +2 -1
- package/skills/entry/SKILL.md +1 -1
- package/skills/sable/SKILL.md +33 -4
- package/skills/sable/agents/openai.yaml +1 -1
- package/targets/ios.json +9 -0
package/registry.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://sable.design/registry.schema.json",
|
|
3
3
|
"name": "@sableui/react",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
|
|
6
6
|
"engine": {
|
|
7
7
|
"behavior": "@base-ui/react",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"composition": "Inspect a relevant pattern skeleton. Reuse when its information architecture fits; original compositions from layout primitives are equally valid.",
|
|
64
64
|
"review": "sable docs reviewing-a-design",
|
|
65
65
|
"finish": "sable check <source paths> runs doctor, lint and grade in one pass and exits 1 on any failure, then lists the review steps no tool can verify. Optional Claude Code Stop hook: sable setup --agent --target claude --hook",
|
|
66
|
+
"adoption": "sable check reports how much of the UI composes Sable and the app-built components Sable already ships (grade --json → adoption). Never a failure; at level none or low, ask the user before moving working screens: sable docs auditing-an-app → Adoption.",
|
|
66
67
|
"evidenceTemplate": "sable grade --template",
|
|
67
68
|
"learning": "sable docs learning-from-builds",
|
|
68
69
|
"localMemory": ".sable/lessons/ for causal observations; .sable/preferences/ for scoped judgments; both project-owned and preserved across package upgrades",
|
|
@@ -78,8 +79,9 @@
|
|
|
78
79
|
"claude": ".claude/skills/sable"
|
|
79
80
|
},
|
|
80
81
|
"canonical": "skills/sable/SKILL.md in the installed package; entries resolve it on every task",
|
|
81
|
-
"
|
|
82
|
-
"
|
|
82
|
+
"instructions": "A short managed block in AGENTS.md (Codex, Cursor) and CLAUDE.md (Claude Code), between <!-- sable:begin --> and <!-- sable:end -->, that sends every UI request through the skill even when it doesn't name Sable. --no-instructions skips it.",
|
|
83
|
+
"upgrade": "pnpm exec sable upgrade (updates the package, then runs the new version's init). From 0.17 or earlier: pnpm update @sableui/react --latest; pnpm exec sable init",
|
|
84
|
+
"preserves": "Local edits, project instructions outside the Sable block, presets and preferences; setup only refreshes unchanged managed entries and blocks, and refuses linked destinations."
|
|
83
85
|
},
|
|
84
86
|
"distributionStatus": "Public on npm as @sableui/react; install with pnpm add @sableui/react.",
|
|
85
87
|
"ios": {
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
"command": "pnpm exec sable init",
|
|
97
99
|
"preview": "pnpm exec sable init --dry-run",
|
|
98
100
|
"after": "Run it once the package is installed; before that `npx sable` resolves an unrelated package.",
|
|
99
|
-
"does": "Installs missing packages with the lockfile's package manager (React, Tailwind and its build plugin), writes the css imports in order into the global stylesheet, adds bundlerDedupe and the Tailwind plugin to a Vite config (a postcss.config.mjs for Next.js), loads the fonts, wraps a Vite starter's <App /> in IconProvider, runs agentSetup, then sable doctor.",
|
|
101
|
+
"does": "Installs missing packages with the lockfile's package manager (React, Tailwind and its build plugin), writes the css imports in order into the global stylesheet, adds bundlerDedupe and the Tailwind plugin to a Vite config (a postcss.config.mjs for Next.js), loads the fonts, wraps a Vite starter's <App /> in IconProvider, runs agentSetup (skill entries plus the AGENTS.md / CLAUDE.md block; in a terminal it offers the Stop hook), then sable doctor.",
|
|
100
102
|
"frameworks": "Vite and Next.js (App Router) get every step. Other setups get the packages and the stylesheet; the bundler and provider steps are printed.",
|
|
101
103
|
"manual": "Each edit it cannot make safely (an unfamiliar config shape, a Next.js layout, a stylesheet split across files) is printed as the exact snippet and the file it belongs in. Apply them, then run sable doctor.",
|
|
102
104
|
"flags": "--dry-run previews; --no-agent skips the skill; --no-install prints install commands instead of running them.",
|
|
@@ -1886,7 +1888,15 @@
|
|
|
1886
1888
|
"at-sign",
|
|
1887
1889
|
"replay",
|
|
1888
1890
|
"motion",
|
|
1889
|
-
"emoji"
|
|
1891
|
+
"emoji",
|
|
1892
|
+
"briefcase",
|
|
1893
|
+
"layers",
|
|
1894
|
+
"microphone",
|
|
1895
|
+
"video",
|
|
1896
|
+
"broadcast",
|
|
1897
|
+
"newspaper",
|
|
1898
|
+
"palette",
|
|
1899
|
+
"gamepad"
|
|
1890
1900
|
]
|
|
1891
1901
|
},
|
|
1892
1902
|
"layoutPrimitives": {
|
|
@@ -1903,7 +1913,7 @@
|
|
|
1903
1913
|
"name": "Region",
|
|
1904
1914
|
"status": "stable",
|
|
1905
1915
|
"path": "layout",
|
|
1906
|
-
"summary": "The page region: fluid, edge-to-edge with the fluid page inset (--page-inset). Opt-in centered max cap picked by ROLE (prose/form/feed/content/wide/full), never a global page max-width. The root of nearly every route.",
|
|
1916
|
+
"summary": "The page region: fluid, edge-to-edge with the fluid page inset (--page-inset). Opt-in centered max cap picked by ROLE (prose/form/feed/content/wide/full), never a global page max-width. The root of nearly every route. The inset and cap are classes (px-(--page-inset), max-w-…), so className overrides them at a breakpoint (max-sm:px-0); only safeArea padding is inline.",
|
|
1907
1917
|
"keywords": [
|
|
1908
1918
|
"region",
|
|
1909
1919
|
"page width",
|
|
@@ -2072,7 +2082,7 @@
|
|
|
2072
2082
|
"name": "Grid",
|
|
2073
2083
|
"status": "stable",
|
|
2074
2084
|
"path": "layout",
|
|
2075
|
-
"summary": "CSS Grid within a region. Two modes: fixed/responsive cols count, or minItemWidth so cells breathe with NO breakpoints (the default choice for card grids). A minItemWidth grid keeps its empty columns (track=\"fill\"), so one result stays one cell wide; track=\"fit\" stretches a fixed set across the row.",
|
|
2085
|
+
"summary": "CSS Grid within a region. Two modes: fixed/responsive cols count, or minItemWidth so cells breathe with NO breakpoints (the default choice for card grids). A minItemWidth grid keeps its empty columns (track=\"fill\"), so one result stays one cell wide; track=\"fit\" stretches a fixed set across the row. display is the grid class and the gap a CSS variable, so className utilities win at any breakpoint (hidden md:grid, md:gap-6, lg:grid-cols-3).",
|
|
2076
2086
|
"keywords": [
|
|
2077
2087
|
"grid",
|
|
2078
2088
|
"card grid",
|
|
@@ -2168,7 +2178,7 @@
|
|
|
2168
2178
|
"name": "Columns",
|
|
2169
2179
|
"status": "stable",
|
|
2170
2180
|
"path": "layout",
|
|
2171
|
-
"summary": "An explicit N-column track (default 12) for proportioned layouts — pair with Col spans (list-detail, asymmetric editorial layouts). For uniform card grids prefer Grid minItemWidth.",
|
|
2181
|
+
"summary": "An explicit N-column track (default 12) for proportioned layouts — pair with Col spans (list-detail, asymmetric editorial layouts). For uniform card grids prefer Grid minItemWidth. display is the grid class, so hidden md:grid works; a gap-* utility replaces both gaps and the column-gap cap.",
|
|
2172
2182
|
"keywords": [
|
|
2173
2183
|
"columns",
|
|
2174
2184
|
"12 column",
|
|
@@ -2245,7 +2255,7 @@
|
|
|
2245
2255
|
"name": "Stack",
|
|
2246
2256
|
"status": "stable",
|
|
2247
2257
|
"path": "layout",
|
|
2248
|
-
"summary": "Vertical flow on the 8px rhythm — the default container for stacked content. Gap by role: tight within a group (1.5–2), looser between groups (3–4), generous between sections (6+).",
|
|
2258
|
+
"summary": "Vertical flow on the 8px rhythm — the default container for stacked content. Gap by role: tight within a group (1.5–2), looser between groups (3–4), generous between sections (6+). Layout lives in classes (flex flex-col items-* justify-*) and the gap in a CSS variable, never inline style, so className utilities override any prop at a breakpoint (max-lg:hidden, md:flex-row, md:gap-8).",
|
|
2249
2259
|
"keywords": [
|
|
2250
2260
|
"stack",
|
|
2251
2261
|
"vertical rhythm",
|
|
@@ -2309,7 +2319,7 @@
|
|
|
2309
2319
|
"name": "Row",
|
|
2310
2320
|
"status": "stable",
|
|
2311
2321
|
"path": "layout",
|
|
2312
|
-
"summary": "Horizontal flex flow — inline rows of controls, toolbars, action clusters. Defaults to center-aligned, no wrap.",
|
|
2322
|
+
"summary": "Horizontal flex flow — inline rows of controls, toolbars, action clusters. Defaults to center-aligned, no wrap. Layout lives in classes (flex flex-row items-* justify-*) and the gap in a CSS variable, never inline style, so className utilities override any prop at a breakpoint (hidden md:flex, max-md:flex-col, md:gap-6).",
|
|
2313
2323
|
"keywords": [
|
|
2314
2324
|
"row",
|
|
2315
2325
|
"toolbar",
|
|
@@ -2372,6 +2382,11 @@
|
|
|
2372
2382
|
"label": "Action cluster",
|
|
2373
2383
|
"code": "<Row gap={2} justify=\"end\"><Button variant=\"ghost\">Cancel</Button><Button>Save changes</Button></Row>",
|
|
2374
2384
|
"imports": "import { Button, Row } from \"@sableui/react\""
|
|
2385
|
+
},
|
|
2386
|
+
{
|
|
2387
|
+
"label": "Responsive through className",
|
|
2388
|
+
"code": "<Row justify=\"between\"><span className=\"text-title-sm\">Acme</span><Row gap={1} className=\"max-md:hidden\"><Button variant=\"ghost\" size=\"sm\">Docs</Button><Button variant=\"ghost\" size=\"sm\">Pricing</Button></Row></Row>",
|
|
2389
|
+
"imports": "import { Button, Row } from \"@sableui/react\""
|
|
2375
2390
|
}
|
|
2376
2391
|
]
|
|
2377
2392
|
},
|
|
@@ -2379,7 +2394,7 @@
|
|
|
2379
2394
|
"name": "Spacer",
|
|
2380
2395
|
"status": "stable",
|
|
2381
2396
|
"path": "layout",
|
|
2382
|
-
"summary": "Explicit space on the rhythm, or a flexible grow pusher between siblings (e.g. pushing a footer item to the end of a Row).",
|
|
2397
|
+
"summary": "Explicit space on the rhythm, or a flexible grow pusher between siblings (e.g. pushing a footer item to the end of a Row). The size is a class reading a CSS variable, so className resizes or hides it at a breakpoint.",
|
|
2383
2398
|
"keywords": [
|
|
2384
2399
|
"spacer",
|
|
2385
2400
|
"spacing",
|
|
@@ -2441,7 +2456,7 @@
|
|
|
2441
2456
|
"name": "Center",
|
|
2442
2457
|
"status": "stable",
|
|
2443
2458
|
"path": "layout",
|
|
2444
|
-
"summary": "Horizontally center a block, optionally capping its width (commonly \"prose\" for a reading column inside a wider layout).",
|
|
2459
|
+
"summary": "Horizontally center a block, optionally capping its width (commonly \"prose\" for a reading column inside a wider layout). Centering and cap are classes, so className overrides them at a breakpoint.",
|
|
2445
2460
|
"keywords": [
|
|
2446
2461
|
"center",
|
|
2447
2462
|
"measure",
|
|
@@ -2492,7 +2507,7 @@
|
|
|
2492
2507
|
"name": "AspectBox",
|
|
2493
2508
|
"status": "stable",
|
|
2494
2509
|
"path": "layout",
|
|
2495
|
-
"summary": "Reserves space via aspect-ratio so async media loads with zero layout shift. Wrap images, video, embeds.",
|
|
2510
|
+
"summary": "Reserves space via aspect-ratio so async media loads with zero layout shift. Wrap images, video, embeds. The ratio is a class reading a CSS variable, so className reshapes it at a breakpoint (md:aspect-video).",
|
|
2496
2511
|
"keywords": [
|
|
2497
2512
|
"aspect box",
|
|
2498
2513
|
"aspect ratio",
|
|
@@ -2544,7 +2559,7 @@
|
|
|
2544
2559
|
"name": "Bleed",
|
|
2545
2560
|
"status": "stable",
|
|
2546
2561
|
"path": "layout",
|
|
2547
|
-
"summary": "Escape a width-capped or inset ancestor to span the full CONTENT area (container-query units, so it stops at a fixed Sidebar instead of bleeding under it). Hero bands, profile covers, full-bleed media inside a capped Page.",
|
|
2562
|
+
"summary": "Escape a width-capped or inset ancestor to span the full CONTENT area (container-query units, so it stops at a fixed Sidebar instead of bleeding under it). Hero bands, profile covers, full-bleed media inside a capped Page. Width and margins are classes, so a breakpoint can turn the bleed off (lg:mx-0 lg:w-auto).",
|
|
2548
2563
|
"keywords": [
|
|
2549
2564
|
"bleed",
|
|
2550
2565
|
"full bleed",
|
|
@@ -2589,7 +2604,7 @@
|
|
|
2589
2604
|
"name": "SplitPane",
|
|
2590
2605
|
"status": "stable",
|
|
2591
2606
|
"path": "layout",
|
|
2592
|
-
"summary": "Responsive two-pane layout: side-by-side at/above a breakpoint, stacked (primary first) below. List-detail, media + metadata.",
|
|
2607
|
+
"summary": "Responsive two-pane layout: side-by-side at/above a breakpoint, stacked (primary first) below. List-detail, media + metadata. display is the grid class and the breakpoint rules are layered, so className utilities (hidden lg:grid, gap-8) win.",
|
|
2593
2608
|
"keywords": [
|
|
2594
2609
|
"split pane",
|
|
2595
2610
|
"two pane",
|
|
@@ -2955,6 +2970,308 @@
|
|
|
2955
2970
|
}
|
|
2956
2971
|
]
|
|
2957
2972
|
},
|
|
2973
|
+
{
|
|
2974
|
+
"name": "ActivityHeatmap",
|
|
2975
|
+
"status": "stable",
|
|
2976
|
+
"group": "Charts",
|
|
2977
|
+
"register": "either",
|
|
2978
|
+
"path": "components/activity-heatmap",
|
|
2979
|
+
"summary": "A calendar heatmap: one value per day as a grid of weeks by weekdays — the GitHub contribution graph, for any daily measure. Levels by quartile, categories by source, a real ARIA grid with one tab stop, and a sideways scroll on narrow screens that opens on the latest week.",
|
|
2980
|
+
"keywords": [
|
|
2981
|
+
"heatmap",
|
|
2982
|
+
"calendar heatmap",
|
|
2983
|
+
"contribution graph",
|
|
2984
|
+
"contributions",
|
|
2985
|
+
"activity",
|
|
2986
|
+
"github",
|
|
2987
|
+
"streak",
|
|
2988
|
+
"habit",
|
|
2989
|
+
"daily",
|
|
2990
|
+
"commits",
|
|
2991
|
+
"uptime"
|
|
2992
|
+
],
|
|
2993
|
+
"usage": {
|
|
2994
|
+
"description": "Use ActivityHeatmap to show how often something happened day by day over weeks or months: commits, habits kept, orders, uptime. Pass days as ISO dates with a value; levels, colours, labels, keyboard and tooltips work with no further props. Put it inside a Card and compose any totals beside it (NumberTicker, StatRowBlock). For a value over time that a reader needs to compare precisely, use AreaChart or BarChart; for a month calendar with marked days (a streak calendar), use Calendar.",
|
|
2995
|
+
"bestPractices": [
|
|
2996
|
+
{
|
|
2997
|
+
"guidance": "do",
|
|
2998
|
+
"description": "Pass dates as \"YYYY-MM-DD\" strings. The grid reads them as calendar days, so no time zone can shift a day — never build them with `new Date(...).toISOString()` from a local date."
|
|
2999
|
+
},
|
|
3000
|
+
{
|
|
3001
|
+
"guidance": "do",
|
|
3002
|
+
"description": "Give it a `unit` (\"contribution\" / \"contributions\") so the tooltip and every cell's name say what the number counts."
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
"guidance": "do",
|
|
3006
|
+
"description": "Let the default quantile scale pick levels — one exceptional day then does not wash every other day out. Pass a day's own `level` when the source already has one (GitHub's API)."
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
"guidance": "dont",
|
|
3010
|
+
"description": "Use it for a handful of values or for values a reader must compare exactly; a heatmap shows rhythm, not amounts. Use BarChart.",
|
|
3011
|
+
"instead": [
|
|
3012
|
+
"BarChart",
|
|
3013
|
+
"AreaChart"
|
|
3014
|
+
]
|
|
3015
|
+
},
|
|
3016
|
+
{
|
|
3017
|
+
"guidance": "dont",
|
|
3018
|
+
"description": "Use it for a single month of marked days with a streak; that is Calendar with modifiers.",
|
|
3019
|
+
"instead": [
|
|
3020
|
+
"Calendar"
|
|
3021
|
+
]
|
|
3022
|
+
},
|
|
3023
|
+
{
|
|
3024
|
+
"guidance": "dont",
|
|
3025
|
+
"description": "Fetch inside the component or call GitHub from the browser with a token. Fetch on your server and pass days in; `@sableui/react/adapters/github` converts GitHub's response."
|
|
3026
|
+
}
|
|
3027
|
+
],
|
|
3028
|
+
"anatomy": [
|
|
3029
|
+
{
|
|
3030
|
+
"name": "Weekday labels",
|
|
3031
|
+
"required": false,
|
|
3032
|
+
"description": "Short weekday names beside the rows, outside the scroller so the weeks never slide under them."
|
|
3033
|
+
},
|
|
3034
|
+
{
|
|
3035
|
+
"name": "Month labels",
|
|
3036
|
+
"required": false,
|
|
3037
|
+
"description": "Short month names over each month's first week; they scroll with their weeks."
|
|
3038
|
+
},
|
|
3039
|
+
{
|
|
3040
|
+
"name": "Cell grid",
|
|
3041
|
+
"required": true,
|
|
3042
|
+
"description": "A role=grid table, one gridcell per day, inside a horizontal Scroller that opens on the latest week."
|
|
3043
|
+
},
|
|
3044
|
+
{
|
|
3045
|
+
"name": "Tooltip",
|
|
3046
|
+
"required": false,
|
|
3047
|
+
"description": "The chart tooltip card, anchored to the hovered, focused or tapped day."
|
|
3048
|
+
},
|
|
3049
|
+
{
|
|
3050
|
+
"name": "Legend",
|
|
3051
|
+
"required": false,
|
|
3052
|
+
"description": "A Less–More ramp for intensity, or swatches for categories."
|
|
3053
|
+
}
|
|
3054
|
+
]
|
|
3055
|
+
},
|
|
3056
|
+
"related": {
|
|
3057
|
+
"components": [
|
|
3058
|
+
"Card",
|
|
3059
|
+
"NumberTicker",
|
|
3060
|
+
"BarChart",
|
|
3061
|
+
"Calendar",
|
|
3062
|
+
"Scroller"
|
|
3063
|
+
]
|
|
3064
|
+
},
|
|
3065
|
+
"props": [
|
|
3066
|
+
{
|
|
3067
|
+
"name": "label",
|
|
3068
|
+
"type": "string",
|
|
3069
|
+
"description": "What the grid shows (\"Contributions in 2026\") — its caption and accessible name."
|
|
3070
|
+
},
|
|
3071
|
+
{
|
|
3072
|
+
"name": "data",
|
|
3073
|
+
"type": "ActivityDay[] | Record<string, number>",
|
|
3074
|
+
"description": "The days: `{ date: \"YYYY-MM-DD\", value, level?, category? }[]`, or a map of date to value. Sparse is fine — a missing day is 0; duplicates sum."
|
|
3075
|
+
},
|
|
3076
|
+
{
|
|
3077
|
+
"name": "start",
|
|
3078
|
+
"type": "string",
|
|
3079
|
+
"default": "53 weeks before end",
|
|
3080
|
+
"description": "First day drawn, \"YYYY-MM-DD\". The default starts at the beginning of that week, as GitHub's does."
|
|
3081
|
+
},
|
|
3082
|
+
{
|
|
3083
|
+
"name": "end",
|
|
3084
|
+
"type": "string",
|
|
3085
|
+
"default": "today",
|
|
3086
|
+
"description": "Last day drawn. Days after today inside the range are drawn hollow and skipped by the keyboard."
|
|
3087
|
+
},
|
|
3088
|
+
{
|
|
3089
|
+
"name": "year",
|
|
3090
|
+
"type": "number",
|
|
3091
|
+
"description": "Draw Jan 1 – Dec 31 of this year. Wins over start / end."
|
|
3092
|
+
},
|
|
3093
|
+
{
|
|
3094
|
+
"name": "weekStart",
|
|
3095
|
+
"type": "0 | 1",
|
|
3096
|
+
"default": "the locale's",
|
|
3097
|
+
"description": "0 puts Sunday on the top row, 1 Monday."
|
|
3098
|
+
},
|
|
3099
|
+
{
|
|
3100
|
+
"name": "levels",
|
|
3101
|
+
"type": "number",
|
|
3102
|
+
"default": "5",
|
|
3103
|
+
"description": "How many levels, including the empty level 0."
|
|
3104
|
+
},
|
|
3105
|
+
{
|
|
3106
|
+
"name": "levelScale",
|
|
3107
|
+
"type": "\"quantile\" | \"linear\" | number[] | ((value, stats) => number)",
|
|
3108
|
+
"default": "\"quantile\"",
|
|
3109
|
+
"description": "How a value becomes a level. Quantile splits the positive values evenly, as GitHub does; an array gives each level's lower bound from 1 up; a function decides. A day's own `level` always wins."
|
|
3110
|
+
},
|
|
3111
|
+
{
|
|
3112
|
+
"name": "tone",
|
|
3113
|
+
"type": "ChartTone",
|
|
3114
|
+
"default": "\"1\"",
|
|
3115
|
+
"description": "The hue of the intensity ramp. Decorative tones lead, as on every chart; \"success\" gives the GitHub look."
|
|
3116
|
+
},
|
|
3117
|
+
{
|
|
3118
|
+
"name": "categories",
|
|
3119
|
+
"type": "ChartCategories",
|
|
3120
|
+
"description": "Colour days by `day.category` (label, tone or colour per key). Pair with `levels={2}` for flat category colours."
|
|
3121
|
+
},
|
|
3122
|
+
{
|
|
3123
|
+
"name": "unit",
|
|
3124
|
+
"type": "string | { one: string; other: string }",
|
|
3125
|
+
"description": "Names the value in the tooltip and each cell's accessible name, pluralised with Intl.PluralRules."
|
|
3126
|
+
},
|
|
3127
|
+
{
|
|
3128
|
+
"name": "formatValue",
|
|
3129
|
+
"type": "(value: number) => string",
|
|
3130
|
+
"default": "full to 9,999, compact past",
|
|
3131
|
+
"description": "Formats a value wherever it is shown or read."
|
|
3132
|
+
},
|
|
3133
|
+
{
|
|
3134
|
+
"name": "tooltip",
|
|
3135
|
+
"type": "boolean | ((day: ActivityHeatmapDay) => ReactNode)",
|
|
3136
|
+
"default": "true",
|
|
3137
|
+
"description": "Show the tooltip on hover, focus and tap; a function replaces its content on the same card."
|
|
3138
|
+
},
|
|
3139
|
+
{
|
|
3140
|
+
"name": "legend",
|
|
3141
|
+
"type": "boolean",
|
|
3142
|
+
"default": "true",
|
|
3143
|
+
"description": "The Less–More ramp for intensity, swatches for categories."
|
|
3144
|
+
},
|
|
3145
|
+
{
|
|
3146
|
+
"name": "legendLabels",
|
|
3147
|
+
"type": "[string, string]",
|
|
3148
|
+
"default": "[\"Less\", \"More\"]",
|
|
3149
|
+
"description": "The ramp's end words."
|
|
3150
|
+
},
|
|
3151
|
+
{
|
|
3152
|
+
"name": "monthLabels",
|
|
3153
|
+
"type": "boolean",
|
|
3154
|
+
"default": "true",
|
|
3155
|
+
"description": "Short month names over each month's first week."
|
|
3156
|
+
},
|
|
3157
|
+
{
|
|
3158
|
+
"name": "weekdayLabels",
|
|
3159
|
+
"type": "\"alternate\" | \"all\" | \"none\"",
|
|
3160
|
+
"default": "\"alternate\"",
|
|
3161
|
+
"description": "Which weekday names to show beside the rows; alternate is Mon, Wed, Fri."
|
|
3162
|
+
},
|
|
3163
|
+
{
|
|
3164
|
+
"name": "size",
|
|
3165
|
+
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
3166
|
+
"default": "\"md\"",
|
|
3167
|
+
"description": "The smallest a cell may get (10, 12, 14 px) before the grid scrolls instead of shrinking."
|
|
3168
|
+
},
|
|
3169
|
+
{
|
|
3170
|
+
"name": "stretch",
|
|
3171
|
+
"type": "boolean",
|
|
3172
|
+
"default": "true",
|
|
3173
|
+
"description": "Grow cells to fill the width, up to 20 px. false keeps them at `size`."
|
|
3174
|
+
},
|
|
3175
|
+
{
|
|
3176
|
+
"name": "activeDate",
|
|
3177
|
+
"type": "string | null",
|
|
3178
|
+
"description": "The hovered or focused day, controlled. Pair with defaultActiveDate / onActiveDateChange."
|
|
3179
|
+
},
|
|
3180
|
+
{
|
|
3181
|
+
"name": "selectedDate",
|
|
3182
|
+
"type": "string | null",
|
|
3183
|
+
"description": "The selected day, controlled. Selection is on only when onSelectDate is set."
|
|
3184
|
+
},
|
|
3185
|
+
{
|
|
3186
|
+
"name": "onSelectDate",
|
|
3187
|
+
"type": "(date: string) => void",
|
|
3188
|
+
"description": "Called when a day is clicked or chosen with Enter / Space — to filter a feed below, as GitHub does."
|
|
3189
|
+
},
|
|
3190
|
+
{
|
|
3191
|
+
"name": "renderCell",
|
|
3192
|
+
"type": "(day, cell) => ReactNode",
|
|
3193
|
+
"description": "Draw something over a cell's fill — an icon for a milestone, a reward, an outage."
|
|
3194
|
+
},
|
|
3195
|
+
{
|
|
3196
|
+
"name": "loading",
|
|
3197
|
+
"type": "boolean",
|
|
3198
|
+
"default": "false",
|
|
3199
|
+
"description": "Draw the empty grid, shimmering, while data loads."
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
"name": "motion",
|
|
3203
|
+
"type": "\"full\" | \"subtle\" | \"off\"",
|
|
3204
|
+
"default": "\"full\"",
|
|
3205
|
+
"description": "Turns the arrival down; `subtle` shows the finished grid at once."
|
|
3206
|
+
},
|
|
3207
|
+
{
|
|
3208
|
+
"name": "locale",
|
|
3209
|
+
"type": "string",
|
|
3210
|
+
"default": "runtime locale",
|
|
3211
|
+
"description": "Month and weekday names, numbers and plurals."
|
|
3212
|
+
}
|
|
3213
|
+
],
|
|
3214
|
+
"tokensUsed": [
|
|
3215
|
+
"foreground",
|
|
3216
|
+
"muted-foreground",
|
|
3217
|
+
"border-strong",
|
|
3218
|
+
"ring",
|
|
3219
|
+
"tone-1-mark",
|
|
3220
|
+
"tone-2-mark",
|
|
3221
|
+
"tone-3-mark",
|
|
3222
|
+
"success-mark",
|
|
3223
|
+
"tone-info-mark"
|
|
3224
|
+
],
|
|
3225
|
+
"motion": [
|
|
3226
|
+
{
|
|
3227
|
+
"slot": "arrival",
|
|
3228
|
+
"recipe": "custom",
|
|
3229
|
+
"part": "Cell grid",
|
|
3230
|
+
"why": "Weeks fade in left to right as the grid first scrolls into view, staggered on the chart motion helpers; expressive, so subtle shows the finished grid."
|
|
3231
|
+
},
|
|
3232
|
+
{
|
|
3233
|
+
"slot": "response",
|
|
3234
|
+
"recipe": "custom",
|
|
3235
|
+
"part": "Cell",
|
|
3236
|
+
"why": "The active day takes a ring outline at once and the tooltip glides to it; the other cells stay still — dimming hundreds of cells reads as a flash."
|
|
3237
|
+
},
|
|
3238
|
+
{
|
|
3239
|
+
"slot": "transition",
|
|
3240
|
+
"recipe": "custom",
|
|
3241
|
+
"part": "Cell",
|
|
3242
|
+
"why": "When data or the range changes, each cell's fill cross-fades on the base duration token, so the reader sees which days changed."
|
|
3243
|
+
},
|
|
3244
|
+
{
|
|
3245
|
+
"slot": "activity",
|
|
3246
|
+
"recipe": "motion-shimmer",
|
|
3247
|
+
"part": "Cell grid",
|
|
3248
|
+
"why": "`loading` shimmers the empty grid until days arrive."
|
|
3249
|
+
}
|
|
3250
|
+
],
|
|
3251
|
+
"a11y": [
|
|
3252
|
+
"The grid is a role=grid table, as GitHub's is: weekday row headers and month column headers carry full names for screen readers, and the caption is `label`.",
|
|
3253
|
+
"One tab stop. Arrows move a day or a week; Home / End go to the ends of the row, Page Up / Page Down to the ends of the column, Ctrl+Home / Ctrl+End to the first and last day. Enter or Space selects when onSelectDate is set; Escape hides the tooltip.",
|
|
3254
|
+
"Each cell is named with its date and value (\"Tuesday, October 14, 2026: 12 contributions\") and described by its level's words, which the legend repeats visually.",
|
|
3255
|
+
"Levels rise in lightness, so they read without colour; under forced colours each level draws as a larger inner square."
|
|
3256
|
+
],
|
|
3257
|
+
"examples": [
|
|
3258
|
+
{
|
|
3259
|
+
"label": "Contributions, last year",
|
|
3260
|
+
"code": "const days = [\n\t{ date: \"2026-10-12\", value: 4 },\n\t{ date: \"2026-10-13\", value: 11 },\n\t{ date: \"2026-10-14\", value: 2 },\n]\n\n<ActivityHeatmap label=\"Contributions in the last year\" data={days} tone=\"success\" unit={{ one: \"contribution\", other: \"contributions\" }} />",
|
|
3261
|
+
"imports": "import { ActivityHeatmap } from \"@sableui/react\""
|
|
3262
|
+
},
|
|
3263
|
+
{
|
|
3264
|
+
"label": "Two sources, in a card",
|
|
3265
|
+
"code": "const github = [{ date: \"2026-10-13\", value: 6 }]\nconst codex = [{ date: \"2026-10-13\", value: 1 }, { date: \"2026-10-14\", value: 3 }]\n\n<Card>\n\t<CardHeader>\n\t\t<CardTitle>Building, day by day</CardTitle>\n\t</CardHeader>\n\t<CardContent>\n\t\t<ActivityHeatmap\n\t\t\tlabel=\"GitHub and Codex activity in 2026\"\n\t\t\tyear={2026}\n\t\t\tlevels={2}\n\t\t\tdata={mergeActivity({ github, codex })}\n\t\t\tcategories={{ github: { label: \"GitHub\", tone: \"success\" }, codex: { label: \"Codex\", tone: \"info\" }, both: { label: \"Both\", tone: \"2\" } }}\n\t\t/>\n\t</CardContent>\n</Card>",
|
|
3266
|
+
"imports": "import { ActivityHeatmap, Card, CardContent, CardHeader, CardTitle, mergeActivity } from \"@sableui/react\""
|
|
3267
|
+
},
|
|
3268
|
+
{
|
|
3269
|
+
"label": "Selecting a day",
|
|
3270
|
+
"code": "const [day, setDay] = useState<string | null>(null)\n\n<ActivityHeatmap label=\"Orders per day\" data={{ \"2026-10-13\": 18, \"2026-10-14\": 25 }} unit=\"orders\" selectedDate={day} onSelectDate={setDay} />",
|
|
3271
|
+
"imports": "import { ActivityHeatmap } from \"@sableui/react\""
|
|
3272
|
+
}
|
|
3273
|
+
]
|
|
3274
|
+
},
|
|
2958
3275
|
{
|
|
2959
3276
|
"name": "AppHeader",
|
|
2960
3277
|
"status": "stable",
|
|
@@ -7252,6 +7569,12 @@
|
|
|
7252
7569
|
"name": "prompt",
|
|
7253
7570
|
"type": "string",
|
|
7254
7571
|
"description": "A shell prompt (\"$\", \">\"). Makes it a one-line command: the prompt is dimmed and unselectable, the line truncates, and copy takes only code."
|
|
7572
|
+
},
|
|
7573
|
+
{
|
|
7574
|
+
"name": "wrap",
|
|
7575
|
+
"type": "boolean",
|
|
7576
|
+
"default": "false",
|
|
7577
|
+
"description": "Wrap long lines instead of scrolling them. For prose in a code face (a prompt to paste into an agent, a log line); code keeps scrolling, since wrapping breaks its indentation."
|
|
7255
7578
|
}
|
|
7256
7579
|
],
|
|
7257
7580
|
"subcomponents": [
|
|
@@ -9501,6 +9824,18 @@
|
|
|
9501
9824
|
"InlineStatus"
|
|
9502
9825
|
]
|
|
9503
9826
|
},
|
|
9827
|
+
{
|
|
9828
|
+
"guidance": "do",
|
|
9829
|
+
"description": "Open a dialog from your own button with controlled open/onOpenChange when the trigger is not a DialogTrigger (a menu item, a row action, a keyboard shortcut). Focus returns on close to whatever held it when the dialog opened, with no finalFocus wiring; pass finalFocus only to send it somewhere else."
|
|
9830
|
+
},
|
|
9831
|
+
{
|
|
9832
|
+
"guidance": "do",
|
|
9833
|
+
"description": "Set keepMounted on DialogContent for a draft the user may come back to (a compose form, a long comment, a half-filled request). Closing by Esc or a stray click then hides the dialog instead of unmounting it, so the fields are still filled when it reopens. Clear the draft yourself on submit or discard."
|
|
9834
|
+
},
|
|
9835
|
+
{
|
|
9836
|
+
"guidance": "dont",
|
|
9837
|
+
"description": "Set keepMounted by default. A dialog that should open fresh (a confirmation, a create form after a successful save) would show stale input, and live content (polling, video) keeps running while hidden."
|
|
9838
|
+
},
|
|
9504
9839
|
{
|
|
9505
9840
|
"guidance": "do",
|
|
9506
9841
|
"description": "Use the AlertDialog family for a confirmation that must be answered (discard changes, leave without saving). It looks identical but is role=\"alertdialog\" and a backdrop click does not dismiss it — only Cancel, Esc, or the action. For irreversible deletes, use DestructiveActionModal, which is built on it."
|
|
@@ -9554,14 +9889,30 @@
|
|
|
9554
9889
|
{
|
|
9555
9890
|
"name": "Dialog (Root)",
|
|
9556
9891
|
"type": "Base UI Dialog.Root props (open, defaultOpen, onOpenChange, modal)",
|
|
9557
|
-
"description": "Controls open state. Uncontrolled by default."
|
|
9892
|
+
"description": "Controls open state. Uncontrolled by default. Records the element that holds focus when the dialog opens (through a DialogTrigger, or your own button flipping a controlled open) and returns focus there on close."
|
|
9893
|
+
},
|
|
9894
|
+
{
|
|
9895
|
+
"name": "open",
|
|
9896
|
+
"type": "boolean",
|
|
9897
|
+
"description": "Controlled open state. Pair with onOpenChange — for a dialog opened from your own button instead of a DialogTrigger."
|
|
9898
|
+
},
|
|
9899
|
+
{
|
|
9900
|
+
"name": "defaultOpen",
|
|
9901
|
+
"type": "boolean",
|
|
9902
|
+
"default": "false",
|
|
9903
|
+
"description": "Initial open state when uncontrolled."
|
|
9904
|
+
},
|
|
9905
|
+
{
|
|
9906
|
+
"name": "onOpenChange",
|
|
9907
|
+
"type": "(open: boolean, eventDetails) => void",
|
|
9908
|
+
"description": "Called when the dialog asks to open or close (a trigger, Esc, an outside click, DialogClose)."
|
|
9558
9909
|
}
|
|
9559
9910
|
],
|
|
9560
9911
|
"subcomponents": [
|
|
9561
9912
|
{
|
|
9562
9913
|
"name": "DialogContent",
|
|
9563
|
-
"type": "Base UI Popup props + size?: \"default\" | \"fullscreen\" + showCloseButton?: boolean + scroll?: boolean + className",
|
|
9564
|
-
"description": "Backdrop + popup; portals to <body>. Composes DialogOverlay by default. size=\"fullscreen\" is the media viewer: a near-opaque scrim (bg-overlay/95), an edge-to-edge popup that centres its children with no card chrome, motion-fade instead of motion-pop, and a safe-area-aware floating close button (Button variant=\"floating\"); at that size showCloseButton defaults to true and scroll to false, and the media fits itself with max-h-full max-w-full object-contain. At the default size, showCloseButton (defaults to false) renders a built-in close (X) button in the top-end corner, and the header leaves room for it (Icon name=\"xmark\"). The popup caps at the viewport height less a 1rem margin and wraps its children in a scrolling region, so a tall dialog scrolls instead of overflowing a viewport whose own scroll is locked; pass scroll={false} when the dialog owns its internal layout (a pinned header over a scrolling body) and supplies its own min-h-0 scroll region."
|
|
9914
|
+
"type": "Base UI Popup props (initialFocus, finalFocus) + size?: \"default\" | \"fullscreen\" + showCloseButton?: boolean + scroll?: boolean + keepMounted?: boolean + className",
|
|
9915
|
+
"description": "Backdrop + popup; portals to <body>. Composes DialogOverlay by default. size=\"fullscreen\" is the media viewer: a near-opaque scrim (bg-overlay/95), an edge-to-edge popup that centres its children with no card chrome, motion-fade instead of motion-pop, and a safe-area-aware floating close button (Button variant=\"floating\"); at that size showCloseButton defaults to true and scroll to false, and the media fits itself with max-h-full max-w-full object-contain. At the default size, showCloseButton (defaults to false) renders a built-in close (X) button in the top-end corner, and the header leaves room for it (Icon name=\"xmark\"). The popup caps at the viewport height less a 1rem margin and wraps its children in a scrolling region, so a tall dialog scrolls instead of overflowing a viewport whose own scroll is locked; pass scroll={false} when the dialog owns its internal layout (a pinned header over a scrolling body) and supplies its own min-h-0 scroll region. keepMounted (default false) keeps the content mounted and hidden while closed, so a draft survives close and reopen. finalFocus defaults to the element focused when the dialog opened, trigger or not; an explicit finalFocus (a ref, a function, or false) wins."
|
|
9565
9916
|
},
|
|
9566
9917
|
{
|
|
9567
9918
|
"name": "DialogOverlay",
|
|
@@ -9601,7 +9952,7 @@
|
|
|
9601
9952
|
{
|
|
9602
9953
|
"name": "AlertDialog (Root)",
|
|
9603
9954
|
"type": "Base UI AlertDialog.Root props (open, defaultOpen, onOpenChange)",
|
|
9604
|
-
"description": "Root for a confirmation that needs an answer. Always modal; the popup is role=\"alertdialog\" and a click outside does not dismiss — only an AlertDialogClose, an action that sets open to false, or Esc."
|
|
9955
|
+
"description": "Root for a confirmation that needs an answer. Always modal; the popup is role=\"alertdialog\" and a click outside does not dismiss — only an AlertDialogClose, an action that sets open to false, or Esc. Returns focus on close to the element focused when it opened, exactly as Dialog does."
|
|
9605
9956
|
},
|
|
9606
9957
|
{
|
|
9607
9958
|
"name": "AlertDialogTrigger",
|
|
@@ -9666,6 +10017,8 @@
|
|
|
9666
10017
|
"a11y": [
|
|
9667
10018
|
"Base UI provides focus trapping, scroll lock, aria-modal, and labelled-by wiring (DialogTitle).",
|
|
9668
10019
|
"Content taller than the viewport scrolls inside the dialog (thin scrollbar, contained overscroll) rather than overflowing off screen — the page behind is scroll-locked, so anything past the edge would be unreachable.",
|
|
10020
|
+
"On close, focus returns to the element that held it when the dialog opened, whether a DialogTrigger opened it or your own button set a controlled open. It is captured before the content mounts, so an autoFocus field inside cannot displace it. If that element has left the page, Base UI's default (the trigger, if any) applies; an explicit finalFocus on the content always wins. AlertDialog and Sheet behave the same.",
|
|
10021
|
+
"keepMounted hides closed content with the hidden attribute, so a kept draft is out of the accessibility tree and the tab order until the dialog reopens.",
|
|
9669
10022
|
"Esc and click-outside dismiss by default. AlertDialog keeps Esc but ignores click-outside, and its popup is role=\"alertdialog\".",
|
|
9670
10023
|
"Animate via data-[starting-style]/data-[ending-style] attributes.",
|
|
9671
10024
|
"size=\"fullscreen\" has no title chrome, so name the popup yourself: aria-label on DialogContent (or a visually hidden DialogTitle), plus alt text on the media — the zoomed image is a separate image to assistive technology. Its built-in close button carries aria-label=\"Close\" and clears the safe-area insets (notch, home indicator)."
|
|
@@ -9691,6 +10044,11 @@
|
|
|
9691
10044
|
"code": "<Dialog><DialogTrigger render={<Button variant=\"secondary\">Rename collection</Button>} /><DialogContent showCloseButton><DialogHeader><DialogTitle>Rename collection</DialogTitle><DialogDescription>Update the display name shown to collectors.</DialogDescription></DialogHeader><Form><Field label=\"Collection name\"><Input defaultValue=\"Midnight Archive\" /></Field><DialogFooter><DialogClose render={<Button variant=\"secondary\">Cancel</Button>} /><Button type=\"submit\">Save changes</Button></DialogFooter></Form></DialogContent></Dialog>",
|
|
9692
10045
|
"imports": "import { Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Field, Form, Input } from \"@sableui/react\""
|
|
9693
10046
|
},
|
|
10047
|
+
{
|
|
10048
|
+
"label": "Opened from your own button, keeping a draft",
|
|
10049
|
+
"code": "const [open, setOpen] = useState(false)\n\n<Button variant=\"secondary\" onClick={() => setOpen(true)}>New message</Button>\n<Dialog open={open} onOpenChange={setOpen}>\n\t<DialogContent keepMounted showCloseButton>\n\t\t<DialogHeader>\n\t\t\t<DialogTitle>New message</DialogTitle>\n\t\t\t<DialogDescription>Close it any time. Your draft is here when you come back.</DialogDescription>\n\t\t</DialogHeader>\n\t\t<Field label=\"Message\"><Textarea /></Field>\n\t\t<DialogFooter>\n\t\t\t<DialogClose render={<Button variant=\"secondary\">Close</Button>} />\n\t\t\t<Button onClick={() => setOpen(false)}>Send</Button>\n\t\t</DialogFooter>\n\t</DialogContent>\n</Dialog>",
|
|
10050
|
+
"imports": "import { Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Field, Textarea } from \"@sableui/react\""
|
|
10051
|
+
},
|
|
9694
10052
|
{
|
|
9695
10053
|
"label": "Destructive confirmation",
|
|
9696
10054
|
"code": "<AlertDialog><AlertDialogTrigger render={<Button variant=\"destructive\">Delete project</Button>} /><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Delete project?</AlertDialogTitle><AlertDialogDescription>This removes the project and cannot be undone.</AlertDialogDescription></AlertDialogHeader><AlertDialogFooter><AlertDialogClose render={<Button variant=\"secondary\">Cancel</Button>} /><Button variant=\"destructive\" onClick={deleteProject}>Delete project</Button></AlertDialogFooter></AlertDialogContent></AlertDialog>",
|
|
@@ -12865,6 +13223,10 @@
|
|
|
12865
13223
|
"guidance": "do",
|
|
12866
13224
|
"description": "Leave `alt` empty when a visible title sits next to the image — a tile, a row — and describe it when the image stands alone. A set `alt` also names the fallback, so the description survives a broken URL."
|
|
12867
13225
|
},
|
|
13226
|
+
{
|
|
13227
|
+
"guidance": "do",
|
|
13228
|
+
"description": "Set `position` when a cover crop would cut off the subject — a face high in a portrait, a product at one edge. Store the focus with the image (`\"35% 50%\"`) rather than re-cropping the file."
|
|
13229
|
+
},
|
|
12868
13230
|
{
|
|
12869
13231
|
"guidance": "dont",
|
|
12870
13232
|
"description": "Do not hand-write `<img onError={() => setBroken(true)}>`. An image that fails before hydration never fires the handler on a prerendered page; MediaImage preloads off the DOM and catches it."
|
|
@@ -12911,6 +13273,12 @@
|
|
|
12911
13273
|
"default": "\"cover\"",
|
|
12912
13274
|
"description": "`cover` crops to fill (artwork, photos); `contain` letterboxes on the muted surface (a logo, a product that must not be cropped)."
|
|
12913
13275
|
},
|
|
13276
|
+
{
|
|
13277
|
+
"name": "position",
|
|
13278
|
+
"type": "CSSProperties[\"objectPosition\"]",
|
|
13279
|
+
"default": "\"center\"",
|
|
13280
|
+
"description": "Where the image sits in its frame — CSS `object-position`, the sibling of `fit`. Under `cover` it is the part the crop keeps (`\"50% 30%\"` for a face, `\"bottom\"` for a horizon); under `contain` it is the side the letterbox gives way to."
|
|
13281
|
+
},
|
|
12914
13282
|
{
|
|
12915
13283
|
"name": "srcSet",
|
|
12916
13284
|
"type": "string",
|
|
@@ -13000,6 +13368,11 @@
|
|
|
13000
13368
|
"label": "Logo, never cropped",
|
|
13001
13369
|
"code": "<AspectBox ratio={16 / 9} className=\"rounded-lg\">\n\t<MediaImage src={brand.logo} alt={brand.name} fit=\"contain\" />\n</AspectBox>",
|
|
13002
13370
|
"imports": "import { AspectBox, MediaImage } from \"@sableui/react\""
|
|
13371
|
+
},
|
|
13372
|
+
{
|
|
13373
|
+
"label": "Crop focus per image",
|
|
13374
|
+
"code": "<AspectBox ratio={1} className=\"rounded-md\">\n\t<MediaImage src={project.thumbnail} seed={project.title} position={project.focus ?? \"center\"} />\n</AspectBox>",
|
|
13375
|
+
"imports": "import { AspectBox, MediaImage } from \"@sableui/react\""
|
|
13003
13376
|
}
|
|
13004
13377
|
]
|
|
13005
13378
|
},
|
|
@@ -13139,6 +13512,12 @@
|
|
|
13139
13512
|
"type": "string | number",
|
|
13140
13513
|
"description": "Seeds the fallback art. Defaults to `title` when it is a string."
|
|
13141
13514
|
},
|
|
13515
|
+
{
|
|
13516
|
+
"name": "position",
|
|
13517
|
+
"type": "CSSProperties[\"objectPosition\"]",
|
|
13518
|
+
"default": "\"center\"",
|
|
13519
|
+
"description": "Which part of the cover the crop keeps — CSS `object-position`, forwarded to the cover's MediaImage: `\"50% 30%\"` for a face, `\"top\"` for a poster's title. Store it with the image when each one frames differently."
|
|
13520
|
+
},
|
|
13142
13521
|
{
|
|
13143
13522
|
"name": "aspect",
|
|
13144
13523
|
"type": "\"square\" | \"video\" | \"poster\" | number",
|
|
@@ -13273,6 +13652,11 @@
|
|
|
13273
13652
|
"code": "<Grid minItemWidth=\"8rem\" gap={2}>\n\t{photos.map((photo) => (\n\t\t<MediaTile key={photo.id} size=\"sm\" title={photo.caption} src={photo.url} alt={photo.description} />\n\t))}\n</Grid>",
|
|
13274
13653
|
"imports": "import { Grid, MediaTile } from \"@sableui/react\""
|
|
13275
13654
|
},
|
|
13655
|
+
{
|
|
13656
|
+
"label": "Portfolio grid with crop focus",
|
|
13657
|
+
"code": "<Grid minItemWidth=\"16rem\" gap={4}>\n\t{projects.map((project) => (\n\t\t<MediaTile\n\t\t\tkey={project.slug}\n\t\t\taspect=\"video\"\n\t\t\ttitle={project.title}\n\t\t\tsubtitle={project.client}\n\t\t\tsrc={project.thumbnail}\n\t\t\tposition={project.focus}\n\t\t\thref={`/work/${project.slug}`}\n\t\t/>\n\t))}\n</Grid>",
|
|
13658
|
+
"imports": "import { Grid, MediaTile } from \"@sableui/react\""
|
|
13659
|
+
},
|
|
13276
13660
|
{
|
|
13277
13661
|
"label": "Poster shelf",
|
|
13278
13662
|
"code": "<MediaTile aspect=\"poster\" size=\"lg\" titleAs=\"h3\" title=\"The Long Dark\" subtitle=\"2026 · Drama\" href=\"/films/the-long-dark\" />",
|
|
@@ -15485,7 +15869,11 @@
|
|
|
15485
15869
|
"page header",
|
|
15486
15870
|
"title",
|
|
15487
15871
|
"lede",
|
|
15488
|
-
"page actions"
|
|
15872
|
+
"page actions",
|
|
15873
|
+
"page title size",
|
|
15874
|
+
"display title",
|
|
15875
|
+
"editorial header",
|
|
15876
|
+
"hero title"
|
|
15489
15877
|
],
|
|
15490
15878
|
"usage": {
|
|
15491
15879
|
"description": "Use PageHeader to establish a page's title, supporting description, and primary page actions.",
|
|
@@ -15498,6 +15886,14 @@
|
|
|
15498
15886
|
"guidance": "do",
|
|
15499
15887
|
"description": "Keep the title literal and put explanation in the lede or nearby content."
|
|
15500
15888
|
},
|
|
15889
|
+
{
|
|
15890
|
+
"guidance": "do",
|
|
15891
|
+
"description": "Use size=\"lg\" for an editorial page (a personal site, a marketing page, an article index): a display-tier title over a body-lg lede. Pair it with SectionHeader size=\"lg\" for the sections below, so the steps stay display → heading-2 → body."
|
|
15892
|
+
},
|
|
15893
|
+
{
|
|
15894
|
+
"guidance": "dont",
|
|
15895
|
+
"description": "Use size=\"lg\" on product routes (settings, lists, dashboards). Display type is never product chrome: there it competes with the content it frames. Keep md, and do not restyle the title with a display class through className either."
|
|
15896
|
+
},
|
|
15501
15897
|
{
|
|
15502
15898
|
"guidance": "dont",
|
|
15503
15899
|
"description": "Do not use PageHeader inside repeated cards, dialogs, or compact panels."
|
|
@@ -15546,8 +15942,20 @@
|
|
|
15546
15942
|
"type": "\"h1\" | \"h2\"",
|
|
15547
15943
|
"default": "\"h1\"",
|
|
15548
15944
|
"description": "The title's heading element. Keep h1 — the route's one h1. Use h2 only when something above already owns the h1: a shell that renders the route title, or a page showing a PageHeader as an example."
|
|
15945
|
+
},
|
|
15946
|
+
{
|
|
15947
|
+
"name": "size",
|
|
15948
|
+
"type": "\"md\" | \"lg\"",
|
|
15949
|
+
"default": "\"md\"",
|
|
15950
|
+
"description": "The title's scale. md: text-heading-2 over a text-body-sm description, for product pages. lg: text-display-lg over a text-body-lg lede capped at the prose measure, with more space between and below, for an editorial page (personal site, marketing page, article index). Beside actions or a back control, a title line taller than the controls lifts by half the difference, so its first line stays centred on them."
|
|
15549
15951
|
}
|
|
15550
15952
|
],
|
|
15953
|
+
"variants": {
|
|
15954
|
+
"size": [
|
|
15955
|
+
"md",
|
|
15956
|
+
"lg"
|
|
15957
|
+
]
|
|
15958
|
+
},
|
|
15551
15959
|
"tokensUsed": [
|
|
15552
15960
|
"foreground",
|
|
15553
15961
|
"muted-foreground"
|
|
@@ -15562,6 +15970,11 @@
|
|
|
15562
15970
|
"code": "<PageHeader title=\"Storage credits\" description=\"Fund permanent storage.\" actions={<Button>Top up</Button>} />",
|
|
15563
15971
|
"imports": "import { Button, PageHeader } from \"@sableui/react\""
|
|
15564
15972
|
},
|
|
15973
|
+
{
|
|
15974
|
+
"label": "Editorial page title (size lg)",
|
|
15975
|
+
"code": "<PageHeader size=\"lg\" title=\"Field notes\" description=\"Essays on making things slowly, from a studio by the sea.\" actions={<Button variant=\"outline\">Subscribe</Button>} />",
|
|
15976
|
+
"imports": "import { Button, PageHeader } from \"@sableui/react\""
|
|
15977
|
+
},
|
|
15565
15978
|
{
|
|
15566
15979
|
"label": "One per page, inside the region",
|
|
15567
15980
|
"code": "// PageHeader introduces the current route: title, optional description, and the\n// actions that act on THIS page.\n\n<Region max=\"content\">\n <PageHeader\n title=\"Invoices\"\n description=\"Every charge on this account.\"\n actions={<Button>Export</Button>}\n />\n <p className=\"text-body-md text-muted-foreground\">Page content.</p>\n</Region>\n\n// ✗ Outside the Region. The header then sits at a different measure from the\n// content it introduces, and the page reads as two documents.\n// ✗ Two on one page. A second one is a section heading: a SectionHeader\n// (an h2 on text-heading-3, same title row and actions), not another PageHeader.\n// ✗ Global actions in it (account, search). Those persist across routes and\n// belong in AppHeader.",
|
|
@@ -16486,7 +16899,7 @@
|
|
|
16486
16899
|
"group": "Badges & pills",
|
|
16487
16900
|
"register": "page",
|
|
16488
16901
|
"path": "components/pill",
|
|
16489
|
-
"summary": "A chip someone clicks: a filter, a tag choice, an option in a row. Always a button with aria-pressed. appearance=\"solid\" (filters; selected lifts onto the primary surface) or \"outline\" (quiet chips that annotate content, like reactions); size md or sm. A static label is a Badge.",
|
|
16902
|
+
"summary": "A chip someone clicks: a filter, a tag choice, an option in a row. Always a button with aria-pressed. appearance=\"solid\" (filters; selected lifts onto the primary surface) or \"outline\" (quiet chips that annotate content, like reactions); size lg, md or sm. A static label is a Badge.",
|
|
16490
16903
|
"keywords": [
|
|
16491
16904
|
"pill",
|
|
16492
16905
|
"chip",
|
|
@@ -16506,6 +16919,10 @@
|
|
|
16506
16919
|
"guidance": "do",
|
|
16507
16920
|
"description": "Reflect the current choice with selected, so the active filter reads at a glance."
|
|
16508
16921
|
},
|
|
16922
|
+
{
|
|
16923
|
+
"guidance": "do",
|
|
16924
|
+
"description": "In a toolbar where filter chips share a row with a search field (\"All 8 · Products 5 · Open source 2 … [Find a project]\"), pair Pill size=\"lg\" with SearchInput size=\"sm\", and Button size=\"sm\" for any action beside them. All three stand on --control-sm (32px, 28 compact), the rung the spacing doctrine gives toolbar controls, so the row is one height in either density. Pill md (28px) has no text field of its height in the page register, so md beside a field always reads as a mismatch."
|
|
16925
|
+
},
|
|
16509
16926
|
{
|
|
16510
16927
|
"guidance": "dont",
|
|
16511
16928
|
"description": "Do not use Pill as a static label. A pressable-looking chip that does nothing is the most common cause of dead clicks in a filter row — that is a Badge.",
|
|
@@ -16519,14 +16936,15 @@
|
|
|
16519
16936
|
},
|
|
16520
16937
|
{
|
|
16521
16938
|
"guidance": "dont",
|
|
16522
|
-
"description": "Do not restyle a Pill with border or background classes to get a quieter chip. That is appearance=\"outline\" (and size=\"sm\" for a small one); an override is a look that exists nowhere else in the system."
|
|
16939
|
+
"description": "Do not restyle a Pill with border or background classes to get a quieter chip. That is appearance=\"outline\" (and size=\"sm\" for a small one); an override is a look that exists nowhere else in the system. Likewise never set h-* on a Pill to match a neighbour: pick the size that shares its rung."
|
|
16523
16940
|
}
|
|
16524
16941
|
]
|
|
16525
16942
|
},
|
|
16526
16943
|
"related": {
|
|
16527
16944
|
"components": [
|
|
16528
16945
|
"Badge",
|
|
16529
|
-
"ToggleGroup"
|
|
16946
|
+
"ToggleGroup",
|
|
16947
|
+
"SearchInput"
|
|
16530
16948
|
]
|
|
16531
16949
|
},
|
|
16532
16950
|
"props": [
|
|
@@ -16544,9 +16962,9 @@
|
|
|
16544
16962
|
},
|
|
16545
16963
|
{
|
|
16546
16964
|
"name": "size",
|
|
16547
|
-
"type": "\"md\" | \"sm\"",
|
|
16965
|
+
"type": "\"lg\" | \"md\" | \"sm\"",
|
|
16548
16966
|
"default": "\"md\"",
|
|
16549
|
-
"description": "
|
|
16967
|
+
"description": "lg sits on --control-sm (32px, 28 compact), the toolbar rung it shares with SearchInput sm and Button sm, for a filter row beside a search field. md sits on --control-xs (28px, 24 compact), the chip in a row of its own or among content. sm is a fixed 24px (the target floor, which density does not shrink), for chips under or inside other content."
|
|
16550
16968
|
},
|
|
16551
16969
|
{
|
|
16552
16970
|
"name": "tone",
|
|
@@ -16566,6 +16984,7 @@
|
|
|
16566
16984
|
"outline"
|
|
16567
16985
|
],
|
|
16568
16986
|
"size": [
|
|
16987
|
+
"lg",
|
|
16569
16988
|
"md",
|
|
16570
16989
|
"sm"
|
|
16571
16990
|
],
|
|
@@ -16650,6 +17069,11 @@
|
|
|
16650
17069
|
"code": "<Row gap={1}><Pill selected onClick={() => {}}>All</Pill><Pill onClick={() => {}}>Projects</Pill><Pill onClick={() => {}}>Reports</Pill></Row>",
|
|
16651
17070
|
"imports": "import { Pill, Row } from \"@sableui/react\""
|
|
16652
17071
|
},
|
|
17072
|
+
{
|
|
17073
|
+
"label": "Filters with search",
|
|
17074
|
+
"code": "const [filter, setFilter] = useState(\"all\")\nconst [q, setQ] = useState(\"\")\n\n<Row gap={2} align=\"center\" justify=\"between\" wrap>\n\t<Row gap={1}>\n\t\t<Pill size=\"lg\" selected={filter === \"all\"} onClick={() => setFilter(\"all\")}>All 8</Pill>\n\t\t<Pill size=\"lg\" selected={filter === \"products\"} onClick={() => setFilter(\"products\")}>Products 5</Pill>\n\t\t<Pill size=\"lg\" selected={filter === \"oss\"} onClick={() => setFilter(\"oss\")}>Open source 2</Pill>\n\t</Row>\n\t<SearchInput size=\"sm\" value={q} onValueChange={setQ} placeholder=\"Find a project…\" aria-label=\"Find a project\" className=\"max-w-64\" />\n</Row>",
|
|
17075
|
+
"imports": "import { Pill, Row, SearchInput } from \"@sableui/react\""
|
|
17076
|
+
},
|
|
16653
17077
|
{
|
|
16654
17078
|
"label": "Outline, small (reactions)",
|
|
16655
17079
|
"code": "<Row gap={1}><Pill appearance=\"outline\" size=\"sm\" selected onClick={() => {}}>🎉 3</Pill><Pill appearance=\"outline\" size=\"sm\" onClick={() => {}}>👀</Pill></Row>",
|
|
@@ -18418,7 +18842,7 @@
|
|
|
18418
18842
|
"group": "Forms",
|
|
18419
18843
|
"register": "either",
|
|
18420
18844
|
"path": "components/search-input",
|
|
18421
|
-
"summary": "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value.",
|
|
18845
|
+
"summary": "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value. size md (form), sm (toolbar, matching Pill lg and Button sm) or xs (inset tool-panel field).",
|
|
18422
18846
|
"keywords": [
|
|
18423
18847
|
"search",
|
|
18424
18848
|
"input",
|
|
@@ -18444,6 +18868,14 @@
|
|
|
18444
18868
|
"guidance": "do",
|
|
18445
18869
|
"description": "Light the query in the results it filters with `useMatchHighlight(query)` — a callback ref for the results container, with `selector` to keep it to titles. It paints on the `highlight` pair without wrapping matches in `<mark>`, the same paint Combobox and CommandMenu use for their rows."
|
|
18446
18870
|
},
|
|
18871
|
+
{
|
|
18872
|
+
"guidance": "do",
|
|
18873
|
+
"description": "In a toolbar beside filter chips, use size=\"sm\" with Pill size=\"lg\" (and Button size=\"sm\" for actions). All three stand on --control-sm (32px, 28 compact), the rung the spacing doctrine gives toolbar controls, so the row is one height in either density. Keep md for a search field in a form or a page header beside md buttons, and xs for a filter inside a workspace panel, where it matches Input xs's inset surface."
|
|
18874
|
+
},
|
|
18875
|
+
{
|
|
18876
|
+
"guidance": "dont",
|
|
18877
|
+
"description": "Do not match a neighbour's height with h-* on className or inputClassName. The magnifier, padding and clear button would stay sized for the old rung; pick the size that shares the neighbour's rung."
|
|
18878
|
+
},
|
|
18447
18879
|
{
|
|
18448
18880
|
"guidance": "dont",
|
|
18449
18881
|
"description": "Do not use SearchInput as a command palette; use CommandMenu for global commands, opened from the header by CommandMenuTrigger. A SearchInput that opens a dialog on focus is a text box you cannot type in."
|
|
@@ -18458,10 +18890,17 @@
|
|
|
18458
18890
|
"components": [
|
|
18459
18891
|
"Input",
|
|
18460
18892
|
"CommandMenu",
|
|
18461
|
-
"Combobox"
|
|
18893
|
+
"Combobox",
|
|
18894
|
+
"Pill"
|
|
18462
18895
|
]
|
|
18463
18896
|
},
|
|
18464
18897
|
"props": [
|
|
18898
|
+
{
|
|
18899
|
+
"name": "size",
|
|
18900
|
+
"type": "\"md\" | \"sm\" | \"xs\"",
|
|
18901
|
+
"default": "\"md\"",
|
|
18902
|
+
"description": "The rung of the control ladder, named as on Input, Select and Combobox. md is the form field (--control-md, 40px / 32 compact). sm is the toolbar field (--control-sm, 32px / 28 compact), the rung Pill lg and Button sm share, so filters + search + actions in one row are one height. xs is the tool-panel field (--control-xs, 28px / 24 compact) on Input xs's inset panel-field surface. The magnifier, inline padding and clear button scale with the rung and stay vertically centred."
|
|
18903
|
+
},
|
|
18465
18904
|
{
|
|
18466
18905
|
"name": "value",
|
|
18467
18906
|
"type": "string",
|
|
@@ -18507,7 +18946,7 @@
|
|
|
18507
18946
|
{
|
|
18508
18947
|
"name": "...props",
|
|
18509
18948
|
"type": "ComponentPropsWithRef<\"input\">",
|
|
18510
|
-
"description": "All native input attributes (name, autoFocus, ref, aria-invalid, …) pass through to the <input> except value/onChange, which are owned by value/onValueChange,
|
|
18949
|
+
"description": "All native input attributes (name, autoFocus, ref, aria-invalid, …) pass through to the <input> except value/onChange, which are owned by value/onValueChange, className, which sizes the whole field, and the native numeric size attribute, which the size prop replaces."
|
|
18511
18950
|
}
|
|
18512
18951
|
],
|
|
18513
18952
|
"tokensUsed": [
|
|
@@ -18516,7 +18955,9 @@
|
|
|
18516
18955
|
"foreground",
|
|
18517
18956
|
"muted-foreground",
|
|
18518
18957
|
"ring",
|
|
18519
|
-
"destructive"
|
|
18958
|
+
"destructive",
|
|
18959
|
+
"panel-field",
|
|
18960
|
+
"panel-field-hover"
|
|
18520
18961
|
],
|
|
18521
18962
|
"motion": [
|
|
18522
18963
|
{
|
|
@@ -18572,6 +19013,16 @@
|
|
|
18572
19013
|
"code": "const [q, setQ] = useState(\"\")\nconst results = useMatchHighlight<HTMLUListElement>(q, { selector: \"[data-title]\" })\nconst shown = posts.filter((post) => post.title.toLowerCase().includes(q.trim().toLowerCase()))\n\n<SearchInput value={q} onValueChange={setQ} placeholder=\"Search posts\" />\n<ul ref={results}>\n\t{shown.map((post) => (\n\t\t<li key={post.id}><span data-title>{post.title}</span></li>\n\t))}\n</ul>",
|
|
18573
19014
|
"imports": "import { SearchInput, useMatchHighlight } from \"@sableui/react\""
|
|
18574
19015
|
},
|
|
19016
|
+
{
|
|
19017
|
+
"label": "Filters with search",
|
|
19018
|
+
"code": "const [filter, setFilter] = useState(\"all\")\nconst [q, setQ] = useState(\"\")\n\n<Row gap={2} align=\"center\" justify=\"between\" wrap>\n\t<Row gap={1}>\n\t\t<Pill size=\"lg\" selected={filter === \"all\"} onClick={() => setFilter(\"all\")}>All 8</Pill>\n\t\t<Pill size=\"lg\" selected={filter === \"products\"} onClick={() => setFilter(\"products\")}>Products 5</Pill>\n\t\t<Pill size=\"lg\" selected={filter === \"oss\"} onClick={() => setFilter(\"oss\")}>Open source 2</Pill>\n\t</Row>\n\t<SearchInput size=\"sm\" value={q} onValueChange={setQ} placeholder=\"Find a project…\" aria-label=\"Find a project\" className=\"max-w-64\" />\n</Row>",
|
|
19019
|
+
"imports": "import { Pill, Row, SearchInput } from \"@sableui/react\""
|
|
19020
|
+
},
|
|
19021
|
+
{
|
|
19022
|
+
"label": "Sizes",
|
|
19023
|
+
"code": "<Stack gap={1}>\n\t<SearchInput size=\"md\" placeholder=\"Search posts\" aria-label=\"Search posts\" />\n\t<SearchInput size=\"sm\" placeholder=\"Find a project…\" aria-label=\"Find a project\" />\n\t<SearchInput size=\"xs\" placeholder=\"Filter layers\" aria-label=\"Filter layers\" />\n</Stack>",
|
|
19024
|
+
"imports": "import { SearchInput, Stack } from \"@sableui/react\""
|
|
19025
|
+
},
|
|
18575
19026
|
{
|
|
18576
19027
|
"label": "Disabled",
|
|
18577
19028
|
"code": "<SearchInput disabled placeholder=\"Search\" />",
|
|
@@ -18597,7 +19048,9 @@
|
|
|
18597
19048
|
"h2",
|
|
18598
19049
|
"subsection",
|
|
18599
19050
|
"optical alignment",
|
|
18600
|
-
"bleed"
|
|
19051
|
+
"bleed",
|
|
19052
|
+
"editorial section",
|
|
19053
|
+
"large section title"
|
|
18601
19054
|
],
|
|
18602
19055
|
"usage": {
|
|
18603
19056
|
"description": "Use SectionHeader to head each section below the page title — a home-page shelf with View all, a settings group, a list with its own actions. PageHeader is the route's single h1; every section under it takes a SectionHeader, with titleId wired to the section's aria-labelledby.",
|
|
@@ -18608,7 +19061,7 @@
|
|
|
18608
19061
|
},
|
|
18609
19062
|
{
|
|
18610
19063
|
"guidance": "do",
|
|
18611
|
-
"description": "Pick titleAs by the document outline (h2 under the page h1, h3 inside an h2 section) and size by visual weight: md for a page's sections and shelves, sm for denser groups like settings."
|
|
19064
|
+
"description": "Pick titleAs by the document outline (h2 under the page h1, h3 inside an h2 section) and size by visual weight: lg for an editorial page's sections under a PageHeader size=\"lg\", md for a page's sections and shelves, sm for denser groups like settings."
|
|
18612
19065
|
},
|
|
18613
19066
|
{
|
|
18614
19067
|
"guidance": "do",
|
|
@@ -18696,13 +19149,14 @@
|
|
|
18696
19149
|
},
|
|
18697
19150
|
{
|
|
18698
19151
|
"name": "size",
|
|
18699
|
-
"type": "\"md\" | \"sm\"",
|
|
19152
|
+
"type": "\"lg\" | \"md\" | \"sm\"",
|
|
18700
19153
|
"default": "\"md\"",
|
|
18701
|
-
"description": "Visual weight, independent of the heading level: md (text-heading-3) for a page's sections and shelves; sm (text-heading-4) for denser sets — settings groups, subsections."
|
|
19154
|
+
"description": "Visual weight, independent of the heading level: lg (text-heading-2 over a text-body-md description) for an editorial page's sections, under a PageHeader size=\"lg\"; md (text-heading-3) for a page's sections and shelves; sm (text-heading-4) for denser sets — settings groups, subsections. lg stops at heading-2: heading-1 is the page title's, one per route. The actions stay centred on the title's first line at every size."
|
|
18702
19155
|
}
|
|
18703
19156
|
],
|
|
18704
19157
|
"variants": {
|
|
18705
19158
|
"size": [
|
|
19159
|
+
"lg",
|
|
18706
19160
|
"md",
|
|
18707
19161
|
"sm"
|
|
18708
19162
|
]
|
|
@@ -18758,6 +19212,11 @@
|
|
|
18758
19212
|
"code": "<section aria-labelledby=\"playlists\" className=\"flex flex-col gap-4\">\n\t<SectionHeader\n\t\ttitleId=\"playlists\"\n\t\ttitle=\"Your playlists\"\n\t\tactions={\n\t\t\t<>\n\t\t\t\t<Button variant=\"ghost\" size=\"sm\" iconStart={<Icon name=\"filter\" />}>Filter</Button>\n\t\t\t\t<Button size=\"sm\" iconStart={<Icon name=\"plus\" />}>New playlist</Button>\n\t\t\t</>\n\t\t}\n\t/>\n\t<Stack gap={1}>\n\t\t<Entity className=\"[--entity-px:0]\" title=\"Late drives\" subtitle=\"42 tracks\" />\n\t\t<Entity className=\"[--entity-px:0]\" title=\"Focus\" subtitle=\"18 tracks\" />\n\t</Stack>\n</section>",
|
|
18759
19213
|
"imports": "import { Button, Entity, Icon, SectionHeader, Stack } from \"@sableui/react\""
|
|
18760
19214
|
},
|
|
19215
|
+
{
|
|
19216
|
+
"label": "An editorial section (size lg)",
|
|
19217
|
+
"code": "<section aria-labelledby=\"selected-work\" className=\"flex flex-col gap-6\">\n\t<SectionHeader\n\t\ttitleId=\"selected-work\"\n\t\tsize=\"lg\"\n\t\ttitle=\"Selected work\"\n\t\tdescription=\"Five projects from the last two years.\"\n\t\tactions={\n\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/work\" />} aria-label=\"View all work\">\n\t\t\t\tView all\n\t\t\t</Button>\n\t\t}\n\t/>\n\t<Grid minItemWidth=\"16rem\" gap={3}>\n\t\t<Card><CardHeader><CardTitle>Tidewater</CardTitle><CardDescription>Brand identity</CardDescription></CardHeader></Card>\n\t\t<Card><CardHeader><CardTitle>Northlight</CardTitle><CardDescription>Product design</CardDescription></CardHeader></Card>\n\t</Grid>\n</section>",
|
|
19218
|
+
"imports": "import { Button, Card, CardDescription, CardHeader, CardTitle, Grid, SectionHeader } from \"@sableui/react\""
|
|
19219
|
+
},
|
|
18761
19220
|
{
|
|
18762
19221
|
"label": "A subsection inside a section",
|
|
18763
19222
|
"code": "<SectionHeader titleAs=\"h3\" size=\"sm\" title=\"Appears on\" />",
|
|
@@ -19721,7 +20180,7 @@
|
|
|
19721
20180
|
{
|
|
19722
20181
|
"name": "Sheet (Root)",
|
|
19723
20182
|
"type": "Base UI Drawer.Root props (open, defaultOpen, onOpenChange, onOpenChangeComplete, modal, disablePointerDismissal, actionsRef, handle, snapPoints, snapPoint, defaultSnapPoint, onSnapPointChange, snapToSequentialPoints) — minus swipeDirection",
|
|
19724
|
-
"description": "Controls open state. Uncontrolled by default. The dismiss swipe direction is not a prop: it follows SheetContent's side. onOpenChange reports reason \"swipe\" when a swipe closed it."
|
|
20183
|
+
"description": "Controls open state. Uncontrolled by default. The dismiss swipe direction is not a prop: it follows SheetContent's side. onOpenChange reports reason \"swipe\" when a swipe closed it. Records the element focused when the sheet opens, trigger or not, and returns focus there on close."
|
|
19725
20184
|
},
|
|
19726
20185
|
{
|
|
19727
20186
|
"name": "snapPoints",
|
|
@@ -19809,7 +20268,7 @@
|
|
|
19809
20268
|
"The panel scrolls its own content (thin scrollbar, contained overscroll) when it outgrows its bound — the full height on left/right, 80vh on top/bottom.",
|
|
19810
20269
|
"Esc and click-outside dismiss by default; on touch, a swipe toward the sheet's own edge dismisses too. A short or slow drag springs back.",
|
|
19811
20270
|
"Swipe is an extra path, never the only one: Esc, click-outside and SheetClose all still work.",
|
|
19812
|
-
"Focus matches Dialog: the first tabbable on mouse/keyboard open, the panel itself on touch (so the virtual keyboard stays down)
|
|
20271
|
+
"Focus matches Dialog: the first tabbable on mouse/keyboard open, the panel itself on touch (so the virtual keyboard stays down). On close, focus returns to the element that held it when the sheet opened: the SheetTrigger, or your own button when it drives a controlled open. It is captured before the content mounts, so an autoFocus field inside cannot displace it; an explicit finalFocus on SheetContent wins.",
|
|
19813
20272
|
"Render a SheetClose inside SheetContent so screen-reader users can escape the popup.",
|
|
19814
20273
|
"Slide animation runs via data-[starting-style]/data-[ending-style] attributes on the motion-duration token; while swiping the panel tracks the finger directly, and a released swipe finishes on the same token (scaled by flick speed), so reduced motion makes it instant.",
|
|
19815
20274
|
"A mouse drag that starts on sheet content selects text rather than dragging the panel; mouse dragging only starts from the padding edge.",
|
|
@@ -28149,6 +28608,36 @@
|
|
|
28149
28608
|
"render",
|
|
28150
28609
|
"style"
|
|
28151
28610
|
],
|
|
28611
|
+
"ActivityHeatmap": [
|
|
28612
|
+
"activeDate",
|
|
28613
|
+
"categories",
|
|
28614
|
+
"data",
|
|
28615
|
+
"defaultActiveDate",
|
|
28616
|
+
"end",
|
|
28617
|
+
"formatValue",
|
|
28618
|
+
"label",
|
|
28619
|
+
"legend",
|
|
28620
|
+
"legendLabels",
|
|
28621
|
+
"levelScale",
|
|
28622
|
+
"levels",
|
|
28623
|
+
"loading",
|
|
28624
|
+
"locale",
|
|
28625
|
+
"monthLabels",
|
|
28626
|
+
"motion",
|
|
28627
|
+
"onActiveDateChange",
|
|
28628
|
+
"onSelectDate",
|
|
28629
|
+
"renderCell",
|
|
28630
|
+
"selectedDate",
|
|
28631
|
+
"size",
|
|
28632
|
+
"start",
|
|
28633
|
+
"stretch",
|
|
28634
|
+
"tone",
|
|
28635
|
+
"tooltip",
|
|
28636
|
+
"unit",
|
|
28637
|
+
"weekStart",
|
|
28638
|
+
"weekdayLabels",
|
|
28639
|
+
"year"
|
|
28640
|
+
],
|
|
28152
28641
|
"AlertDialog": [
|
|
28153
28642
|
"actionsRef",
|
|
28154
28643
|
"children",
|
|
@@ -28170,6 +28659,7 @@
|
|
|
28170
28659
|
"className",
|
|
28171
28660
|
"finalFocus",
|
|
28172
28661
|
"initialFocus",
|
|
28662
|
+
"keepMounted",
|
|
28173
28663
|
"render",
|
|
28174
28664
|
"scroll",
|
|
28175
28665
|
"showCloseButton",
|
|
@@ -28630,7 +29120,8 @@
|
|
|
28630
29120
|
"filename",
|
|
28631
29121
|
"highlight",
|
|
28632
29122
|
"prompt",
|
|
28633
|
-
"showCopy"
|
|
29123
|
+
"showCopy",
|
|
29124
|
+
"wrap"
|
|
28634
29125
|
],
|
|
28635
29126
|
"CodeHighlightProvider": [
|
|
28636
29127
|
"children",
|
|
@@ -29064,6 +29555,7 @@
|
|
|
29064
29555
|
"className",
|
|
29065
29556
|
"finalFocus",
|
|
29066
29557
|
"initialFocus",
|
|
29558
|
+
"keepMounted",
|
|
29067
29559
|
"render",
|
|
29068
29560
|
"scroll",
|
|
29069
29561
|
"showCloseButton",
|
|
@@ -29380,6 +29872,7 @@
|
|
|
29380
29872
|
"fallback",
|
|
29381
29873
|
"fit",
|
|
29382
29874
|
"loading",
|
|
29875
|
+
"position",
|
|
29383
29876
|
"seed",
|
|
29384
29877
|
"sizes",
|
|
29385
29878
|
"src",
|
|
@@ -29393,6 +29886,7 @@
|
|
|
29393
29886
|
"href",
|
|
29394
29887
|
"loading",
|
|
29395
29888
|
"meta",
|
|
29889
|
+
"position",
|
|
29396
29890
|
"render",
|
|
29397
29891
|
"seed",
|
|
29398
29892
|
"size",
|
|
@@ -29810,6 +30304,7 @@
|
|
|
29810
30304
|
"actions",
|
|
29811
30305
|
"back",
|
|
29812
30306
|
"description",
|
|
30307
|
+
"size",
|
|
29813
30308
|
"title",
|
|
29814
30309
|
"titleAs"
|
|
29815
30310
|
],
|
|
@@ -30178,6 +30673,7 @@
|
|
|
30178
30673
|
"inputClassName",
|
|
30179
30674
|
"onClear",
|
|
30180
30675
|
"onValueChange",
|
|
30676
|
+
"size",
|
|
30181
30677
|
"value"
|
|
30182
30678
|
],
|
|
30183
30679
|
"SectionHeader": [
|