@sableui/react 0.13.1 → 0.14.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 +2 -2
- package/CHANGELOG.md +132 -0
- package/CONTRIBUTING.md +7 -5
- package/DESIGN.md +8 -3
- package/bin/sable.mjs +17 -1
- package/dist/blocks/approval-block.js +93 -0
- package/dist/blocks/approval-block.meta.js +23 -0
- package/dist/blocks/index.d.ts +17 -2
- package/dist/blocks/index.js +4 -0
- package/dist/components/accordion/accordion.meta.js +10 -3
- package/dist/components/area-chart/area-chart.meta.js +10 -3
- package/dist/components/attachment/attachment.meta.js +26 -0
- package/dist/components/badge/badge.meta.js +2 -1
- package/dist/components/banner/banner.meta.js +4 -2
- package/dist/components/bar-chart/bar-chart.meta.js +11 -4
- package/dist/components/bottom-nav/bottom-nav.js +3 -1
- package/dist/components/bottom-nav/bottom-nav.meta.js +32 -0
- package/dist/components/breadcrumbs/breadcrumbs.meta.js +26 -0
- package/dist/components/button/button.js +21 -4
- package/dist/components/button/button.meta.js +7 -7
- package/dist/components/button-group/button-group.meta.js +2 -1
- package/dist/components/calendar/calendar.js +18 -1
- package/dist/components/calendar/calendar.meta.js +38 -0
- package/dist/components/carousel/carousel.js +6 -4
- package/dist/components/carousel/carousel.meta.js +42 -0
- package/dist/components/chat-composer/chat-composer.js +21 -21
- package/dist/components/chat-composer/chat-composer.meta.js +38 -0
- package/dist/components/chat-composer/use-eased-growth.js +70 -0
- package/dist/components/checkbox/checkbox-mark.js +8 -1
- package/dist/components/checkbox/checkbox.js +3 -1
- package/dist/components/checkbox/checkbox.meta.js +34 -1
- package/dist/components/checkbox-card/checkbox-card.js +3 -0
- package/dist/components/checkbox-card/checkbox-card.meta.js +32 -0
- package/dist/components/code-block/code-block.js +14 -3
- package/dist/components/code-block/code-block.meta.js +28 -1
- package/dist/components/collapsible/collapsible.meta.js +34 -1
- package/dist/components/color-field/color-field.meta.js +26 -0
- package/dist/components/combobox/combobox.meta.js +48 -2
- package/dist/components/command-menu/command-menu.js +8 -3
- package/dist/components/command-menu/command-menu.meta.js +44 -3
- package/dist/components/confirm-button/confirm-button.js +179 -0
- package/dist/components/confirm-button/confirm-button.meta.js +143 -0
- package/dist/components/confirm-button/index.js +7 -0
- package/dist/components/context-card/context-card.meta.js +2 -1
- package/dist/components/data-table/data-table.js +6 -2
- package/dist/components/data-table/data-table.meta.js +32 -0
- package/dist/components/dialog/dialog.meta.js +6 -3
- package/dist/components/disclosure-picker/disclosure-picker.meta.js +2 -1
- package/dist/components/entity/entity.meta.js +26 -0
- package/dist/components/feedback/feedback.js +113 -47
- package/dist/components/feedback/feedback.meta.js +49 -4
- package/dist/components/inline-status/inline-status.meta.js +2 -1
- package/dist/components/input-group/input-group.meta.js +27 -1
- package/dist/components/line-chart/line-chart.meta.js +10 -3
- package/dist/components/menu/menu.meta.js +2 -1
- package/dist/components/menubar/menubar.meta.js +2 -1
- package/dist/components/meter/meter.meta.js +2 -1
- package/dist/components/middle-truncate/middle-truncate.meta.js +1 -1
- package/dist/components/note/note.meta.js +34 -1
- package/dist/components/notification-badge/notification-badge.js +17 -5
- package/dist/components/notification-badge/notification-badge.meta.js +34 -2
- package/dist/components/number-ticker/index.js +7 -0
- package/dist/components/number-ticker/number-ticker.js +246 -0
- package/dist/components/number-ticker/number-ticker.meta.js +159 -0
- package/dist/components/otp-field/otp-field.js +23 -3
- package/dist/components/otp-field/otp-field.meta.js +23 -2
- package/dist/components/page-loader/page-loader.meta.js +2 -1
- package/dist/components/panel-group/panel-group.meta.js +26 -0
- package/dist/components/panel-section/panel-section.js +37 -10
- package/dist/components/panel-section/panel-section.meta.js +32 -0
- package/dist/components/pie-chart/pie-chart.js +20 -11
- package/dist/components/pie-chart/pie-chart.meta.js +17 -4
- package/dist/components/pill/pill.meta.js +34 -1
- package/dist/components/popover/popover.meta.js +4 -2
- package/dist/components/progress/progress.meta.js +2 -1
- package/dist/components/radar-chart/radar-chart.js +4 -3
- package/dist/components/radar-chart/radar-chart.meta.js +10 -3
- package/dist/components/radial-chart/radial-chart.js +22 -22
- package/dist/components/radial-chart/radial-chart.meta.js +17 -4
- package/dist/components/radio/radio.meta.js +28 -1
- package/dist/components/radio-card/radio-card.meta.js +38 -0
- package/dist/components/relative-time/relative-time.meta.js +32 -0
- package/dist/components/scroller/scroller.meta.js +32 -0
- package/dist/components/scrub-field/scrub-field.meta.js +32 -0
- package/dist/components/search-input/search-input.js +9 -7
- package/dist/components/search-input/search-input.meta.js +26 -0
- package/dist/components/segmented-bar/segmented-bar.meta.js +2 -1
- package/dist/components/select/select.meta.js +4 -2
- package/dist/components/sheet/sheet.meta.js +4 -2
- package/dist/components/show-more/show-more.js +1 -4
- package/dist/components/show-more/show-more.meta.js +38 -0
- package/dist/components/side-nav/side-nav.js +21 -1
- package/dist/components/side-nav/side-nav.meta.js +34 -1
- package/dist/components/side-nav/side-nav.variants.js +3 -1
- package/dist/components/sidebar/sidebar.js +31 -8
- package/dist/components/sidebar/sidebar.meta.js +38 -0
- package/dist/components/skeleton/skeleton.meta.js +2 -1
- package/dist/components/slider/slider.meta.js +26 -0
- package/dist/components/sparkline/sparkline.js +7 -4
- package/dist/components/sparkline/sparkline.meta.js +9 -2
- package/dist/components/spinner/spinner.meta.js +2 -1
- package/dist/components/stepper/stepper.meta.js +4 -2
- package/dist/components/switch/switch.meta.js +2 -1
- package/dist/components/table/table.meta.js +28 -1
- package/dist/components/tabs/tabs.js +27 -8
- package/dist/components/tabs/tabs.meta.js +50 -5
- package/dist/components/theme-selector/theme-selector.meta.js +32 -0
- package/dist/components/theme-switcher/theme-switcher.js +4 -1
- package/dist/components/theme-switcher/theme-switcher.meta.js +42 -0
- package/dist/components/thinking-orb/thinking-orb.meta.js +4 -2
- package/dist/components/toast/toast.meta.js +2 -1
- package/dist/components/toggle/toggle.js +28 -5
- package/dist/components/toggle/toggle.meta.js +46 -3
- package/dist/components/tool-call/index.js +8 -0
- package/dist/components/tool-call/tool-call.js +117 -0
- package/dist/components/tool-call/tool-call.meta.js +147 -0
- package/dist/components/tool-call/tool-call.variants.js +33 -0
- package/dist/components/toolbar/toolbar.meta.js +2 -1
- package/dist/components/tooltip/index.js +3 -1
- package/dist/components/tooltip/tooltip.js +84 -0
- package/dist/components/tooltip/tooltip.meta.js +18 -2
- package/dist/components/tree/tree.js +71 -6
- package/dist/components/tree/tree.meta.js +32 -0
- package/dist/components/video/video.meta.js +32 -0
- package/dist/file-hashes.json +139 -122
- package/dist/foundations/motion.js +49 -3
- package/dist/index.d.ts +616 -46
- package/dist/index.js +4 -1
- package/dist/lib/chart/cartesian.js +9 -10
- package/dist/lib/chart/chart-center.js +30 -0
- package/dist/lib/chart/chart-frame.js +15 -8
- package/dist/lib/chart/dim.js +13 -0
- package/dist/lib/chart/format.js +7 -1
- package/dist/lib/chart/motion.js +6 -4
- package/dist/lib/highlight-glide.js +68 -10
- package/dist/lib/taxonomy.js +3 -0
- package/dist/{meta-DVG8ceze.d.ts → meta-DX5r5wQL.d.ts} +8 -0
- package/dist/motion/MotionProvider.js +45 -3
- package/dist/motion/Reveal.js +1 -1
- package/dist/motion/TextReveal.js +1 -1
- package/dist/motion/index.js +15 -2
- package/dist/motion/useAutoSize.js +42 -0
- package/dist/motion/useShake.js +25 -0
- package/dist/patterns/index.d.ts +1 -1
- package/dist/styles.css +298 -5
- package/llms/blocks/approval-block.md +128 -0
- package/llms/components/accordion.md +4 -3
- package/llms/components/area-chart.md +4 -3
- package/llms/components/attachment.md +7 -0
- package/llms/components/badge.md +1 -1
- package/llms/components/banner.md +2 -2
- package/llms/components/bar-chart.md +5 -4
- package/llms/components/bottom-nav.md +8 -0
- package/llms/components/breadcrumbs.md +7 -0
- package/llms/components/button-group.md +1 -1
- package/llms/components/button.md +6 -6
- package/llms/components/calendar.md +9 -0
- package/llms/components/carousel.md +17 -0
- package/llms/components/chat-composer.md +9 -0
- package/llms/components/checkbox-card.md +8 -0
- package/llms/components/checkbox.md +9 -1
- package/llms/components/code-block.md +8 -0
- package/llms/components/collapsible.md +9 -1
- package/llms/components/color-field.md +7 -0
- package/llms/components/combobox.md +12 -2
- package/llms/components/command-menu.md +12 -3
- package/llms/components/confirm-button.md +92 -0
- package/llms/components/context-card.md +1 -1
- package/llms/components/data-table.md +8 -0
- package/llms/components/dialog.md +3 -3
- package/llms/components/disclosure-picker.md +1 -1
- package/llms/components/entity.md +7 -0
- package/llms/components/feedback.md +22 -3
- package/llms/components/inline-status.md +1 -1
- package/llms/components/input-group.md +8 -1
- package/llms/components/line-chart.md +4 -3
- package/llms/components/menu.md +1 -1
- package/llms/components/menubar.md +1 -1
- package/llms/components/meter.md +1 -1
- package/llms/components/middle-truncate.md +1 -1
- package/llms/components/note.md +9 -1
- package/llms/components/notification-badge.md +10 -2
- package/llms/components/number-ticker.md +86 -0
- package/llms/components/otp-field.md +15 -1
- package/llms/components/page-loader.md +1 -1
- package/llms/components/panel-group.md +7 -0
- package/llms/components/panel-section.md +8 -0
- package/llms/components/pie-chart.md +6 -4
- package/llms/components/pill.md +9 -1
- package/llms/components/popover.md +2 -2
- package/llms/components/progress.md +1 -1
- package/llms/components/radar-chart.md +4 -3
- package/llms/components/radial-chart.md +6 -4
- package/llms/components/radio-card.md +9 -0
- package/llms/components/radio.md +8 -1
- package/llms/components/relative-time.md +8 -0
- package/llms/components/scroller.md +8 -0
- package/llms/components/scrub-field.md +8 -0
- package/llms/components/search-input.md +7 -0
- package/llms/components/segmented-bar.md +1 -1
- package/llms/components/select.md +2 -2
- package/llms/components/sheet.md +2 -2
- package/llms/components/show-more.md +9 -0
- package/llms/components/side-nav.md +9 -1
- package/llms/components/sidebar.md +9 -0
- package/llms/components/skeleton.md +1 -1
- package/llms/components/slider.md +7 -0
- package/llms/components/sparkline.md +3 -2
- package/llms/components/spinner.md +1 -1
- package/llms/components/stepper.md +2 -2
- package/llms/components/switch.md +1 -1
- package/llms/components/table.md +8 -1
- package/llms/components/tabs.md +22 -4
- package/llms/components/theme-selector.md +8 -0
- package/llms/components/theme-switcher.md +17 -0
- package/llms/components/thinking-orb.md +2 -2
- package/llms/components/toast.md +1 -1
- package/llms/components/toggle.md +20 -3
- package/llms/components/tool-call.md +115 -0
- package/llms/components/toolbar.md +1 -1
- package/llms/components/tooltip.md +12 -2
- package/llms/components/tree.md +8 -0
- package/llms/components/video.md +8 -0
- package/llms/foundations/motion.md +48 -3
- package/llms/reference/utilities.md +23 -1
- package/llms/tasks/reviewing-motion.md +14 -0
- package/llms-full.txt +1004 -98
- package/llms.txt +16 -11
- package/package.json +1 -1
- package/registry.json +2708 -187
- package/targets/ios.json +27 -0
package/llms/components/menu.md
CHANGED
|
@@ -16,7 +16,7 @@ Use Menu for a compact list of actions opened from a visible trigger.
|
|
|
16
16
|
- Do: Keep item labels action-oriented and easy to scan.
|
|
17
17
|
- Do: Inside a <Workspace> a Menu adapts automatically — its popup re-applies the compact density across the portal, so items render on the chrome type tier and the control ladder resolves to workspace sizes. Do NOT override the size per call site; that was necessary before v0.41 and is now drift.
|
|
18
18
|
- Don't: Do not use Menu for complex forms, persistent navigation, or arbitrary rich content.
|
|
19
|
-
- Don't: Do not build a Select out of it. A menu fires actions and has no value; a Select holds one and reports changes. Swapping them loses form semantics and keyboard type-ahead.
|
|
19
|
+
- Don't: Do not build a Select out of it. A menu fires actions and has no value; a Select holds one and reports changes. Swapping them loses form semantics and keyboard type-ahead. → use **Select**
|
|
20
20
|
|
|
21
21
|
### Anatomy
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@ Use Menubar for a tool or editor whose commands are too many for a toolbar and b
|
|
|
16
16
|
- Do: Name the bar with aria-label ("Editor") so it is announced as the application's menus, not a list of buttons.
|
|
17
17
|
- Do: Keep titles to one short word each. A reader scans a menubar left to right; a phrase in the bar is a toolbar button.
|
|
18
18
|
- Don't: Do not use MenubarTrigger outside a Menubar, or a plain MenuTrigger inside one. The bar's arrow keys and hover-to-switch work on the triggers it owns; a Button trigger in the bar looks like a menu title and behaves like a stray button.
|
|
19
|
-
- Don't: Do not use a menubar for site navigation. Its items are commands in menus (role=menuitem), not links between pages.
|
|
19
|
+
- Don't: Do not use a menubar for site navigation. Its items are commands in menus (role=menuitem), not links between pages. → use **NavigationMenu**
|
|
20
20
|
|
|
21
21
|
### Anatomy
|
|
22
22
|
|
package/llms/components/meter.md
CHANGED
|
@@ -15,7 +15,7 @@ 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
|
-
- 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.
|
|
18
|
+
- 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
19
|
- 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.
|
|
20
20
|
|
|
21
21
|
### Anatomy
|
|
@@ -58,5 +58,5 @@ import { MiddleTruncate } from "@sableui/react"
|
|
|
58
58
|
import { Button, Icon, MiddleTruncate, Row, useCopy } from "@sableui/react"
|
|
59
59
|
|
|
60
60
|
const { copy, copied } = useCopy(commitHash)
|
|
61
|
-
<Row gap={1}><MiddleTruncate text={commitHash} /><Button variant="ghost" size="icon-xs" aria-label="Copy commit hash" onClick={copy}><Icon name={copied
|
|
61
|
+
<Row gap={1}><MiddleTruncate text={commitHash} /><Button variant="ghost" size="icon-xs" aria-label="Copy commit hash" onClick={copy}><span aria-hidden="true" className="motion-icon-swap"><span data-active={!copied} className="inline-flex"><Icon name="copy" /></span><span data-active={copied} className="inline-flex"><Icon name="check" /></span></span></Button><span role="status" className="sr-only">{copied ? "Copied" : ""}</span></Row>
|
|
62
62
|
```
|
package/llms/components/note.md
CHANGED
|
@@ -16,7 +16,7 @@ Use Note for helpful supporting information that should sit near the relevant co
|
|
|
16
16
|
- Do: Reach for Note rather than Banner when the message qualifies something on the page — a warning inside a dialog, a caveat above a submit button. Banner is a full-width announcement strip for page- or app-level state; inside a form it reads as a slab, and its soft fill carries no border or tone-colored icon, so the severity stops being visible.
|
|
17
17
|
- Do: Keep tone calm and concise so the note does not compete with primary content.
|
|
18
18
|
- Do: When the reader can resolve the state, pass `action` rather than putting a Button in children. The prop is data — label plus handler — so the callout owns the placement and weight; a Button dropped into children inherits neither, floats in the box's padding, and (in `secondary`) punches a near-background hole in the tone fill.
|
|
19
|
-
- Don't: Do not use Note for state the whole page or app is in (an outage, an offline session, a blocked account) — that is Banner — or for a message tied to one field, which is the field's own error text.
|
|
19
|
+
- Don't: Do not use Note for state the whole page or app is in (an outage, an offline session, a blocked account) — that is Banner — or for a message tied to one field, which is the field's own error text. → use **Banner**
|
|
20
20
|
- Don't: Do not give a Note two actions. One remedy reads as the sentence's next move; two make it a decision point, which is a Dialog's job.
|
|
21
21
|
- Don't: Do not stack several in a row. Three notes together are a wall of asides; if everything is called out, nothing is.
|
|
22
22
|
|
|
@@ -37,6 +37,14 @@ Use Note for helpful supporting information that should sit near the relevant co
|
|
|
37
37
|
|
|
38
38
|
`foreground`, `border`, `muted`, `muted-foreground`, `tone-info`, `tone-warning`, `success`, `destructive`
|
|
39
39
|
|
|
40
|
+
## Motion
|
|
41
|
+
|
|
42
|
+
- **arrival** · Root · _no motion_ — A note is part of the content it annotates and renders with it. A callout that animated in would pull the eye before the reader has reached it in the flow.
|
|
43
|
+
- **response** · Action · `motion-interactive` — The action's focus ring eases in, and its underline thickens on hover. Inside a block of guidance, that is what separates the one thing to do from the prose around it.
|
|
44
|
+
- **response** · Action · `motion-press` — A note's action usually navigates or opens something; the small settle on press is the only confirmation before the next surface paints.
|
|
45
|
+
- **transition** · Root · _no motion_ — A note has no states to move between: its tone is fixed when it renders. A note that changes tone is a new message, and should arrive as one.
|
|
46
|
+
- **activity** · Root · _no motion_ — Static guidance. Work in progress belongs in a Progress or a Spinner, not in a callout.
|
|
47
|
+
|
|
40
48
|
## Accessibility
|
|
41
49
|
|
|
42
50
|
- Rendered with role="note" so assistive tech can announce it as an aside.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
An unread indicator. Renders a bare dot or a count pill (capped as "{max}+"), or nothing when there's nothing to show. overlay pins it to the corner of a relative host; it is aria-hidden, so the host's name carries the count.
|
|
5
|
+
An unread indicator. Renders a bare dot or a count pill (capped as "{max}+"), or nothing when there's nothing to show. A changing count rolls to its new number, and a badge that appears after being empty pops in. overlay pins it to the corner of a relative host; it is aria-hidden, so the host's name carries the count.
|
|
6
6
|
|
|
7
7
|
**Keywords:** notification, badge, count, unread
|
|
8
8
|
|
|
@@ -28,6 +28,7 @@ Use NotificationBadge to show a compact unread count, alert count, or attention
|
|
|
28
28
|
| tone | "destructive" \| "primary" \| "neutral" | "destructive" | Fill color. destructive for alarming counts; primary/neutral for non-alarming ones. |
|
|
29
29
|
| size | "sm" \| "md" \| "lg" | "md" | Scales the dot/pill on the spacing scale (count pill: sm 16px, md 18px, lg 20px at the default density). |
|
|
30
30
|
| overlay | boolean | false | Pin to the top-end corner of the nearest relative host (an icon button) — one placement for every caller instead of hand-tuned absolute offsets. |
|
|
31
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. Under `subtle` the count updates in place and the badge fades in without the pop. |
|
|
31
32
|
|
|
32
33
|
## Variants
|
|
33
34
|
|
|
@@ -39,6 +40,13 @@ Use NotificationBadge to show a compact unread count, alert count, or attention
|
|
|
39
40
|
|
|
40
41
|
`destructive`, `destructive-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`
|
|
41
42
|
|
|
43
|
+
## Motion
|
|
44
|
+
|
|
45
|
+
- **arrival** · root · `motion-appear` — A badge that shows up after being empty is the news — something just arrived — so it fades and scales in where it sits instead of popping into existence. Never on first paint: a badge that mounts with a count is content, and a page of them popping on load is noise.
|
|
46
|
+
- **response** · root · _no motion_ — It is decorative and not interactive; the host it sits on answers the pointer.
|
|
47
|
+
- **transition** · Count · _custom (outside the recipe layer)_ — The count rolls to its new number through NumberTicker (expressive), so 3 → 4 reads as one more arriving rather than a different badge. Past the cap the ticker holds at max with a static "+", so a count climbing beyond 99 does not keep rolling a badge that has not changed.
|
|
48
|
+
- **activity** · root · _no motion_ — An unread count has no working state.
|
|
49
|
+
|
|
42
50
|
## Accessibility
|
|
43
51
|
|
|
44
52
|
- The badge is aria-hidden: a count rendered on a control is not associated with it, so the host needs an accessible name that includes the count — "Notifications, 3 unread" — or a screen-reader user hears "Notifications, button" and never learns there is anything new. Pass aria-hidden={false} only for a standalone badge whose number is the information.
|
|
@@ -46,7 +54,7 @@ Use NotificationBadge to show a compact unread count, alert count, or attention
|
|
|
46
54
|
|
|
47
55
|
## Related
|
|
48
56
|
|
|
49
|
-
- Components: Badge, StatusDot
|
|
57
|
+
- Components: Badge, StatusDot, NumberTicker
|
|
50
58
|
|
|
51
59
|
## Examples
|
|
52
60
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# NumberTicker
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A number that rolls to its new value digit by digit, like an odometer. Formatted with Intl.NumberFormat (currency, percent, compact, any locale); separators, signs and units hold still while each digit column turns, and a digit that appears or goes away eases its width so nothing around it jumps. At rest on first paint; under subtle or reduced motion the value simply updates.
|
|
6
|
+
|
|
7
|
+
**Keywords:** number, counter, odometer, rolling number, animated number, ticker, count up, stat, kpi, metric, price
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use NumberTicker for a figure that changes in place while someone is looking at it — a dashboard total after a filter changes, a live count, a price as a plan's options change, a score. The roll says "this number moved, and by roughly this much"; the reader sees the change happen instead of noticing, later, that a number is different.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for the headline figure that a control on the same screen changes — the total above a chart, the price beside a quantity stepper — so cause and effect are seen together.
|
|
16
|
+
- Do: Format through `format` (currency, percent, compact) rather than pre-formatting a string, so separators and units stay put while only the digits roll.
|
|
17
|
+
- Do: Pass `aria-live="polite"` only when the change itself is news the reader would otherwise miss; most tickers sit beside the control that changed them and need no announcement.
|
|
18
|
+
- Don't: Do not use it for values that change many times a second (a stopwatch, a stock tick, a progress percentage) — the rolls never land and read as a blur. Render the number as plain tabular text.
|
|
19
|
+
- Don't: Do not use it for numbers that are names rather than amounts — an order ID, a phone number, a year, a version. Rolling implies a quantity going up or down.
|
|
20
|
+
- Don't: Do not animate a number on page load to make it feel alive. The first paint is at rest on purpose; for an entrance, reveal the card it sits in (the Reveal motion primitive).
|
|
21
|
+
- Don't: Do not use it for a value inside a known range that needs a picture of how full it is — show a Meter, with the number beside it. → use **Meter**
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
| Name | Type | Default | Description |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| value | number | — | The number to show. When it changes, the digits roll to it. |
|
|
28
|
+
| format | Intl.NumberFormatOptions | — | How to write the number — `{ style: "currency", currency: "USD" }`, `{ style: "percent" }`, `{ notation: "compact" }`, fraction digits. Passed to Intl.NumberFormat; an inline object is fine (it is compared by value). |
|
|
29
|
+
| locales | string \| string[] | runtime locale | BCP 47 locale(s) for the formatting. Digits roll through the locale's own numbering system. |
|
|
30
|
+
| prefix | ReactNode | — | Before the number. A string becomes part of the number's text (and rolls in or out with it); an element is rendered as given. |
|
|
31
|
+
| suffix | ReactNode | — | After the number ("+", " pts"). A string becomes part of the number's text; an element is rendered as given. |
|
|
32
|
+
| trend | "auto" \| "up" \| "down" | "auto" | Which way the digits roll. `auto` rolls up as the value grows and down as it shrinks; `up` / `down` fix it (a countdown, a score that only climbs). |
|
|
33
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. The roll is expressive, so `subtle` updates the value in place. |
|
|
34
|
+
| className | string | — | Merged onto the root span. Set the type tier here (`text-heading-2`, `text-display-lg`); the ticker adds tabular figures so widths hold still. |
|
|
35
|
+
| ...span props | Omit<ComponentPropsWithRef<"span">, "children" \| "prefix"> | — | Remaining span attributes pass through to the root — `aria-live` to announce changes, `id`, data attributes. |
|
|
36
|
+
|
|
37
|
+
## Motion
|
|
38
|
+
|
|
39
|
+
- **arrival** · Root · _no motion_ — The first paint is at rest. A number counting up on page load is decoration that delays the reading; the roll is kept for the moment the value actually changes.
|
|
40
|
+
- **response** · Root · _no motion_ — It is a reading, not a control; the control that changes it gives the feedback.
|
|
41
|
+
- **transition** · Digit · _custom (outside the recipe layer)_ — Each digit column turns to its new place on the expressive roll (the slower token, emphasized easing, staggered from the ones column), and arriving or departing columns ease their width. The roll is what shows a change happened and how big it was — the hundreds turning reads as a bigger move than the ones. No recipe can translate per-digit columns, so it is custom; it is multiplied by --motion-expressive, so `subtle` and reduced motion land the value at once.
|
|
42
|
+
- **activity** · Root · _no motion_ — A number has no working state; show a Skeleton while it loads.
|
|
43
|
+
|
|
44
|
+
## Accessibility
|
|
45
|
+
|
|
46
|
+
- The whole written number (with any string prefix or suffix) is real text in a visually hidden span; the rolling columns are aria-hidden and draw their glyphs as generated content, so screen readers, find-in-page and copy all get "$1,234.50", never a stack of digits.
|
|
47
|
+
- It is not a live region by default — a figure that changes because the reader moved a control is already where their attention is. Pass `aria-live="polite"` when a change elsewhere should be announced.
|
|
48
|
+
- Tabular figures keep every digit the same width, so a roll never shifts the text around it.
|
|
49
|
+
|
|
50
|
+
## Related
|
|
51
|
+
|
|
52
|
+
- Components: Meter, NotificationBadge, PieChart, RadialChart, Sparkline
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
### Basic
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { NumberTicker } from "@sableui/react"
|
|
60
|
+
|
|
61
|
+
<NumberTicker value={1284} className="text-heading-2" />
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Currency
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { NumberTicker } from "@sableui/react"
|
|
68
|
+
|
|
69
|
+
<NumberTicker value={4280.5} format={{ style: "currency", currency: "USD" }} className="text-display-lg" />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Percent, announced
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { NumberTicker } from "@sableui/react"
|
|
76
|
+
|
|
77
|
+
<NumberTicker value={0.428} format={{ style: "percent", maximumFractionDigits: 1 }} aria-live="polite" />
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Subtle
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { NumberTicker } from "@sableui/react"
|
|
84
|
+
|
|
85
|
+
<NumberTicker value={92} suffix=" pts" motion="subtle" />
|
|
86
|
+
```
|
|
@@ -15,6 +15,7 @@ Use OtpField for a short, fixed-length code the user copies from somewhere else
|
|
|
15
15
|
- Do: Set `length` to the exact length the server sends, and verify in `onValueComplete` (or with `autoSubmit` inside a form). Most users paste or autofill the whole code at once, so the moment of completion is the natural submit.
|
|
16
16
|
- Do: Label it through a `Field` ("Verification code") and say where the code was sent in the description. The first slot takes the field's name; the rest are announced by position.
|
|
17
17
|
- Do: Use `groupSize` for codes of 6 or more so the user can check the code in chunks against the message they are reading from.
|
|
18
|
+
- Do: Clear the error as soon as the user edits the code (`onValueChange` → reset the error), and set it again when the next code is refused. Each refusal is then a fresh change to invalid, so the field shakes for every wrong code, not only the first.
|
|
18
19
|
- Don't: Do not use OtpField for a password, an arbitrary-length PIN, or anything the user types from memory. Slots promise a fixed length; use Input with `type="password"` and `autocomplete="current-password"` instead.
|
|
19
20
|
- Don't: Do not intercept paste or clear the field on a wrong code before the user has seen the error. Blocking paste breaks SMS autofill and password managers, and wiping the slots erases the evidence of which digit was wrong.
|
|
20
21
|
- Don't: Do not swap it for NumberField because the code is digits. A code is an identifier, not a quantity: NumberField strips leading zeros and offers steppers that make no sense here.
|
|
@@ -41,7 +42,8 @@ Use OtpField for a short, fixed-length code the user copies from somewhere else
|
|
|
41
42
|
| autoSubmit | boolean | false | Submit the owning form when the code becomes complete. |
|
|
42
43
|
| mask | boolean | false | Render slots as password inputs. Only for codes that are secret on screen, such as a card PIN. |
|
|
43
44
|
| getSlotLabel | (index: number, length: number) => string | Character N of L | Accessible name for slots after the first. Override to localize. |
|
|
44
|
-
| aria-invalid | boolean | — | Marks every slot invalid (destructive border). `<Field error>` sets it for you. |
|
|
45
|
+
| aria-invalid | boolean | — | Marks every slot invalid (destructive border and digits). Turning it on after mount shakes the field once. `<Field error>` sets it for you. |
|
|
46
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. The shake is the flourish; the destructive colour stays at every level. |
|
|
45
47
|
| disabled / readOnly / required / name / id / form | — | — | Standard form-control props. `id` lands on the first slot, so `<Field>` and `<label htmlFor>` name the field. |
|
|
46
48
|
|
|
47
49
|
## Tokens used
|
|
@@ -53,6 +55,7 @@ Use OtpField for a short, fixed-length code the user copies from somewhere else
|
|
|
53
55
|
- **arrival** · Root · _no motion_ — A code field is part of the form it sits in and appears with it. Animating it in would delay the one thing the user arrived to do — paste the code they just copied.
|
|
54
56
|
- **response** · Slot · `motion-interactive` — Focus moves between slots on every keystroke. The eased ring and border are what let the eye follow the cursor across six identical boxes instead of hunting for it after each digit.
|
|
55
57
|
- **transition** · Slot · `motion-interactive` — A filled slot strengthens its border, so a pasted code visibly lands in every slot at once. Easing it reads as the code arriving rather than the field flickering.
|
|
58
|
+
- **transition** · Root · `motion-shake` — A refused code shakes the field once as it turns invalid — the short horizontal no that makes a second wrong code feel noticed rather than ignored. It never carries the refusal alone: the digits and borders turn destructive and the Field says why. It plays on the change to invalid, not on mount, and is expressive, so `subtle` keeps only the colour.
|
|
56
59
|
- **activity** · Root · _no motion_ — Verifying the code is the form's work, not the field's. Show it on the submit Button's `loading` state, where the user expects the answer to appear.
|
|
57
60
|
|
|
58
61
|
## Accessibility
|
|
@@ -61,6 +64,7 @@ Use OtpField for a short, fixed-length code the user copies from somewhere else
|
|
|
61
64
|
- Only one slot is in the tab order. Arrow keys, Home/End and Backspace move between slots; Tab leaves the whole field.
|
|
62
65
|
- The first slot carries autocomplete="one-time-code" and the numeric virtual keyboard, so iOS and Android offer the SMS code above the keyboard.
|
|
63
66
|
- Slots render at 20px (`text-mono-lg`), above the 16px floor that stops iOS Safari zooming on focus.
|
|
67
|
+
- An invalid code is never signalled by motion alone: every slot carries aria-invalid, the digits and borders turn destructive, and the Field's error message is wired through aria-describedby. The shake resolves instantly under reduced motion and `subtle`.
|
|
64
68
|
|
|
65
69
|
## Related
|
|
66
70
|
|
|
@@ -92,6 +96,16 @@ import { Field, OtpField } from "@sableui/react"
|
|
|
92
96
|
<Field label="Verification code" error="That code has expired. Request a new code and try again."><OtpField length={6} groupSize={3} defaultValue="482913" /></Field>
|
|
93
97
|
```
|
|
94
98
|
|
|
99
|
+
### Verify on completion, shake on a wrong code
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { Field, OtpField } from "@sableui/react"
|
|
103
|
+
|
|
104
|
+
const [code, setCode] = useState("")
|
|
105
|
+
const [wrong, setWrong] = useState(false)
|
|
106
|
+
<Field label="Verification code" error={wrong ? "That code does not match. Check the message and try again." : undefined}><OtpField length={6} groupSize={3} value={code} onValueChange={(next) => { setCode(next); setWrong(false) }} onValueComplete={(value) => setWrong(value !== expected)} /></Field>
|
|
107
|
+
```
|
|
108
|
+
|
|
95
109
|
### Alphanumeric
|
|
96
110
|
|
|
97
111
|
```tsx
|
|
@@ -14,7 +14,7 @@ Use PageLoader for full-route or full-page pending states where the entire view
|
|
|
14
14
|
|
|
15
15
|
- Do: Use it during route transitions, auth checks, or initial data loading.
|
|
16
16
|
- Do: Prefer a clear page-level message when the wait may take noticeable time.
|
|
17
|
-
- Don't: Do not use PageLoader for small inline waits; use Spinner, an indeterminate Progress, or Skeleton.
|
|
17
|
+
- Don't: Do not use PageLoader for small inline waits; use Spinner, an indeterminate Progress, or Skeleton. → use **Spinner**, **Progress**, **Skeleton**
|
|
18
18
|
- Don't: Do not leave it mounted if the route's data can fail. It covers the whole viewport, so a PageLoader that never resolves is indistinguishable from a hung application — always give it a failure path.
|
|
19
19
|
|
|
20
20
|
## Props
|
|
@@ -39,6 +39,13 @@ Use PanelGroup for the body of a tool workspace: fixed-width Panels docked left
|
|
|
39
39
|
|
|
40
40
|
`primary`, `panel`, `hairline`, `hairline-strong`
|
|
41
41
|
|
|
42
|
+
## Motion
|
|
43
|
+
|
|
44
|
+
- **arrival** · Root · _no motion_ — Panels are the app's frame, present from first paint. A dock that slid in on load would make the workspace look like it is still assembling.
|
|
45
|
+
- **response** · PanelResizer · `motion-interactive` — The hairline widens into a visible bar on hover and turns primary on keyboard focus, eased, so a 1px edge says it can be grabbed before the pointer commits.
|
|
46
|
+
- **transition** · Panel · _no motion_ — Width tracks the pointer one-to-one while dragging, and keyboard steps land at once. An eased width would trail the cursor, and resizing is a layout change: easing it would reflow the canvas beside the panel on every frame.
|
|
47
|
+
- **activity** · Root · _no motion_ — Layout chrome has no working state.
|
|
48
|
+
|
|
42
49
|
## Accessibility
|
|
43
50
|
|
|
44
51
|
- PanelResizer is a role=separator with aria-orientation, aria-valuenow/min/max, and is focusable — a pointer-only resizer is a keyboard trap.
|
|
@@ -48,6 +48,14 @@ Use PanelSection to group properties inside an inspector or docked panel, with P
|
|
|
48
48
|
|
|
49
49
|
`foreground`, `muted-foreground`, `hairline`
|
|
50
50
|
|
|
51
|
+
## Motion
|
|
52
|
+
|
|
53
|
+
- **arrival** · Root · _no motion_ — Sections are part of the inspector they sit in and arrive with it. Animating each in would make a panel switch look like a page load.
|
|
54
|
+
- **response** · PropertyGroup trigger · `motion-interactive` — The fold row eases its focus ring, so arrowing down a dense inspector shows where the keyboard is without the ring flickering from row to row.
|
|
55
|
+
- **transition** · PropertyGroup panel · `motion-disclosure` — The group opens by easing its height, so the rows below slide down to make room instead of jumping. In an inspector, the next section moving is otherwise how you lose your place.
|
|
56
|
+
- **transition** · PropertyGroup indicator · `motion-icon-swap` — The + collapses into − as the group opens, both glyphs stacked as one mark, so the indicator reads as this row changing state rather than a different icon landing.
|
|
57
|
+
- **activity** · Root · _no motion_ — The inspector edits values in place; nothing in it loads.
|
|
58
|
+
|
|
51
59
|
## Accessibility
|
|
52
60
|
|
|
53
61
|
- PropertyGroup's row is a real button with aria-expanded (Collapsible); its summary is part of the button's name, so a closed group still announces its value.
|
|
@@ -36,7 +36,8 @@ Use PieChart for a share of one total across two to six categories — traffic s
|
|
|
36
36
|
| tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
|
|
37
37
|
| valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
|
|
38
38
|
| locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
|
|
39
|
-
|
|
|
39
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. The sweep, the slice morph and the rolling centre number are the flourish; the hover pull-out and dim are the feedback `subtle` keeps. |
|
|
40
|
+
| animate | boolean | true | Sweep the slices in clockwise when the chart scrolls into view. `animate={false}` is `motion="subtle"`. |
|
|
40
41
|
| activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
|
|
41
42
|
| defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
|
|
42
43
|
| onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
|
|
@@ -47,9 +48,10 @@ Use PieChart for a share of one total across two to six categories — traffic s
|
|
|
47
48
|
|
|
48
49
|
## Motion
|
|
49
50
|
|
|
50
|
-
- **arrival** · Slices · _custom (outside the recipe layer)_ — Once on screen the slices are revealed by a mask that sweeps clockwise from 12 o'clock — the whole is read as it fills. On the motion tokens; reduced motion shows the finished chart.
|
|
51
|
-
- **response** · Active slice · _no motion_ — The hovered slice pulls outward and the rest dim at once; the geometry change itself eases (see transition).
|
|
52
|
-
- **transition** ·
|
|
51
|
+
- **arrival** · Slices · _custom (outside the recipe layer)_ — Once on screen the slices are revealed by a mask that sweeps clockwise from 12 o'clock — the whole is read as it fills. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.
|
|
52
|
+
- **response** · Active slice · _no motion_ — The hovered slice pulls outward and the rest dim at once (mixed toward the surface, not faded); the geometry change itself eases (see transition). Essential: `subtle` keeps it.
|
|
53
|
+
- **transition** · Centre value · _custom (outside the recipe layer)_ — A numeric centre value rolls to its new total digit by digit (NumberTicker) when the data changes, so the headline is seen moving with the slices instead of being found different afterwards. Expressive: under `subtle` it updates in place.
|
|
54
|
+
- **transition** · Slices · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place.
|
|
53
55
|
- **activity** · PieChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
|
|
54
56
|
|
|
55
57
|
## Accessibility
|
package/llms/components/pill.md
CHANGED
|
@@ -14,7 +14,7 @@ Use Pill for a row of filters or choices a person clicks to narrow or toggle wha
|
|
|
14
14
|
|
|
15
15
|
- Do: Use short text that names the filter or choice.
|
|
16
16
|
- Do: Reflect the current choice with selected, so the active filter reads at a glance.
|
|
17
|
-
- Don't: 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.
|
|
17
|
+
- Don't: 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. → use **Badge**
|
|
18
18
|
- Don't: Do not use Pill for primary actions or important system status.
|
|
19
19
|
- Don't: 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.
|
|
20
20
|
|
|
@@ -39,6 +39,14 @@ Use Pill for a row of filters or choices a person clicks to narrow or toggle wha
|
|
|
39
39
|
|
|
40
40
|
`palette-wash`, `palette-wash-strong`, `palette-edge`, `palette-solid`, `palette-text`, `palette-solid-ink`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`, `primary`, `primary-foreground`, `card`, `border`, `muted-foreground`, `foreground`
|
|
41
41
|
|
|
42
|
+
## Motion
|
|
43
|
+
|
|
44
|
+
- **arrival** · Root · _no motion_ — Pills arrive with the filter row or the content they annotate. A chip that animated in alone would read as a filter someone just added.
|
|
45
|
+
- **response** · Root · `motion-interactive` — The hover wash eases, so sweeping the pointer along a filter row reads as one cursor moving rather than chips flashing in turn.
|
|
46
|
+
- **response** · Root · `motion-press` — A pill often toggles a filter that takes a moment to reach the results; the settle on press confirms the click landed before they change.
|
|
47
|
+
- **transition** · Root · `motion-interactive` — `selected` lifts the chip onto its selected surface on the same ease, so toggling reads as this chip changing state, not a different chip appearing in its place.
|
|
48
|
+
- **activity** · Root · _no motion_ — A chip is a choice, not a process. If applying it is slow, the results show it.
|
|
49
|
+
|
|
42
50
|
## Accessibility
|
|
43
51
|
|
|
44
52
|
- Renders a native <button type="button"> for focus and keyboard activation.
|
|
@@ -14,8 +14,8 @@ Use Popover for anchored secondary content that needs more room than a tooltip b
|
|
|
14
14
|
|
|
15
15
|
- Do: Use it for small pickers, summaries, or contextual controls tied to a trigger.
|
|
16
16
|
- Do: Keep focus behavior and dismissal predictable.
|
|
17
|
-
- Don't: Do not use Popover for blocking confirmations, long forms, or critical information.
|
|
18
|
-
- Don't: Do not put a form that can fail validation inside one. A popover dismisses on outside click, which discards what the user typed with no warning — that is a Dialog.
|
|
17
|
+
- Don't: Do not use Popover for blocking confirmations, long forms, or critical information. → use **Dialog**
|
|
18
|
+
- Don't: Do not put a form that can fail validation inside one. A popover dismisses on outside click, which discards what the user typed with no warning — that is a Dialog. → use **Dialog**
|
|
19
19
|
|
|
20
20
|
### Anatomy
|
|
21
21
|
|
|
@@ -15,7 +15,7 @@ Use Progress to communicate determinate or indeterminate completion for a task o
|
|
|
15
15
|
- Do: Use determinate values when progress can be measured.
|
|
16
16
|
- Do: Pair the visual indicator with text when users need context or exact values.
|
|
17
17
|
- Don't: Do not use Progress as a decorative divider or status color bar.
|
|
18
|
-
- Don't: Do not use a determinate Progress for an unknown duration. A bar that sits at 90% is worse than a Spinner, because it makes a promise about time that you cannot keep.
|
|
18
|
+
- Don't: Do not use a determinate Progress for an unknown duration. A bar that sits at 90% is worse than a Spinner, because it makes a promise about time that you cannot keep. → use **Spinner**
|
|
19
19
|
|
|
20
20
|
### Anatomy
|
|
21
21
|
|
|
@@ -35,7 +35,8 @@ Use RadarChart to compare the shape of a profile across five to eight dimensions
|
|
|
35
35
|
| tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
|
|
36
36
|
| valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
|
|
37
37
|
| locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
|
|
38
|
-
|
|
|
38
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. The polygons growing in and morphing are the flourish; the hover highlight is the feedback `subtle` keeps. |
|
|
39
|
+
| animate | boolean | true | Grow the polygons from the centre when the chart scrolls into view. `animate={false}` is `motion="subtle"`. |
|
|
39
40
|
| activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
|
|
40
41
|
| defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
|
|
41
42
|
| onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
|
|
@@ -46,9 +47,9 @@ Use RadarChart to compare the shape of a profile across five to eight dimensions
|
|
|
46
47
|
|
|
47
48
|
## Motion
|
|
48
49
|
|
|
49
|
-
- **arrival** · Polygon · _custom (outside the recipe layer)_ — Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart.
|
|
50
|
+
- **arrival** · Polygon · _custom (outside the recipe layer)_ — Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.
|
|
50
51
|
- **response** · Active spoke · _no motion_ — The hovered spoke's vertices and label highlight at once.
|
|
51
|
-
- **transition** · Polygon · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
|
|
52
|
+
- **transition** · Polygon · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place.
|
|
52
53
|
- **activity** · RadarChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
|
|
53
54
|
|
|
54
55
|
## Accessibility
|
|
@@ -37,7 +37,8 @@ Use RadialChart for progress toward goals (several rings), a single score or usa
|
|
|
37
37
|
| tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
|
|
38
38
|
| valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
|
|
39
39
|
| locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
|
|
40
|
-
|
|
|
40
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. The draw-on, the ring morph and the rolling centre number are the flourish; the hover dim is the feedback `subtle` keeps. |
|
|
41
|
+
| animate | boolean | true | Draw the rings round from their start when the chart scrolls into view. `animate={false}` is `motion="subtle"`. |
|
|
41
42
|
| activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
|
|
42
43
|
| defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
|
|
43
44
|
| onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
|
|
@@ -48,9 +49,10 @@ Use RadialChart for progress toward goals (several rings), a single score or usa
|
|
|
48
49
|
|
|
49
50
|
## Motion
|
|
50
51
|
|
|
51
|
-
- **arrival** · Ring · _custom (outside the recipe layer)_ — Once on screen each ring draws round from its start (stroke-dashoffset on a round-capped arc), staggered outside in. On the motion tokens; reduced motion shows the finished chart.
|
|
52
|
-
- **response** · Active ring · _no motion_ — The hovered ring stays full strength and the others dim at once.
|
|
53
|
-
- **transition** ·
|
|
52
|
+
- **arrival** · Ring · _custom (outside the recipe layer)_ — Once on screen each ring draws round from its start (stroke-dashoffset on a round-capped arc), staggered outside in. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.
|
|
53
|
+
- **response** · Active ring · _no motion_ — The hovered ring stays full strength and the others dim at once — mixed toward the surface rather than faded, so a dimmed ring stays solid over its track instead of turning muddy. Essential: `subtle` keeps it.
|
|
54
|
+
- **transition** · Centre value · _custom (outside the recipe layer)_ — A numeric centre value rolls to its new total digit by digit (NumberTicker) when the data changes, so the headline is seen moving with the rings instead of being found different afterwards. Expressive: under `subtle` it updates in place.
|
|
55
|
+
- **transition** · Ring · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place.
|
|
54
56
|
- **activity** · RadialChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
|
|
55
57
|
|
|
56
58
|
## Accessibility
|
|
@@ -52,6 +52,15 @@ Use RadioCard for visual choice cards when users compare a small set of named op
|
|
|
52
52
|
|
|
53
53
|
`input`, `card`, `foreground`, `primary`, `muted`, `ring`, `muted-foreground`, `tone-3`, `tone-2`, `tone-1`, `tone-info`, `tone-warning`
|
|
54
54
|
|
|
55
|
+
## Motion
|
|
56
|
+
|
|
57
|
+
- **arrival** · Root · _no motion_ — Cards arrive with the form. A plan picker that animated each card in would delay the choice it exists to make.
|
|
58
|
+
- **response** · Root · `motion-interactive` — The edge steps toward the ring colour on hover, eased. On a card-sized target a hard snap across the whole border reads as the layout flickering.
|
|
59
|
+
- **response** · Root · `motion-press` — The settle on press confirms a click anywhere in the card — the radio is small, and without it a click in the card's body can feel like it missed the control.
|
|
60
|
+
- **transition** · Root · `motion-interactive` — Checking eases the tone ring and wash onto the new card as the old one releases, so the choice reads as moving between cards rather than two cards repainting.
|
|
61
|
+
- **transition** · Indicator · `motion-interactive` — The radio dot grows in on a spring, the same mark as Radio, so a card and a plain radio confirm a choice the same way.
|
|
62
|
+
- **activity** · Root · _no motion_ — A choice commits instantly. A slow write belongs to the surrounding form's pending state.
|
|
63
|
+
|
|
55
64
|
## Accessibility
|
|
56
65
|
|
|
57
66
|
- Base UI provides the radiogroup/radio roles, aria-checked, hidden native inputs, and roving tabindex.
|
package/llms/components/radio.md
CHANGED
|
@@ -14,7 +14,7 @@ Use Radio when a user must choose exactly one option from a visible set.
|
|
|
14
14
|
|
|
15
15
|
- Do: Use it when all options should be visible for comparison.
|
|
16
16
|
- Do: Keep option labels mutually exclusive and easy to understand.
|
|
17
|
-
- Don't: Do not use Radio for independent toggles or multiple selection; use Checkbox or Switch.
|
|
17
|
+
- Don't: Do not use Radio for independent toggles or multiple selection; use Checkbox or Switch. → use **Checkbox**, **Switch**
|
|
18
18
|
- Don't: Do not use a radio group with only one option, and do not omit the Fieldset. Without the fieldset the options share no accessible name, so a screen reader reads the choices with no question attached.
|
|
19
19
|
|
|
20
20
|
### Anatomy
|
|
@@ -40,6 +40,13 @@ Use Radio when a user must choose exactly one option from a visible set.
|
|
|
40
40
|
|
|
41
41
|
`input`, `card`, `primary`, `ring`
|
|
42
42
|
|
|
43
|
+
## Motion
|
|
44
|
+
|
|
45
|
+
- **arrival** · Root · _no motion_ — Radios arrive with the form they belong to. Animating each one in turns a question into a title sequence.
|
|
46
|
+
- **response** · Root · `motion-interactive` — The ring's edge and focus ring ease, so arrowing through a group — which moves the selection with focus — reads as one choice moving down the list.
|
|
47
|
+
- **transition** · Indicator · `motion-interactive` — The dot grows from the centre on a spring and shrinks out of the radio being left. A group's selection moves from one option to another, and the paired grow and shrink is what makes it read as moving rather than two dots blinking.
|
|
48
|
+
- **activity** · Root · _no motion_ — A radio commits instantly. A slow write belongs to the surrounding form's pending state, not the control.
|
|
49
|
+
|
|
43
50
|
## Accessibility
|
|
44
51
|
|
|
45
52
|
- Base UI provides the radiogroup/radio roles, aria-checked, hidden native inputs, and roving tabindex.
|
|
@@ -29,6 +29,14 @@ Use RelativeTime to show recent activity or timestamps in conversational relativ
|
|
|
29
29
|
|
|
30
30
|
`popover`, `popover-foreground`, `border`, `ring`
|
|
31
31
|
|
|
32
|
+
## Motion
|
|
33
|
+
|
|
34
|
+
- **arrival** · Root · _no motion_ — It is a word in a line of text and arrives with that line.
|
|
35
|
+
- **response** · Root · _no motion_ — The only hover change on the timestamp is a dotted underline, and text-decoration-line is discrete: CSS cannot tween it, so a transition here would animate nothing and imply the snap had been handled.
|
|
36
|
+
- **response** · Tooltip · `motion-pop` — The pointer's real answer is the absolute time, which arrives in a Tooltip on its own fast pop. That short entrance is what makes the full date read as belonging to the word under the cursor.
|
|
37
|
+
- **transition** · Root · _no motion_ — The label does not tick: it is computed when the component renders, so it never changes in front of the reader. A timestamp rolling from one minute to two would pull the eye off the content it dates.
|
|
38
|
+
- **activity** · Root · _no motion_ — A timestamp has nothing to wait for.
|
|
39
|
+
|
|
32
40
|
## Accessibility
|
|
33
41
|
|
|
34
42
|
- Renders a semantic <time> element with a machine-readable dateTime (ISO) attribute.
|
|
@@ -41,6 +41,14 @@ Use Scroller to create a contained overflow region for content that naturally ex
|
|
|
41
41
|
|
|
42
42
|
`ring`
|
|
43
43
|
|
|
44
|
+
## Motion
|
|
45
|
+
|
|
46
|
+
- **arrival** · Root · _no motion_ — It is a region, not an event; its content arrives with whatever fills it.
|
|
47
|
+
- **response** · Viewport · `motion-interactive` — The region is focusable so the keyboard can scroll it, and its focus ring eases in like any control's. It is the one sign the region, not the page, now takes the arrow keys.
|
|
48
|
+
- **response** · Scrollbar · _custom (outside the recipe layer)_ — The vertical bar (`scrollbar-quiet`) stays hidden until the region is hovered, focused or scrolled, then fades its thumb in on the motion tokens. A bar that appeared in one frame would flash every time the pointer crossed the list.
|
|
49
|
+
- **transition** · Edge fade · _no motion_ — The edge fades switch on and off as you scroll, in the frame the content is already moving, so the change is carried by the scroll itself. They are a mask gradient, which CSS cannot interpolate without registered custom properties.
|
|
50
|
+
- **activity** · Root · _no motion_ — A scroll region does no work of its own. Content still loading inside it brings its own Skeleton or Spinner.
|
|
51
|
+
|
|
44
52
|
## Accessibility
|
|
45
53
|
|
|
46
54
|
- The edge fades are a CSS mask-image on the scrolling element, not overlay elements — there is nothing in the DOM to intercept a pointer or be read by assistive tech, and the content under a fade stays fully interactive.
|
|
@@ -42,6 +42,14 @@ Use ScrubField for numeric properties in tool chrome — an inspector, a propert
|
|
|
42
42
|
|
|
43
43
|
`foreground`, `muted-foreground`, `ring`, `panel-field`, `panel-field-hover`
|
|
44
44
|
|
|
45
|
+
## Motion
|
|
46
|
+
|
|
47
|
+
- **arrival** · Root · _no motion_ — It arrives with the inspector row it sits in.
|
|
48
|
+
- **response** · Root · `motion-interactive` — The shell eases its focus ring like every field, so tabbing through a column of numbers shows where the keyboard is without flicker.
|
|
49
|
+
- **response** · Handle · `motion-interactive` — The handle's ink brightens while the field is hovered. It is the only sign that the icon or prefix can be dragged, which nobody would guess from a label.
|
|
50
|
+
- **transition** · Value · _no motion_ — The number follows the drag pixel for pixel. Easing it — or rolling it with a NumberTicker — would show a value the pointer has already left, in a control whose whole point is precision.
|
|
51
|
+
- **activity** · Root · _no motion_ — Scrubbing is synchronous; there is nothing to wait for.
|
|
52
|
+
|
|
45
53
|
## Accessibility
|
|
46
54
|
|
|
47
55
|
- The input carries the accessible name via `label`; the glyph is aria-hidden because it is decorative.
|
|
@@ -36,6 +36,13 @@ Use SearchInput for text queries that filter or search a known content set.
|
|
|
36
36
|
|
|
37
37
|
`input`, `card`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
38
38
|
|
|
39
|
+
## Motion
|
|
40
|
+
|
|
41
|
+
- **arrival** · Clear button · `motion-appear` — The X mounts as the first character lands. Fading and settling in from a smaller scale keeps it from popping into the corner of the eye on every new search; removal is instant so an emptied field does not wait on it.
|
|
42
|
+
- **response** · Input · `motion-interactive` — The focus ring and invalid border ease in, so focus reads as the field answering rather than a repaint.
|
|
43
|
+
- **transition** · Root · _no motion_ — The field has no states of its own to move between — the clear button arriving is its only change, and arrival covers it.
|
|
44
|
+
- **activity** · Root · _no motion_ — Searching is the results' job: a list skeleton or a Spinner beside them, not a field that animates while you type.
|
|
45
|
+
|
|
39
46
|
## Accessibility
|
|
40
47
|
|
|
41
48
|
- Renders a native <input type="search"> — keyboard and form semantics come for free.
|
|
@@ -14,7 +14,7 @@ Use SegmentedBar when a stat card or row needs to show how a total divides into
|
|
|
14
14
|
|
|
15
15
|
- Do: Keep it to two to six segments and give the same category the same tone everywhere it appears.
|
|
16
16
|
- Do: Show the legend's shares in text ("42%") — colour identifies a segment, but the number is what a reader compares.
|
|
17
|
-
- Don't: Use it for a single value against a limit; that is Meter, which carries the meter role and its value.
|
|
17
|
+
- Don't: Use it for a single value against a limit; that is Meter, which carries the meter role and its value. → use **Meter**
|
|
18
18
|
- Don't: Rely on tone order alone to tell segments apart; a legend row per segment, with its label, is required.
|
|
19
19
|
|
|
20
20
|
### Anatomy
|
|
@@ -17,8 +17,8 @@ Use Select when users choose one value from a known, finite set. Use Combobox fo
|
|
|
17
17
|
- Do: Wrap in Field when the select belongs to a form so label, helper text, and errors are wired automatically.
|
|
18
18
|
- Do: Leave the trigger's type alone on mobile. Below the sm breakpoint the md trigger renders text-body-lg (17px) so it matches the Input and Textarea beside it, which carry that tier because iOS Safari zooms the page when a focused field computes under 16px. A custom field in the same row should follow: a second paired tier under a max-sm: variant, never a hand-set font-size.
|
|
19
19
|
- Do: Inside a <Workspace> a Select adapts automatically — trigger and popup both pick up the compact density, including across the portal. Do NOT hand-size it per call site; that was necessary before v0.41 and is now drift.
|
|
20
|
-
- Don't: Use Select for very large or fuzzy lists. Use Combobox when typing is the primary way to find an option.
|
|
21
|
-
- Don't: Use Select for binary on/off settings. Use Switch or Checkbox instead.
|
|
20
|
+
- Don't: Use Select for very large or fuzzy lists. Use Combobox when typing is the primary way to find an option. → use **Combobox**
|
|
21
|
+
- Don't: Use Select for binary on/off settings. Use Switch or Checkbox instead. → use **Switch**, **Checkbox**
|
|
22
22
|
|
|
23
23
|
### Anatomy
|
|
24
24
|
|
package/llms/components/sheet.md
CHANGED
|
@@ -15,8 +15,8 @@ Use Sheet for secondary workflows that slide in while preserving context from th
|
|
|
15
15
|
- Do: Use it for inspect, edit, filter, or create flows that relate to the current page.
|
|
16
16
|
- Do: Include clear title, close path, and footer actions for task-oriented sheets.
|
|
17
17
|
- Do: Give a bottom sheet snap points when it shows a summary first and detail on demand (a map result, a now-playing panel): `snapPoints={[0.4, 1]}`. The first stop is where it opens; put what matters at that height, because the rest is a drag away.
|
|
18
|
-
- Don't: Do not use Sheet for simple confirmations or deeply nested workflows.
|
|
19
|
-
- Don't: Do not use it on desktop where a Dialog fits. A sheet's affordance is edge-anchored travel for thumb reach; on a wide viewport it becomes a dialog that arrives from the wrong place.
|
|
18
|
+
- Don't: Do not use Sheet for simple confirmations or deeply nested workflows. → use **Dialog**
|
|
19
|
+
- Don't: Do not use it on desktop where a Dialog fits. A sheet's affordance is edge-anchored travel for thumb reach; on a wide viewport it becomes a dialog that arrives from the wrong place. → use **Dialog**
|
|
20
20
|
|
|
21
21
|
### Anatomy
|
|
22
22
|
|
|
@@ -30,6 +30,15 @@ Use ShowMore to reveal additional content after an intentionally shortened previ
|
|
|
30
30
|
|
|
31
31
|
`background`, `foreground`, `ring`
|
|
32
32
|
|
|
33
|
+
## Motion
|
|
34
|
+
|
|
35
|
+
- **arrival** · Root · _no motion_ — The preview arrives with the content it clips. Its fade and toggle appear only once a measurement proves the content overflows.
|
|
36
|
+
- **response** · Toggle · `motion-interactive` — The toggle is a link Button, so its hover and focus ease on Button's recipe.
|
|
37
|
+
- **transition** · Content · _custom (outside the recipe layer)_ — The region eases its max-height between the collapsed and the measured full height on the slow motion token. motion-disclosure keys on Base UI's starting and ending styles, which this self-contained toggle does not have. The text unrolling in place keeps the reader's line; a snap drops them at a different scroll position.
|
|
38
|
+
- **transition** · Fade · `motion-interactive` — The bottom fade dissolves as the region opens and returns as it closes, so the cut edge and the reveal read as one movement.
|
|
39
|
+
- **transition** · Chevron · `motion-interactive` — The chevron turns 180° in step with the region (motion-interactive lists `rotate`), so the toggle visibly points at what it will do next.
|
|
40
|
+
- **activity** · Root · _no motion_ — Expanding is local and instant; nothing loads.
|
|
41
|
+
|
|
33
42
|
## Accessibility
|
|
34
43
|
|
|
35
44
|
- The toggle is a real <button> with aria-expanded reflecting the open state.
|