@sableui/react 0.15.0 → 0.17.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/CHANGELOG.md +99 -0
- package/bin/sable-lint.mjs +5 -1
- package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
- 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/blocks/wallet-block.js +2 -2
- package/dist/components/app-shell/app-shell-frame.js +16 -0
- package/dist/components/app-shell/app-shell-main.js +12 -0
- package/dist/components/app-shell/app-shell.js +28 -4
- package/dist/components/app-shell/app-shell.meta.js +88 -4
- package/dist/components/area-chart/area-chart.meta.js +6 -1
- package/dist/components/attachment/attachment.meta.js +2 -1
- package/dist/components/attachment/attachment.variants.js +1 -1
- package/dist/components/badge/badge.meta.js +8 -1
- package/dist/components/badge/badge.variants.js +7 -7
- package/dist/components/banner/banner.meta.js +6 -1
- package/dist/components/banner/banner.variants.js +5 -5
- package/dist/components/bar-chart/bar-chart.meta.js +6 -1
- package/dist/components/caret-menu/caret-menu.js +5 -6
- package/dist/components/chat-composer/chat-composer.meta.js +2 -2
- package/dist/components/chat-composer/chat-composer.variants.js +1 -1
- package/dist/components/color-field/color-field.js +6 -8
- package/dist/components/combobox/combobox.js +27 -5
- package/dist/components/combobox/combobox.meta.js +10 -2
- package/dist/components/command-menu/command-menu.js +14 -3
- package/dist/components/command-menu/command-menu.meta.js +9 -1
- package/dist/components/date-picker/date-picker.meta.js +2 -2
- package/dist/components/feedback/feedback.js +1 -1
- package/dist/components/feedback/feedback.meta.js +2 -1
- package/dist/components/gradient-art/gradient-art.js +8 -5
- package/dist/components/gradient-art/gradient-art.meta.js +35 -2
- package/dist/components/input/input.meta.js +11 -3
- package/dist/components/input-group/input-group.js +1 -1
- package/dist/components/input-group/input-group.meta.js +1 -1
- package/dist/components/line-chart/line-chart.meta.js +6 -1
- package/dist/components/menu/menu.js +1 -1
- package/dist/components/meter/meter.js +53 -7
- package/dist/components/meter/meter.meta.js +51 -4
- package/dist/components/meter/meter.variants.js +43 -7
- package/dist/components/note/note.meta.js +5 -1
- package/dist/components/note/note.variants.js +4 -4
- package/dist/components/number-field/number-field.meta.js +2 -2
- package/dist/components/otp-field/otp-field.meta.js +2 -2
- package/dist/components/panel-group/panel-group.js +5 -8
- package/dist/components/pie-chart/pie-chart.meta.js +6 -1
- package/dist/components/radar-chart/radar-chart.meta.js +6 -1
- package/dist/components/radial-chart/radial-chart.meta.js +6 -1
- package/dist/components/search-input/search-input.meta.js +11 -3
- 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/segmented-bar/segmented-bar.meta.js +8 -1
- package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.js +5 -4
- package/dist/components/sidebar/sidebar.js +16 -1
- package/dist/components/sidebar/sidebar.meta.js +7 -2
- package/dist/components/sparkline/sparkline.meta.js +8 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -1
- package/dist/components/status-dot/status-dot.variants.js +7 -7
- package/dist/components/stepper/stepper.meta.js +2 -1
- package/dist/components/stepper/stepper.variants.js +1 -1
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/textarea/textarea.js +5 -0
- package/dist/components/textarea/textarea.meta.js +3 -3
- package/dist/file-hashes.json +99 -92
- package/dist/foundations/accessibility.js +5 -0
- package/dist/index.d.ts +227 -25
- package/dist/index.js +12 -1
- package/dist/lib/chart/types.js +8 -8
- package/dist/lib/chart/use-arrival.js +5 -4
- package/dist/lib/field-shell.js +1 -1
- package/dist/lib/highlight-glide.js +6 -6
- package/dist/lib/scrub-gesture.js +6 -6
- package/dist/lib/taxonomy.js +1 -0
- package/dist/lib/use-active-section.js +59 -0
- package/dist/lib/use-caret-trigger.js +11 -18
- package/dist/lib/use-match-highlight.js +69 -0
- package/dist/lib/use-smooth-wheel.js +10 -8
- package/dist/presets/index.d.ts +13 -2
- package/dist/presets/index.js +17 -1
- package/dist/styles.css +100 -0
- package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
- package/dist/themes/_contract.js +14 -0
- package/dist/themes/atelier/atelier.css +18 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/contrast.js +48 -1
- package/dist/themes/ember/ember.css +18 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +18 -0
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +18 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -0
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -0
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +18 -0
- package/llms/blocks/approval-block.md +40 -55
- package/llms/blocks/wallet-block.md +5 -4
- package/llms/components/app-shell.md +72 -3
- package/llms/components/area-chart.md +1 -1
- package/llms/components/attachment.md +1 -1
- package/llms/components/badge.md +1 -1
- package/llms/components/banner.md +1 -1
- package/llms/components/bar-chart.md +1 -1
- package/llms/components/chat-composer.md +1 -1
- package/llms/components/combobox.md +2 -1
- package/llms/components/command-menu.md +3 -2
- package/llms/components/date-picker.md +1 -1
- package/llms/components/feedback.md +1 -1
- package/llms/components/gradient-art.md +28 -1
- package/llms/components/input-group.md +1 -1
- package/llms/components/input.md +4 -2
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +33 -4
- package/llms/components/note.md +1 -1
- package/llms/components/number-field.md +1 -1
- package/llms/components/otp-field.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/search-input.md +20 -2
- package/llms/components/section-rail.md +101 -0
- package/llms/components/segmented-bar.md +1 -1
- package/llms/components/select.md +1 -1
- package/llms/components/sidebar.md +3 -2
- package/llms/components/sparkline.md +1 -1
- package/llms/components/status-dot.md +1 -1
- package/llms/components/stepper.md +1 -1
- package/llms/components/table.md +2 -2
- package/llms/components/textarea.md +2 -2
- package/llms/foundations/accessibility.md +1 -0
- package/llms/reference/token-intent.md +22 -2
- package/llms/reference/utilities.md +5 -2
- package/llms-full.txt +367 -105
- package/llms.txt +6 -5
- package/package.json +1 -1
- package/registry.json +609 -67
- package/scripts/lint-rules.mjs +6 -3
- package/targets/ios.json +9 -0
|
@@ -40,7 +40,7 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
40
40
|
|
|
41
41
|
| Name | Type | Description |
|
|
42
42
|
| --- | --- | --- |
|
|
43
|
-
| CommandMenuTrigger | { onOpen?: () => void; placeholder?: string; shortcut?: string \| false; size?: "sm" \| "md"; disabled?: boolean } | The search field in a header that is really a button. Wears SearchInput's shell (
|
|
43
|
+
| CommandMenuTrigger | { onOpen?: () => void; placeholder?: string; shortcut?: string \| false; size?: "sm" \| "md"; disabled?: boolean } | The search field in a header that is really a button. Wears SearchInput's shell (the field fill and outline, leading magnifier, muted placeholder) on the control ladder, with a trailing Kbd: ⌘K on macOS, Ctrl K elsewhere. onOpen runs on click and on the shortcut, which it listens for page-wide (useCommandMenuShortcut). shortcut is the key (default "k"); false drops both the hint and the listener. size is sm in a header beside small buttons, md (default) beside Input and SearchInput. It sits outside CommandMenu, so the Dialog returns focus to it on close. |
|
|
44
44
|
| useCommandMenuShortcut | (onShortcut: (event: KeyboardEvent) => void, key?: string \| false) => void | The page-wide ⌘K / Ctrl+K listener CommandMenuTrigger installs, for a palette with no visible trigger. Ignored while focus is in a text field, textarea, select or contenteditable (Ctrl+K belongs to the field there) except the palette's own input, so the callback may toggle. It calls preventDefault() on a match and skips an event already prevented, so two listeners on one page fire once. key defaults to "k"; false turns it off. Removed on unmount. |
|
|
45
45
|
| CommandMenuPanel | { items?: CommandItem[]; query?: string; onQueryChange?: (query: string) => void; filter?: CommandFilter; listClassName?: string; placeholder?: string; emptyMessage?: string; title?: string; scope?: ReactNode; inputProps?: ComponentProps<'input'>; className?: string; children?: ReactNode } | The palette rendered in place: the same search header, filtered and grouped listbox, keyboard model and result-count live region, on a static card (border-border, bg-card, no shadow) instead of in a Dialog. Takes every CommandMenu prop except open, onOpenChange, finalFocus and description; className lands on the card, title names the listbox. Focus is not trapped and the input does not take focus on mount (inputProps.autoFocus opts in). Esc clears the query, and passes through once it is empty, so a Popover around the panel still closes. A chosen row runs its onSelect and the panel stays as it was. The page-wide shortcut does not fire from its input. |
|
|
46
46
|
| CommandMenuGroup | { label: string; children } | A labelled group of command rows. |
|
|
@@ -58,6 +58,7 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
58
58
|
| query | string | — | Controlled query. Leave undefined to let the palette own it; either way it resets on close (a controlled caller gets onQueryChange('')). |
|
|
59
59
|
| onQueryChange | (query: string) => void | — | Called on every query change. Required to control query, optional otherwise. |
|
|
60
60
|
| filter | ((items: CommandItem[], query: string) => CommandItem[]) \| null | case-insensitive substring match on label | Replaces or disables the built-in match. A function ranks and filters items itself and its order is kept verbatim; null renders items exactly as given. Either also stops declarative CommandMenuItem children self-filtering. The query arrives raw — trimming and case are policy, and this is the prop that hands policy over. |
|
|
61
|
+
| highlightMatches | boolean | true | Paints the query wherever it appears in a row's label (not its detail or hint), on the `highlight` / `highlight-foreground` pair, through the CSS Custom Highlight API — no `<mark>` in the row, and the accessible name is unchanged. A literal, case-insensitive match, so under a fuzzy `filter` a row that matched as a subsequence keeps an unpainted label. `false` turns it off. Applies to CommandMenuPanel too. |
|
|
61
62
|
| listClassName | string | — | Classes for the scrolling list. className lands on the dialog surface, so this is the seam for the list's own height cap — listClassName="max-h-[60vh]". The list is a Scroller, so the cap lands on the element that scrolls and the edge fades come free. |
|
|
62
63
|
| placeholder | string | "Type a command or search…" | Placeholder for the search input. |
|
|
63
64
|
| emptyMessage | string | "No results found." | Copy shown when the query matches nothing. |
|
|
@@ -68,7 +69,7 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
68
69
|
|
|
69
70
|
## Tokens used
|
|
70
71
|
|
|
71
|
-
`card`, `card-foreground`, `border`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`
|
|
72
|
+
`highlight`, `highlight-foreground`, `card`, `card-foreground`, `border`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`
|
|
72
73
|
|
|
73
74
|
## Motion
|
|
74
75
|
|
|
@@ -38,7 +38,7 @@ Use DatePicker when users need to choose one date — or, with granularity="minu
|
|
|
38
38
|
|
|
39
39
|
## Tokens used
|
|
40
40
|
|
|
41
|
-
`primary`, `primary-foreground`, `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `popover`, `popover-foreground`, `border`, `
|
|
41
|
+
`primary`, `primary-foreground`, `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `popover`, `popover-foreground`, `border`, `field-border`, `field`, `ring`
|
|
42
42
|
|
|
43
43
|
## Accessibility
|
|
44
44
|
|
|
@@ -32,7 +32,7 @@ Use Feedback to collect lightweight sentiment, usefulness, or quality signals fr
|
|
|
32
32
|
|
|
33
33
|
## Tokens used
|
|
34
34
|
|
|
35
|
-
`popover`, `popover-foreground`, `border`, `input`, `card`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`, `success`
|
|
35
|
+
`popover`, `popover-foreground`, `border`, `input`, `card`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`, `success`, `success-mark`
|
|
36
36
|
|
|
37
37
|
## Motion
|
|
38
38
|
|
|
@@ -8,13 +8,15 @@ Placeholder artwork painted by a static WebGL shader from the preset's own art d
|
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
-
Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable.
|
|
11
|
+
Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable. `intensity="soft"` paints the same art as a pastel wash — the iridescent aura AI products use as a card or modal header band, and as a stage for floating chips and logo tiles.
|
|
12
12
|
|
|
13
13
|
### Best practices
|
|
14
14
|
|
|
15
15
|
- Do: Pass a stable `seed` per item — the name for an avatar, the title for a cover. Each item then keeps the same art across renders, reloads and themes, which is what lets someone recognise it.
|
|
16
16
|
- Do: Size it with layout classes (`aspect-[4/3]`, `h-20`, `size-10 rounded-full`) and put any glyph or initials inside as children; they render above the art.
|
|
17
17
|
- Do: Keep the default `mesh` for covers, tiles and avatars. Reach for `grain` where a printed, tactile surface suits — a banner, a hero card — and keep one variant per surface type so art reads as a system.
|
|
18
|
+
- Do: Use `intensity="soft"` as a stage: a card or modal header band, the art on an integration or pricing card, a field behind floating prompt chips or rounded logo tiles. Make the tiles and chips floating surfaces (`bg-popover` with `border-border-floating` and a shadow, or a `floating` Button) so they lift off the wash; soft stays light in dark mode too, so the same composition holds in both.
|
|
19
|
+
- Don't: Do not put text straight on soft art with `text-overlay-foreground` ink. Soft is a light ground in every mode, so overlay ink washes out on it; put the content on a Card or a chip over the art, or use the default vivid intensity for a short overlay label.
|
|
18
20
|
- Don't: Do not put body text over it. The art spans the palette's lightest and most saturated tones, so no single ink is guaranteed to clear contrast across it; reserve overlays for a short label on a scrim or an avatar's initials.
|
|
19
21
|
- Don't: Do not pass colours, and reach for `tones` rarely. The art follows the preset's --art-* tokens on purpose; to change how a product's art looks, set the --theme-art-* hooks in its theme or preset, so every cover, banner and avatar changes together.
|
|
20
22
|
- Don't: Do not use it as a page or hero background. It paints at the element's pixel size, so a full-viewport instance costs a large frame on every theme change; it is sized for covers, banners and tiles.
|
|
@@ -26,12 +28,14 @@ Use GradientArt where a real image belongs but none exists yet, or where the ima
|
|
|
26
28
|
| variant | "mesh" \| "grain" | "mesh" | mesh: soft spots of colour pulled together by gentle waves. grain: glowing light over deep corners with a printed texture. |
|
|
27
29
|
| tones | ("info" \| "3" \| "2" \| "1" \| "warning")[] | — | Paint these `--tone-*` colours instead of the preset's art palette. Leave unset to follow the preset's art direction. |
|
|
28
30
|
| seed | string \| number | — | Fixes the composition: the same seed paints the same art in every theme. Pass a stable id. |
|
|
31
|
+
| intensity | "vivid" \| "soft" | "vivid" | vivid: the preset's art at full strength. soft: every colour and the ground lifted to a light pastel of itself (high lightness, half chroma) — a wash for header bands and for staging chips and logo tiles, light in both modes. |
|
|
29
32
|
| children | ReactNode | — | Rendered above the art — initials, a glyph, a short label. |
|
|
30
33
|
| className | string | — | Size and shape: aspect, height, rounding. |
|
|
31
34
|
|
|
32
35
|
## Variants
|
|
33
36
|
|
|
34
37
|
- **variant**: mesh, grain
|
|
38
|
+
- **intensity**: vivid, soft
|
|
35
39
|
|
|
36
40
|
## Tokens used
|
|
37
41
|
|
|
@@ -78,3 +82,26 @@ import { GradientArt } from "@sableui/react"
|
|
|
78
82
|
|
|
79
83
|
<GradientArt seed="Mara Okafor" className="grid size-10 place-items-center rounded-full text-label-md text-overlay-foreground">MO</GradientArt>
|
|
80
84
|
```
|
|
85
|
+
|
|
86
|
+
### Soft header band with logo tiles
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { Button, Card, CardDescription, CardFooter, CardHeader, CardTitle, GradientArt, Icon } from "@sableui/react"
|
|
90
|
+
|
|
91
|
+
<Card className="w-full max-w-sm">
|
|
92
|
+
<GradientArt intensity="soft" seed="Integrations" className="flex h-32 items-center justify-center gap-3 rounded-t-lg">
|
|
93
|
+
{["mail", "calendar", "chart"].map((name) => (
|
|
94
|
+
<span key={name} className="grid size-12 place-items-center rounded-lg border border-border-floating bg-popover text-popover-foreground shadow-md">
|
|
95
|
+
<Icon name={name} className="text-xl" />
|
|
96
|
+
</span>
|
|
97
|
+
))}
|
|
98
|
+
</GradientArt>
|
|
99
|
+
<CardHeader>
|
|
100
|
+
<CardTitle>Connect your tools</CardTitle>
|
|
101
|
+
<CardDescription>Bring mail, calendar and analytics into one place.</CardDescription>
|
|
102
|
+
</CardHeader>
|
|
103
|
+
<CardFooter>
|
|
104
|
+
<Button>Connect</Button>
|
|
105
|
+
</CardFooter>
|
|
106
|
+
</Card>
|
|
107
|
+
```
|
|
@@ -30,7 +30,7 @@ Use InputGroup when something belongs inside the field with the text: a unit, a
|
|
|
30
30
|
|
|
31
31
|
## Tokens used
|
|
32
32
|
|
|
33
|
-
`
|
|
33
|
+
`field-border`, `field`, `ring`, `destructive`, `muted-foreground`, `foreground`
|
|
34
34
|
|
|
35
35
|
## Motion
|
|
36
36
|
|
package/llms/components/input.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Styled native text input. Soft-square (rounded-sm),
|
|
5
|
+
Styled native text input. Soft-square (rounded-sm), the theme's field surface (outlined or filled), 2px focus ring; aria-invalid raises the destructive error treatment.
|
|
6
6
|
|
|
7
7
|
**Keywords:** input, text input, field, form, email, search, file, invalid, validation
|
|
8
8
|
|
|
@@ -16,6 +16,8 @@ Use Input for short freeform text, search, email, password, file, and native inp
|
|
|
16
16
|
- Do: Use type, inputMode, autoComplete, and placeholder as progressive hints, not as the only label.
|
|
17
17
|
- Do: Set aria-invalid or use Field error copy when validation fails.
|
|
18
18
|
- Do: Keep a focusable field at 16px or larger on mobile. iOS Safari zooms the whole page when a focused input computes under 16px and never zooms back out. Input handles this itself — below the sm breakpoint it renders text-body-lg (17px) and returns to text-body-md at sm and up. Build a custom field the same way: a second paired tier under a max-sm: variant, never a hand-set font-size.
|
|
19
|
+
- Do: Choose outlined or filled fields once, in the theme: `--theme-field: var(--muted)` with `--theme-field-border: transparent` turns every Input, Textarea, Select, Combobox, SearchInput, InputGroup and ChatComposer into a borderless filled well; unset, they are outlined on the card. Every field reads the `field` / `field-border` pair, so they change together.
|
|
20
|
+
- Don't: Restyle one Input as a filled chip with `bg-muted border-transparent`. It drifts from every other field on the page, and the theme can no longer move them together; set the field tokens instead.
|
|
19
21
|
- Don't: Override an Input's type with a page tier below 16px (text-body-md, text-body-sm, text-label-*) without a >=16px mobile arm — that reintroduces the iOS focus zoom.
|
|
20
22
|
- Don't: Use placeholder text as the label; it disappears once the user types and is not enough context.
|
|
21
23
|
- Don't: Use Input for long body copy. Use Textarea for multi-line content.
|
|
@@ -45,7 +47,7 @@ Use Input for short freeform text, search, email, password, file, and native inp
|
|
|
45
47
|
|
|
46
48
|
## Tokens used
|
|
47
49
|
|
|
48
|
-
`
|
|
50
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
49
51
|
|
|
50
52
|
## Accessibility
|
|
51
53
|
|
|
@@ -46,7 +46,7 @@ Use LineChart to compare how two or more measures change over the same sequence,
|
|
|
46
46
|
|
|
47
47
|
## Tokens used
|
|
48
48
|
|
|
49
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
49
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
50
50
|
|
|
51
51
|
## Motion
|
|
52
52
|
|
package/llms/components/meter.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A role=meter reading over Base UI Meter, drawn as a bar or a ring — how full something is within a known range (storage, quota, strength), with a tone for good/caution/bad. Not task progress.
|
|
6
6
|
|
|
7
|
-
**Keywords:** meter, usage bar, storage, quota, capacity, limit, password strength, level
|
|
7
|
+
**Keywords:** meter, usage bar, storage, quota, capacity, limit, password strength, level, segmented, tick bar, score, credits, spend limit
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
@@ -15,15 +15,17 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
15
15
|
- Do: Label it and show the reading in units people use ("7.2 of 10 GB"), with MeterLabel and MeterValue. The bar shows proportion; the number is what gets acted on.
|
|
16
16
|
- Do: Pick `tone` from thresholds you own and apply them consistently across the page — warning near the limit, destructive at or over it. The component does not guess where good becomes bad.
|
|
17
17
|
- Do: Use shape="ring" when the reading is a score that belongs in a small, glanceable spot on a card. It is the same measurement drawn as a circular gauge, not a different component.
|
|
18
|
+
- Do: Use `segments` (a tick bar, ▮▮▮▮▮▮▯▯▯▯) for a discrete budget, score or quota read at a glance — a spend limit, credits left, an ICP lead score, months of data coverage. Pick a count that matches the unit when there is one (10 for a score out of 10, 12 for months) and otherwise size it to the track: a tick reads as a tick when it is taller than it is wide, about one per 8px of track (40 across a card row, 20 in a narrow table cell). Any reading above min shows at least one tick.
|
|
18
19
|
- Don't: Do not use Meter for a task in flight — an upload, an install, a step count. That is Progress; Meter tells assistive tech nothing is happening. → use **Progress**
|
|
19
20
|
- Don't: Do not use it for a value without a meaningful maximum (followers, total spend). A bar implies a full scale; an unbounded count is a stat.
|
|
21
|
+
- Don't: Do not segment a fine-grained continuous value where the exact proportion matters (disk at 73.4%) — ticks round it to the nearest mark; use the continuous bar. And do not use a tick bar for a task in flight or steps completed: that is Progress, or a Stepper for named steps. → use **Progress**
|
|
20
22
|
|
|
21
23
|
### Anatomy
|
|
22
24
|
|
|
23
25
|
| Part | Required | Description |
|
|
24
26
|
| --- | --- | --- |
|
|
25
27
|
| MeterTrack | yes | The rail that represents the full range. |
|
|
26
|
-
| MeterIndicator | yes | The fill, sized from value and coloured by tone. |
|
|
28
|
+
| MeterIndicator | yes | The fill, sized from value and coloured by tone. Not used with `segments`: the track draws its ticks instead. |
|
|
27
29
|
| MeterLabel | no | The visible name; also names the meter for assistive tech. |
|
|
28
30
|
| MeterValue | no | The formatted reading. |
|
|
29
31
|
|
|
@@ -31,7 +33,7 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
31
33
|
|
|
32
34
|
| Name | Type | Description |
|
|
33
35
|
| --- | --- | --- |
|
|
34
|
-
| MeterTrack | Base UI Track props + className + size | The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default. |
|
|
36
|
+
| MeterTrack | Base UI Track props + className + size | The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default — or, when the Meter has `segments`, its row of ticks (children are not rendered then). |
|
|
35
37
|
| MeterIndicator | Base UI Indicator props + className + tone | The fill. Inherits the Meter's tone unless given its own. |
|
|
36
38
|
| MeterLabel | Base UI Label props + className | Visible label; wires aria-labelledby on the root. |
|
|
37
39
|
| MeterValue | Base UI Value props + className | Formatted reading. Accepts a `(formatted, value) => ReactNode` child. |
|
|
@@ -47,6 +49,7 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
47
49
|
| shape | "bar" \| "ring" | "bar" | bar is a horizontal track; ring is a circular gauge for a small, glanceable reading — the same value, min, max and tones. |
|
|
48
50
|
| size | "sm" \| "md" \| "lg" | "md" | Ring diameter (40 / 64 / 96px at the default spacing; the centre value steps with it), or bar thickness on the same sm / md / lg rails as Progress. |
|
|
49
51
|
| showValue | boolean | — | Print the value in the ring's centre (formatted like MeterValue). |
|
|
52
|
+
| segments | number | — | Draw the bar as this many separate ticks instead of a continuous fill (a quota, score or spend limit). Filled ticks = round(share × N), at least one for any reading above min, none at min. Ticks take the tone; empty ones sit on the rail colour. The segmented track has its own, taller sm / md / lg ladder. Bar only: ignored for shape="ring". |
|
|
50
53
|
| strokeWidth | number | 6 | Ring stroke width, in viewBox units of 100. |
|
|
51
54
|
| format | Intl.NumberFormatOptions | — | Formatting for MeterValue and aria-valuetext (percent, unit, currency). |
|
|
52
55
|
| getAriaValueText | (formatted: string, value: number) => string | — | Human-readable reading for assistive tech ("7.2 of 10 gigabytes used"). |
|
|
@@ -60,13 +63,14 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
60
63
|
|
|
61
64
|
## Tokens used
|
|
62
65
|
|
|
63
|
-
`secondary`, `primary`, `success`, `tone-warning`, `destructive`, `foreground`, `muted-foreground`
|
|
66
|
+
`secondary`, `primary`, `tone-3`, `success`, `tone-warning`, `destructive`, `foreground`, `muted-foreground`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-3-mark`
|
|
64
67
|
|
|
65
68
|
## Motion
|
|
66
69
|
|
|
67
70
|
- **arrival** · Meter · _no motion_ — A meter is a reading, present with the page that reports it. Animating the fill up from zero on load would act out a change that did not happen.
|
|
68
71
|
- **response** · Meter · _no motion_ — It reports and accepts no input. A bar that reacted to the pointer would read as a Slider the user could drag.
|
|
69
72
|
- **transition** · MeterIndicator · _custom (outside the recipe layer)_ — Width and colour ease on the motion tokens (no recipe animates width) when the reading changes — freeing space after a delete visibly shrinks the bar, so the user sees the cause and effect rather than a different bar.
|
|
73
|
+
- **transition** · MeterTrack (segments) · _custom (outside the recipe layer)_ — With segments, the fill runs through the ticks from the edge it is leaving — left to right as it grows, right to left as it shrinks — each tick easing its colour on the fast token after a stagger that is a share of the slow token (a full sweep is one slow duration) × --motion-expressive. Every tick switching at once would read as a different bar, not the same budget moving; `subtle` lands them together and off is instant.
|
|
70
74
|
- **activity** · Meter · _no motion_ — A meter has no indeterminate state; a reading is either known or not shown. Unknown-but-working is Progress with no value.
|
|
71
75
|
|
|
72
76
|
## Accessibility
|
|
@@ -111,6 +115,31 @@ import { Meter, MeterLabel, MeterTrack, MeterValue } from "@sableui/react"
|
|
|
111
115
|
</Meter>
|
|
112
116
|
```
|
|
113
117
|
|
|
118
|
+
### Segmented spend limit
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { Meter, MeterLabel, MeterTrack, MeterValue } from "@sableui/react"
|
|
122
|
+
|
|
123
|
+
<Meter value={1240} max={2000} segments={40} getAriaValueText={() => "€1,240 of €2,000 spent this month"}>
|
|
124
|
+
<div className="mb-2 flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
|
|
125
|
+
<MeterLabel>Monthly spend limit</MeterLabel>
|
|
126
|
+
<MeterValue>{() => "€1,240 / €2,000"}</MeterValue>
|
|
127
|
+
</div>
|
|
128
|
+
<MeterTrack />
|
|
129
|
+
</Meter>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Segmented score cell
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { Meter } from "@sableui/react"
|
|
136
|
+
|
|
137
|
+
<div className="flex items-center gap-3">
|
|
138
|
+
<Meter value={8} max={10} segments={10} size="sm" tone="success" className="w-24" aria-label="ICP score" getAriaValueText={(_, v) => `${v} out of 10`} />
|
|
139
|
+
<span className="text-mono-sm text-foreground">8/10</span>
|
|
140
|
+
</div>
|
|
141
|
+
```
|
|
142
|
+
|
|
114
143
|
### Tones
|
|
115
144
|
|
|
116
145
|
```tsx
|
package/llms/components/note.md
CHANGED
|
@@ -35,7 +35,7 @@ Use Note for helpful supporting information that should sit near the relevant co
|
|
|
35
35
|
|
|
36
36
|
## Tokens used
|
|
37
37
|
|
|
38
|
-
`foreground`, `border`, `muted`, `muted-foreground`, `tone-info`, `tone-warning`, `success`, `destructive`
|
|
38
|
+
`foreground`, `border`, `muted`, `muted-foreground`, `tone-info`, `tone-warning`, `success`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`
|
|
39
39
|
|
|
40
40
|
## Motion
|
|
41
41
|
|
|
@@ -32,7 +32,7 @@ Use NumberField for numeric values that benefit from validation, min/max bounds,
|
|
|
32
32
|
|
|
33
33
|
## Tokens used
|
|
34
34
|
|
|
35
|
-
`
|
|
35
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`
|
|
36
36
|
|
|
37
37
|
## Accessibility
|
|
38
38
|
|
|
@@ -48,7 +48,7 @@ Use OtpField for a short, fixed-length code the user copies from somewhere else
|
|
|
48
48
|
|
|
49
49
|
## Tokens used
|
|
50
50
|
|
|
51
|
-
`
|
|
51
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `border-strong`, `primary`, `ring`, `destructive`
|
|
52
52
|
|
|
53
53
|
## Motion
|
|
54
54
|
|
|
@@ -44,7 +44,7 @@ Use PieChart for a share of one total across two to six categories — traffic s
|
|
|
44
44
|
|
|
45
45
|
## Tokens used
|
|
46
46
|
|
|
47
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
47
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
48
48
|
|
|
49
49
|
## Motion
|
|
50
50
|
|
|
@@ -43,7 +43,7 @@ Use RadarChart to compare the shape of a profile across five to eight dimensions
|
|
|
43
43
|
|
|
44
44
|
## Tokens used
|
|
45
45
|
|
|
46
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
46
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
47
47
|
|
|
48
48
|
## Motion
|
|
49
49
|
|
|
@@ -45,7 +45,7 @@ Use RadialChart for progress toward goals (several rings), a single score or usa
|
|
|
45
45
|
|
|
46
46
|
## Tokens used
|
|
47
47
|
|
|
48
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
48
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
49
49
|
|
|
50
50
|
## Motion
|
|
51
51
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm),
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
**Keywords:** search, input, filter, query
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ Use SearchInput for text queries that filter or search a known content set.
|
|
|
15
15
|
- Do: Use a clear placeholder that names the content being searched.
|
|
16
16
|
- Do: Provide a visible clear affordance when query state persists.
|
|
17
17
|
- Do: When a button stands in for search (a header trigger that opens CommandMenu with ⌘K), use CommandMenuTrigger. It wears this field's shell from the same source, so the two cannot drift; a hand-styled button already did once, into a borderless chip that matched neither the field nor a button.
|
|
18
|
+
- Do: 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.
|
|
18
19
|
- Don't: 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.
|
|
19
20
|
- Don't: Do not search on every keystroke against an expensive backend without debouncing. The user sees results flicker and reorder under them, and you send a request per character.
|
|
20
21
|
|
|
@@ -34,7 +35,7 @@ Use SearchInput for text queries that filter or search a known content set.
|
|
|
34
35
|
|
|
35
36
|
## Tokens used
|
|
36
37
|
|
|
37
|
-
`
|
|
38
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
38
39
|
|
|
39
40
|
## Motion
|
|
40
41
|
|
|
@@ -82,6 +83,23 @@ import { Field, SearchInput } from "@sableui/react"
|
|
|
82
83
|
<Field label="Search posts"><SearchInput defaultValue="roadmap" className="max-w-80" /></Field>
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
### Highlighting matches in the results
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { SearchInput, useMatchHighlight } from "@sableui/react"
|
|
90
|
+
|
|
91
|
+
const [q, setQ] = useState("")
|
|
92
|
+
const results = useMatchHighlight<HTMLUListElement>(q, { selector: "[data-title]" })
|
|
93
|
+
const shown = posts.filter((post) => post.title.toLowerCase().includes(q.trim().toLowerCase()))
|
|
94
|
+
|
|
95
|
+
<SearchInput value={q} onValueChange={setQ} placeholder="Search posts" />
|
|
96
|
+
<ul ref={results}>
|
|
97
|
+
{shown.map((post) => (
|
|
98
|
+
<li key={post.id}><span data-title>{post.title}</span></li>
|
|
99
|
+
))}
|
|
100
|
+
</ul>
|
|
101
|
+
```
|
|
102
|
+
|
|
85
103
|
### Disabled
|
|
86
104
|
|
|
87
105
|
```tsx
|
|
@@ -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
|
+
```
|
|
@@ -40,7 +40,7 @@ Use SegmentedBar when a stat card or row needs to show how a total divides into
|
|
|
40
40
|
|
|
41
41
|
## Tokens used
|
|
42
42
|
|
|
43
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `primary`, `muted-foreground`, `success`, `tone-warning`, `destructive`
|
|
43
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `primary`, `muted-foreground`, `success`, `tone-warning`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
44
44
|
|
|
45
45
|
## Motion
|
|
46
46
|
|
|
@@ -50,7 +50,7 @@ Use Select when users choose one value from a known, finite set. Use Combobox fo
|
|
|
50
50
|
|
|
51
51
|
## Tokens used
|
|
52
52
|
|
|
53
|
-
`
|
|
53
|
+
`field-border`, `field`, `foreground`, `ring`, `border`, `popover`, `popover-foreground`, `accent`, `accent-foreground`, `muted-foreground`
|
|
54
54
|
|
|
55
55
|
## Motion
|
|
56
56
|
|
|
@@ -15,6 +15,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
15
15
|
- Do: Use it for stable app destinations that should remain visible while content changes.
|
|
16
16
|
- Do: Keep item labels predictable and active state obvious.
|
|
17
17
|
- Do: Set surface="page" when a full-width top bar sits above the rail. A theme's tinted or dark rail is for a full-height rail that frames the app.
|
|
18
|
+
- Do: Keep surface="rail" inside AppShell frame="inset": the rail's tint becomes the ground the main panel floats on. Do not add a border-r class back — the gap is the divider.
|
|
18
19
|
- Don't: Do not use Sidebar for page-local settings, filters, or temporary action groups.
|
|
19
20
|
- Don't: Do not put page content in it. It is fixed-width chrome OUTSIDE the content grid, so anything in it is exempt from the page's measure and rhythm.
|
|
20
21
|
|
|
@@ -40,7 +41,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
40
41
|
| collapsed | boolean | false | Collapse the rail to an icon-only width (--sidebar-width-collapsed). Items hide their label + badge and reveal the label in a hover tooltip; the content area reflows. Controlled — wire a toggle from app state. Read the state in custom header/footer content via useSidebar(). |
|
|
41
42
|
| icons | boolean | true | Render the leading icon on each SidebarItem. Set false for a text-only rail. Ignored while collapsed (icon-only by definition). |
|
|
42
43
|
| variant | "app" \| "inline" | "app" | Where the rail lives. "app" is the page's chrome column — sticky, full viewport height, hidden below lg where AppHeader + BottomNav take over. "inline" is always visible and fills its parent's height, for a rail inside a bounded frame (a nested shell, an embedded preview). Use it instead of overriding the layout with !important classes. |
|
|
43
|
-
| surface | "rail" \| "page" | "rail" | Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border — right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border — use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat. |
|
|
44
|
+
| surface | "rail" \| "page" | "rail" | Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border — right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border — use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat. In AppShell's sidebar slot with frame="inset", the rail's surface is also the shell's ground: from lg a "rail" rail drops its border (the gap around the main panel divides them) and either surface stretches to the shell's body row instead of sizing to the viewport. AppShell signals this to its sidebar slot only, so a Sidebar elsewhere in the page is unaffected. |
|
|
44
45
|
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the collapse ease and the current-page glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
45
46
|
|
|
46
47
|
## Variants
|
|
@@ -49,7 +50,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
49
50
|
|
|
50
51
|
## Tokens used
|
|
51
52
|
|
|
52
|
-
`sidebar`, `sidebar-foreground`, `sidebar-border`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-ring`
|
|
53
|
+
`sidebar`, `sidebar-foreground`, `sidebar-border`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-ring`, `shell-ground`
|
|
53
54
|
|
|
54
55
|
## Motion
|
|
55
56
|
|
|
@@ -36,7 +36,7 @@ Use Sparkline next to a number to show where it has been — a KPI's last 30 day
|
|
|
36
36
|
|
|
37
37
|
## Tokens used
|
|
38
38
|
|
|
39
|
-
`primary`, `tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `muted-foreground`, `success`, `destructive`
|
|
39
|
+
`primary`, `tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `muted-foreground`, `success`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
40
40
|
|
|
41
41
|
## Motion
|
|
42
42
|
|
|
@@ -39,7 +39,7 @@ Use StatusDot as a compact visual state cue next to text, avatars, rows, or labe
|
|
|
39
39
|
|
|
40
40
|
## Tokens used
|
|
41
41
|
|
|
42
|
-
`palette-dot`, `muted-foreground`, `tone-3`, `success`, `tone-warning`, `tone-info`, `tone-1`, `tone-2`, `destructive`
|
|
42
|
+
`palette-dot`, `muted-foreground`, `tone-3`, `success`, `tone-warning`, `tone-info`, `tone-1`, `tone-2`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
43
43
|
|
|
44
44
|
## Accessibility
|
|
45
45
|
|
|
@@ -51,7 +51,7 @@ Use Stepper above or beside a flow that is split into a known, small number of n
|
|
|
51
51
|
|
|
52
52
|
## Tokens used
|
|
53
53
|
|
|
54
|
-
`primary`, `primary-foreground`, `background`, `foreground`, `muted-foreground`, `border-strong`, `destructive`, `destructive-foreground`, `ring`
|
|
54
|
+
`primary`, `primary-foreground`, `background`, `foreground`, `muted-foreground`, `border-strong`, `destructive`, `destructive-foreground`, `ring`, `destructive-mark`
|
|
55
55
|
|
|
56
56
|
## Motion
|
|
57
57
|
|
package/llms/components/table.md
CHANGED
|
@@ -36,8 +36,8 @@ Use Table for structured data with consistent columns and rows. Use DataTable wh
|
|
|
36
36
|
| TableBody | ComponentPropsWithRef<"tbody"> | The data row group (tbody). |
|
|
37
37
|
| TableFooter | ComponentPropsWithRef<"tfoot"> | Summary row group (tfoot) with a muted fill. |
|
|
38
38
|
| TableRow | ComponentPropsWithRef<"tr"> | A row (tr) — hairline bottom border, hover lift, and a selected state via the data-selected attribute. |
|
|
39
|
-
| TableHead | ComponentPropsWithRef<"th"> | A column header cell (th) in muted label styling. |
|
|
40
|
-
| TableCell | ComponentPropsWithRef<"td"> | A data cell (td). |
|
|
39
|
+
| TableHead | ComponentPropsWithRef<"th"> & { numeric?: boolean } | A column header cell (th) in muted label styling. `numeric` marks a number column: end-aligned, tabular figures — set it on the column's cells too. |
|
|
40
|
+
| TableCell | ComponentPropsWithRef<"td"> & { numeric?: boolean } | A data cell (td). `numeric` end-aligns it with tabular figures, matching its column's header. |
|
|
41
41
|
| TableCaption | ComponentPropsWithRef<"caption"> | Accessible description of the table (caption), muted. |
|
|
42
42
|
|
|
43
43
|
## Props
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Styled native multi-line text control. Soft-square,
|
|
5
|
+
Styled native multi-line text control. Soft-square, the theme's field surface, --ring focus, and an aria-invalid destructive error state.
|
|
6
6
|
|
|
7
7
|
**Keywords:** textarea, multi line input, form, text
|
|
8
8
|
|
|
@@ -31,7 +31,7 @@ Use Textarea for multi-line freeform text entry such as notes, descriptions, or
|
|
|
31
31
|
|
|
32
32
|
## Tokens used
|
|
33
33
|
|
|
34
|
-
`
|
|
34
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
35
35
|
|
|
36
36
|
## Accessibility
|
|
37
37
|
|
|
@@ -30,6 +30,7 @@ Sable's components ship accessible behavior from Base UI. These rules cover what
|
|
|
30
30
|
- **Never:** Color as the only signal (a red border with no message). **Instead:** Color plus text or an icon. Roughly 1 in 12 men cannot tell red from green.
|
|
31
31
|
- **Never:** Opacity on the focus ring. **Instead:** ring-ring at full opacity; tune the token in the theme. A faded ring loses the contrast the theme tested. *Enforced by sable-lint: faded-focus-ring.*
|
|
32
32
|
- **Never:** A brand or status fill retuned under its old label — dark ink on a bright red that passes 4.5:1 at APCA Lc 42. **Instead:** A label that clears 4.5:1 and APCA Lc 60 in both modes. Move the fill's lightness, not its hue, keeping the ink its colour takes. A status colour on a surface is its own token (`--destructive-text`), so the fill can move freely. The ratio alone passes labels that read muddy. A deliberate brand pair is allowed with a recorded reason (lint disable or preset exceptions). *Enforced by sable-lint: label-contrast.*
|
|
33
|
+
- **Never:** A label fill painted on a mark that carries no text — a meter, a segmented bar, a status dot, a chart series, a soft tint. **Instead:** The colour's `-mark` token (`bg-success-mark`, `bg-tone-warning-mark/15`): the same hue lifted and saturated, held to 2:1 on the page. Text contrast is for text; a graphic owes visibility, and 3:1 (WCAG 1.4.11) only when it is the sole carrier of its meaning. A fill is pinned dark enough for a white label. On a bar or dot with nothing to label, that darkness only reads as mud: a brown warning, a forest-green success.
|
|
33
34
|
- **Never:** Blocking paste, or type="number" for codes and card numbers. **Instead:** type="text" inputMode="numeric" with the right autocomplete. Blocking paste breaks password managers; number inputs scroll-change identifiers.
|
|
34
35
|
- **Never:** forced-color-adjust: none to 'fix' high-contrast mode. **Instead:** Let the system colors apply; verify the result. It removes the user's chosen colors exactly where they need them.
|
|
35
36
|
- **Never:** outline: none without a replacement. **Instead:** focus-visible:ring-2 ring-ring. Keyboard users lose their place.
|