@sableui/react 0.16.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 +132 -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/blocks/approval-block.js +30 -48
- package/dist/blocks/approval-block.meta.js +7 -8
- package/dist/blocks/index.d.ts +1 -1
- 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/section-rail/index.js +7 -0
- package/dist/components/section-rail/section-rail.js +153 -0
- package/dist/components/section-rail/section-rail.meta.js +189 -0
- package/dist/components/sheet/sheet.js +15 -3
- package/dist/components/sheet/sheet.meta.js +2 -2
- package/dist/file-hashes.json +53 -39
- package/dist/icon-sets/reicon.js +23 -1
- package/dist/index.d.ts +364 -27
- package/dist/index.js +11 -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 +2 -0
- package/dist/lib/use-active-section.js +59 -0
- package/dist/styles.css +122 -114
- package/llms/blocks/approval-block.md +40 -55
- 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/section-rail.md +101 -0
- 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 +516 -90
- package/llms.txt +5 -3
- package/package.json +6 -1
- package/registry.json +745 -46
- 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 +18 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# SectionRail
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A slim vertical rail of dots, one per section of a long page, that shows where the reader is and jumps to any section. The current section's bar glides as the page scrolls; a shared tooltip names each dot.
|
|
6
|
+
|
|
7
|
+
**Keywords:** section rail, scroll spy, scrollspy, dot navigation, page navigation, table of contents, on this page, chapter nav, scroll progress, case study nav
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use SectionRail beside a long, single-column read — a case study, an essay, a long landing page — so the reader can see which section they are in and jump to another without leaving the text. It follows the scroll by itself (useActiveSection); pass value when the parent already knows the current section.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Give every section an id that matches its entry, and keep the list to the page's top-level sections — about ten at most, so the rail stays shorter than the viewport.
|
|
16
|
+
- Do: Place it with layout classes — usually `fixed top-1/2 left-6 z-(--z-nav) -translate-y-1/2` — and hide it below the width where it would overlap the text (`max-lg:hidden`). Offer the same links another way on narrow screens, such as a Collapsible list at the top of the page.
|
|
17
|
+
- Do: Pass `root` when the content scrolls inside an element (a Scroller, an inset AppShell main) rather than the window, and give sections `scroll-mt-*` when a sticky header would cover their tops after a jump.
|
|
18
|
+
- Do: Set `side="right"` when the rail sits on the right edge, so labels open toward the content.
|
|
19
|
+
- Don't: Use it for app navigation between pages; that is SideNav or Sidebar. A SectionRail only moves within one page. → use **SideNav**, **Sidebar**
|
|
20
|
+
- Don't: Use it to show the steps of a task or a flow; that is Stepper, which shows progress through steps rather than position in a read. → use **Stepper**
|
|
21
|
+
- Don't: Use it for a handful of switchable views in one place; that is Tabs. → use **Tabs**
|
|
22
|
+
|
|
23
|
+
### Anatomy
|
|
24
|
+
|
|
25
|
+
| Part | Required | Description |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Rail | yes | The `<nav>` landmark: a rounded muted track holding one link per section. |
|
|
28
|
+
| Dot | yes | A link to `#id`, drawn as a small dot that grows under the pointer or focus. |
|
|
29
|
+
| Marker | yes | One primary bar that sits on the current section's dot and glides to the next. |
|
|
30
|
+
| Label | yes | A shared tooltip that names the hovered or focused dot and travels between dots. |
|
|
31
|
+
|
|
32
|
+
## Props
|
|
33
|
+
|
|
34
|
+
| Name | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| sections | readonly { id: string; label: string }[] | — | The sections, in document order. `id` is the section element's id (the link goes to `#id`); `label` is the tooltip and the link's accessible name. |
|
|
37
|
+
| value | string | — | The current section's id, when the parent decides it. Leave it unset and the rail follows the scroll itself. |
|
|
38
|
+
| onValueChange | (id: string) => void | — | Called with the new id when the reader scrolls into a section or picks one. |
|
|
39
|
+
| offset | number | 0.35 | Where the reading line sits, as a fraction of the viewport from its top. A section becomes current once its top crosses it. |
|
|
40
|
+
| root | RefObject<HTMLElement \| null> | — | The element that scrolls, when it is not the window. |
|
|
41
|
+
| side | "left" \| "right" | "left" | Which edge of the page the rail sits on. The labels open on the other side, toward the content. |
|
|
42
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the marker's glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
43
|
+
| aria-label | string | "Sections" | The nav landmark's name. Name it for the page ("Case study sections") when it has other navs. |
|
|
44
|
+
|
|
45
|
+
## Tokens used
|
|
46
|
+
|
|
47
|
+
`muted`, `muted-foreground`, `foreground`, `primary`, `ring`, `popover`, `popover-foreground`, `border-floating`
|
|
48
|
+
|
|
49
|
+
## Motion
|
|
50
|
+
|
|
51
|
+
- **arrival** · Root · _no motion_ — Page chrome arrives with the page; the marker lands on the current section with no transition, because a bar sliding in on load narrates a move nobody made.
|
|
52
|
+
- **response** · Dot · `motion-interactive` — The dot darkens and grows under the pointer or focus, and the label travels between dots as one tooltip (TooltipGroup) rather than closing and reopening at each.
|
|
53
|
+
- **transition** · Marker · `motion-glide` — One bar travels to the new section as the page scrolls, so the change reads as movement down the page. A picked section holds the bar until the reader scrolls by hand, so it goes straight there instead of stepping through every section in between, and stays on the pick even when the page ends before that section can reach the reading line. Expressive: `subtle` moves it at once.
|
|
54
|
+
- **activity** · Root · _no motion_ — Navigation chrome has no working state.
|
|
55
|
+
|
|
56
|
+
## Accessibility
|
|
57
|
+
|
|
58
|
+
- Renders a <nav> landmark (default name "Sections") of real links to `#id`, so it works with no script, middle-click and copy-link.
|
|
59
|
+
- Each link is named by its section's label; the current one carries aria-current="location".
|
|
60
|
+
- Activating a link from the keyboard moves focus into the section, as following an anchor does, so the next Tab continues from there.
|
|
61
|
+
- The tooltip opens on focus as well as hover; the dots are 44×32 targets.
|
|
62
|
+
- Under reduced motion the jump is instant and the marker moves without travel.
|
|
63
|
+
|
|
64
|
+
## Related
|
|
65
|
+
|
|
66
|
+
- Components: SideNav, Tooltip, Stepper, Tabs, GlidingHighlight
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
### Fixed beside a long read
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { SectionRail } from "@sableui/react"
|
|
74
|
+
|
|
75
|
+
<SectionRail
|
|
76
|
+
aria-label="Case study sections"
|
|
77
|
+
sections={[
|
|
78
|
+
{ id: "overview", label: "Overview" },
|
|
79
|
+
{ id: "problem", label: "The problem" },
|
|
80
|
+
{ id: "process", label: "Process" },
|
|
81
|
+
{ id: "outcome", label: "Outcome" },
|
|
82
|
+
]}
|
|
83
|
+
className="fixed top-1/2 left-6 z-(--z-nav) -translate-y-1/2 max-lg:hidden"
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### On the right edge
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { SectionRail } from "@sableui/react"
|
|
91
|
+
|
|
92
|
+
<SectionRail
|
|
93
|
+
side="right"
|
|
94
|
+
sections={[
|
|
95
|
+
{ id: "intro", label: "Introduction" },
|
|
96
|
+
{ id: "usage", label: "Usage" },
|
|
97
|
+
{ id: "api", label: "API" },
|
|
98
|
+
]}
|
|
99
|
+
className="fixed top-1/2 right-6 z-(--z-nav) -translate-y-1/2 max-lg:hidden"
|
|
100
|
+
/>
|
|
101
|
+
```
|
package/llms/components/sheet.md
CHANGED
|
@@ -48,7 +48,7 @@ Use Sheet for secondary workflows that slide in while preserving context from th
|
|
|
48
48
|
|
|
49
49
|
| Name | Type | Default | Description |
|
|
50
50
|
| --- | --- | --- | --- |
|
|
51
|
-
| Sheet (Root) | Base UI Drawer.Root props (open, defaultOpen, onOpenChange, onOpenChangeComplete, modal, disablePointerDismissal, actionsRef, handle, snapPoints, snapPoint, defaultSnapPoint, onSnapPointChange, snapToSequentialPoints) — minus swipeDirection | — | 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. |
|
|
51
|
+
| Sheet (Root) | Base UI Drawer.Root props (open, defaultOpen, onOpenChange, onOpenChangeComplete, modal, disablePointerDismissal, actionsRef, handle, snapPoints, snapPoint, defaultSnapPoint, onSnapPointChange, snapToSequentialPoints) — minus swipeDirection | — | 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. |
|
|
52
52
|
| snapPoints | (number \| string)[] | — | Stops a bottom sheet rests at, smallest first: a fraction of the viewport (0.4), pixels (320), or a px/rem string ("20rem"). The panel is the full stop's height and opens at the first stop; a swipe moves between stops and a swipe below the first closes it. side="bottom" only: other sides ignore them, with a development warning. |
|
|
53
53
|
| snapPoint | number \| string \| null | — | The active stop, controlled. Pair with onSnapPointChange; use defaultSnapPoint to open at a different stop uncontrolled. |
|
|
54
54
|
| onSnapPointChange | (snapPoint: number \| string \| null, eventDetails) => void | — | Fires when a swipe (or your code) moves the sheet to another stop. |
|
|
@@ -68,7 +68,7 @@ Use Sheet for secondary workflows that slide in while preserving context from th
|
|
|
68
68
|
- 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.
|
|
69
69
|
- 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.
|
|
70
70
|
- Swipe is an extra path, never the only one: Esc, click-outside and SheetClose all still work.
|
|
71
|
-
- Focus matches Dialog: the first tabbable on mouse/keyboard open, the panel itself on touch (so the virtual keyboard stays down)
|
|
71
|
+
- 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.
|
|
72
72
|
- Render a SheetClose inside SheetContent so screen-reader users can escape the popup.
|
|
73
73
|
- 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.
|
|
74
74
|
- A mouse drag that starts on sheet content selects text rather than dragging the panel; mouse dragging only starts from the padding edge.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Reserves space via aspect-ratio so async media loads with zero layout shift. Wrap images, video, embeds.
|
|
5
|
+
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).
|
|
6
6
|
|
|
7
7
|
**Keywords:** aspect box, aspect ratio, media, layout shift
|
|
8
8
|
|
package/llms/layout/bleed.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
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.
|
|
5
|
+
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).
|
|
6
6
|
|
|
7
7
|
**Keywords:** bleed, full bleed, media band, layout
|
|
8
8
|
|
package/llms/layout/center.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Horizontally center a block, optionally capping its width (commonly "prose" for a reading column inside a wider layout).
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
**Keywords:** center, measure, reading width, layout
|
|
8
8
|
|
package/llms/layout/columns.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
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.
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
**Keywords:** columns, 12 column, span, layout
|
|
8
8
|
|
package/llms/layout/grid.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
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.
|
|
5
|
+
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).
|
|
6
6
|
|
|
7
7
|
**Keywords:** grid, card grid, auto fit, layout
|
|
8
8
|
|
package/llms/layout/region.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
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.
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
**Keywords:** region, page width, container, layout
|
|
8
8
|
|
package/llms/layout/row.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Horizontal flex flow — inline rows of controls, toolbars, action clusters. Defaults to center-aligned, no wrap.
|
|
5
|
+
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).
|
|
6
6
|
|
|
7
7
|
**Keywords:** row, toolbar, horizontal, layout
|
|
8
8
|
|
|
@@ -38,3 +38,11 @@ import { Button, Row } from "@sableui/react"
|
|
|
38
38
|
|
|
39
39
|
<Row gap={2} justify="end"><Button variant="ghost">Cancel</Button><Button>Save changes</Button></Row>
|
|
40
40
|
```
|
|
41
|
+
|
|
42
|
+
### Responsive through className
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { Button, Row } from "@sableui/react"
|
|
46
|
+
|
|
47
|
+
<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>
|
|
48
|
+
```
|
package/llms/layout/spacer.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Explicit space on the rhythm, or a flexible grow pusher between siblings (e.g. pushing a footer item to the end of a Row).
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
**Keywords:** spacer, spacing, grow, layout
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Responsive two-pane layout: side-by-side at/above a breakpoint, stacked (primary first) below. List-detail, media + metadata.
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
**Keywords:** split pane, two pane, list detail, responsive
|
|
8
8
|
|
package/llms/layout/stack.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
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+).
|
|
5
|
+
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).
|
|
6
6
|
|
|
7
7
|
**Keywords:** stack, vertical rhythm, layout, spacing
|
|
8
8
|
|
|
@@ -58,6 +58,37 @@ names the nearest pair that reads. Report it with that pair as a recommendation.
|
|
|
58
58
|
A reasoned `design-lint-disable-next-line label-contrast` is the app's recorded
|
|
59
59
|
choice: list it as intentional, not as a defect.
|
|
60
60
|
|
|
61
|
+
## Adoption: an app wired for Sable that barely uses it
|
|
62
|
+
|
|
63
|
+
The commonest drift is not a wrong prop. It is an app where an agent never
|
|
64
|
+
loaded this skill: the stylesheet and tokens are wired, and every control is the
|
|
65
|
+
app's own (a `Modal`, a `PrimaryButton`, a `Tabs` written from scratch). Wiring
|
|
66
|
+
checks pass, and lint only sees the raw elements. `sable check` (and `sable
|
|
67
|
+
grade --json` → `adoption`) measures it: how many UI files compose Sable, which
|
|
68
|
+
app-defined components are named for something Sable ships and do the behaviour
|
|
69
|
+
by hand, and what the app's foundation is. `sable doctor` warns when adoption is
|
|
70
|
+
`none` or `low`. It is never a failure: whether working screens move is the
|
|
71
|
+
user's call.
|
|
72
|
+
|
|
73
|
+
Put the choice to the user before changing anything. Ask, don't migrate silently
|
|
74
|
+
and don't leave it unmentioned. Show the swaps, behaviour first (overlays,
|
|
75
|
+
controls, tabs, menus, anything with focus or keyboard handling), with what each
|
|
76
|
+
one gains ("your Modal has no focus trap or Esc; Sable's Dialog has both").
|
|
77
|
+
Layout and look stay the app's. How strongly to recommend it depends on the
|
|
78
|
+
`foundation` the report names:
|
|
79
|
+
|
|
80
|
+
| Foundation | What to say |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `ad-hoc`: no system besides Sable | Recommend the move plainly. There is nothing to preserve but one-off components, and every one is a behaviour bug waiting. Offer to do it a screen at a time. |
|
|
83
|
+
| `own`: a folder of the app's own primitives | Offer the swaps and say what each gains. Keep the app's own where the user prefers them; suggest building them on Sable's behaviour (a `Button` wrapper around Sable's `Button`) instead. |
|
|
84
|
+
| `library`: another component library (MUI, Radix, shadcn-style `components/ui`) | Ask whether Sable should replace it or only cover new work. Don't mix two control libraries on one screen. |
|
|
85
|
+
| `sable`: Sable already carries the UI | Mention the few swaps listed when those screens are next touched. |
|
|
86
|
+
|
|
87
|
+
Use the question tool the host offers when there is one. Record the answer in
|
|
88
|
+
the project's instructions, so the next task doesn't ask again. On a yes, move
|
|
89
|
+
one screen or component at a time with `fix`/`polish`, running `sable check` and
|
|
90
|
+
looking at the rendered states after each.
|
|
91
|
+
|
|
61
92
|
## Deliver evidence-backed findings
|
|
62
93
|
|
|
63
94
|
Save the audit under `design/<surface>/audit/` or the existing review location.
|
|
@@ -79,15 +79,22 @@ In a monorepo where Sable belongs to one workspace package, run setup in that
|
|
|
79
79
|
package with `--root` naming the repository root (for example
|
|
80
80
|
`pnpm --filter web exec sable setup --agent --root ../..`); the entries go
|
|
81
81
|
there and resolve Sable through the package.
|
|
82
|
+
Setup also adds a short managed block to the project's `AGENTS.md` and
|
|
83
|
+
`CLAUDE.md`, the instructions an agent reads on every task, so a UI request
|
|
84
|
+
goes through the skill even when it doesn't mention Sable. Only the text between
|
|
85
|
+
its `<!-- sable:begin -->` and `<!-- sable:end -->` markers is Sable's;
|
|
86
|
+
`--no-instructions` skips it.
|
|
82
87
|
Each entry resolves `skills/sable/SKILL.md` from the installed package, so package
|
|
83
88
|
upgrades also update the workflow. Other agents can read that file directly.
|
|
84
89
|
Use `$sable build <idea>`, `inspire <references>`, `audit <target>`, or
|
|
85
90
|
`polish <target>` in Codex; use `/sable` with the same modes in Claude Code or
|
|
86
91
|
Cursor. `create` is a build alias. These are agent modes, not shell commands.
|
|
87
92
|
|
|
88
|
-
Upgrade with `pnpm
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
Upgrade with `pnpm exec sable upgrade`: it updates the package, then runs the
|
|
94
|
+
new version's `init`, which refreshes the skill entries and the instructions
|
|
95
|
+
block and checks the wiring. From 0.17 or earlier, which has no `upgrade`, run
|
|
96
|
+
`pnpm update @sableui/react --latest` and then `pnpm exec sable init` once.
|
|
97
|
+
Setup only replaces managed files that have not been locally edited; `--dry-run` checks without writing. Keep
|
|
91
98
|
project preferences in project instructions. Package installation alone does not
|
|
92
99
|
change agent settings. See [building-with-sable.md](./building-with-sable.md),
|
|
93
100
|
[creating-a-preset.md](./creating-a-preset.md), and
|
|
@@ -102,8 +102,7 @@ saved file. Replace, don't add beside:
|
|
|
102
102
|
## Upgrade the package and skill
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
pnpm
|
|
106
|
-
pnpm exec sable setup --agent --update
|
|
105
|
+
pnpm exec sable upgrade
|
|
107
106
|
pnpm exec sable preset --check .sable/presets/clay.json --json
|
|
108
107
|
pnpm exec sable doctor
|
|
109
108
|
```
|