@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
|
@@ -16,7 +16,7 @@ Use SideNav for vertical navigation inside a settings page, documentation area,
|
|
|
16
16
|
- Do: Group related items with SideNavGroup when the rail has more than one conceptual area.
|
|
17
17
|
- Do: Use size='lg' and activeVariant='sidebar' when the nav is part of persistent app chrome.
|
|
18
18
|
- Do: Rows are rounded-sm, which nests in the usual rounded-lg p-2 frame. In any other rounded, padded frame give each item rounded-concentric-<outer>/<inset> — a same-radius row inset into a frame reads as a second system.
|
|
19
|
-
- Don't: Use SideNav for bottom mobile navigation; use BottomNav in AppShell's bottomNav slot.
|
|
19
|
+
- Don't: Use SideNav for bottom mobile navigation; use BottomNav in AppShell's bottomNav slot. → use **BottomNav**
|
|
20
20
|
- Don't: Mix content and sidebar active variants in the same rail unless individual items intentionally override the group.
|
|
21
21
|
|
|
22
22
|
### Anatomy
|
|
@@ -41,11 +41,19 @@ Use SideNav for vertical navigation inside a settings page, documentation area,
|
|
|
41
41
|
| --- | --- | --- | --- |
|
|
42
42
|
| size | "md" \| "lg" | — | Density shared by all items. "md" (default) = the lightweight in-content sub-nav; "lg" = chrome-weight (larger label + icon, roomier rows) for a rail that pairs with the app Sidebar. |
|
|
43
43
|
| activeVariant | "content" \| "sidebar" | "content" | Active-state token vocabulary shared by all items. "content" uses bg-accent/text-accent-foreground for local rails inside the content area; "sidebar" uses bg-sidebar-accent/text-sidebar-accent-foreground for SideNavs rendered inside app chrome. |
|
|
44
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the current-page glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
44
45
|
|
|
45
46
|
## Tokens used
|
|
46
47
|
|
|
47
48
|
`muted`, `muted-foreground`, `foreground`, `accent`, `accent-foreground`, `ring`, `sidebar-foreground`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-ring`
|
|
48
49
|
|
|
50
|
+
## Motion
|
|
51
|
+
|
|
52
|
+
- **arrival** · Root · _no motion_ — In-page navigation arrives with the page it navigates.
|
|
53
|
+
- **response** · SideNavItem · `motion-interactive` — Rows ease their hover fill and focus ring, so moving down a long nav reads as one pointer travelling rather than rows flashing in turn.
|
|
54
|
+
- **transition** · SideNav · `motion-glide` — The current-item fill is one element that travels to the new row when the section changes inside a persistent layout, so the marker reads as moving rather than one row dimming while another lights. It lands in place on first paint, each row keeps its own fill as the server-render fallback, and the active row's heavier weight lands at once — font weight does not ease. Expressive: `subtle` moves it at once.
|
|
55
|
+
- **activity** · Root · _no motion_ — Navigation chrome has no working state; a loading section shows it in the content.
|
|
56
|
+
|
|
49
57
|
## Accessibility
|
|
50
58
|
|
|
51
59
|
- SideNav renders a <nav> landmark; pair multiple navs with aria-label to distinguish them.
|
|
@@ -41,6 +41,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
41
41
|
| 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
42
|
| 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
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
|
+
| 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. |
|
|
44
45
|
|
|
45
46
|
## Variants
|
|
46
47
|
|
|
@@ -50,6 +51,14 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
50
51
|
|
|
51
52
|
`sidebar`, `sidebar-foreground`, `sidebar-border`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-ring`
|
|
52
53
|
|
|
54
|
+
## Motion
|
|
55
|
+
|
|
56
|
+
- **arrival** · Root · _no motion_ — The rail is the app's frame, present from first paint. It is not summoned, so it has no entrance.
|
|
57
|
+
- **response** · SidebarItem · `motion-interactive` — Rows ease their hover fill (SideNav's row recipe on the rail rung), so moving down the nav reads as the pointer travelling rather than rows flashing.
|
|
58
|
+
- **transition** · SidebarNav · `motion-glide` — The current-page fill is one element that travels to the new item when the page changes inside a persistent layout, so the marker reads as moving rather than one row dimming while another lights. It lands in place on first paint, and each item keeps its own fill as the server-render fallback. Expressive: `subtle` moves it at once.
|
|
59
|
+
- **transition** · Root · `motion-size` — Collapsing to the icon rail eases the width instead of jumping 192px in one frame, so the content beside it visibly takes the room the rail gave up. Nothing inside jumps — a collapsed row's centred icon and an expanded row's inset icon sit on the same column — and labels leave at once and fade back in on reopen, clipped to the rail meanwhile. It reflows the page for the length of the ease, which is acceptable for a deliberate user toggle and never happens on load. Expressive: `subtle` snaps.
|
|
60
|
+
- **activity** · Root · _no motion_ — Navigation chrome has no working state; a loading route shows it in the page.
|
|
61
|
+
|
|
53
62
|
## Accessibility
|
|
54
63
|
|
|
55
64
|
- SidebarNav renders a <nav> landmark.
|
|
@@ -14,7 +14,7 @@ Use Skeleton to reserve space and hint at the shape of content while data is loa
|
|
|
14
14
|
|
|
15
15
|
- Do: Match the approximate size and rhythm of the eventual content.
|
|
16
16
|
- Do: Use it for content regions where layout stability matters during loading.
|
|
17
|
-
- Don't: Do not leave Skeleton visible after an error or empty state; switch to EmptyState (tone='destructive' for an error).
|
|
17
|
+
- Don't: Do not leave Skeleton visible after an error or empty state; switch to EmptyState (tone='destructive' for an error). → use **EmptyState**
|
|
18
18
|
- Don't: Do not use a skeleton whose shape differs from the content that replaces it. A mismatched placeholder causes a visible jump, which is worse than a brief spinner.
|
|
19
19
|
|
|
20
20
|
## Props
|
|
@@ -60,6 +60,13 @@ Use Slider for bounded numeric choices where approximate adjustment is faster th
|
|
|
60
60
|
|
|
61
61
|
`secondary`, `primary`, `background`, `ring`
|
|
62
62
|
|
|
63
|
+
## Motion
|
|
64
|
+
|
|
65
|
+
- **arrival** · Root · _no motion_ — It arrives with the form or panel it sits in.
|
|
66
|
+
- **response** · Thumb · `motion-interactive` — The thumb's dot grows from 12px to full size on hover and keyboard focus, and a little past it while dragging, on a spring. Only the paint resizes, never the hit target, so the growth says the thumb is live without moving where you have to aim.
|
|
67
|
+
- **transition** · Thumb · _no motion_ — The thumb and the filled range follow the pointer one-to-one, and keyboard steps land at once. An eased position would trail the drag and show a value the user has already moved past.
|
|
68
|
+
- **activity** · Root · _no motion_ — A slider commits as it moves; there is nothing to wait for.
|
|
69
|
+
|
|
63
70
|
## Accessibility
|
|
64
71
|
|
|
65
72
|
- Base UI provides role=slider, aria-valuemin/max/now, and arrow-key / Page Up-Down / Home-End control.
|
|
@@ -26,7 +26,8 @@ Use Sparkline next to a number to show where it has been — a KPI's last 30 day
|
|
|
26
26
|
| variant | "line" \| "area" \| "bar" | "line" | A line, a line over a soft fill, or columns. |
|
|
27
27
|
| tone | "1" \| "2" \| "3" \| "info" \| "warning" \| "primary" \| "neutral" \| "success" \| "destructive" | "primary" | Colour by role. |
|
|
28
28
|
| className | string | "h-8 w-24" | Sizes it; the drawing stretches to fit. |
|
|
29
|
-
|
|
|
29
|
+
| 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. Drawing on (or rising) is the flourish, so `subtle` and `off` show the finished line. |
|
|
30
|
+
| animate | boolean | true | Draw on (or rise) when it scrolls into view. `animate={false}` is `motion="subtle"`. |
|
|
30
31
|
| locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
|
|
31
32
|
|
|
32
33
|
## Variants
|
|
@@ -39,7 +40,7 @@ Use Sparkline next to a number to show where it has been — a KPI's last 30 day
|
|
|
39
40
|
|
|
40
41
|
## Motion
|
|
41
42
|
|
|
42
|
-
- **arrival** · Line / bars · _custom (outside the recipe layer)_ — Once on screen the line draws on (or the bars rise, in sequence). On the motion tokens; reduced motion shows the finished drawing.
|
|
43
|
+
- **arrival** · Line / bars · _custom (outside the recipe layer)_ — Once on screen the line draws on (or the bars rise, in sequence). On the motion tokens; reduced motion shows the finished drawing. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished line.
|
|
43
44
|
- **response** · Sparkline · _no motion_ — Not interactive: it has no tooltip or focus.
|
|
44
45
|
- **transition** · Sparkline · _no motion_ — New data redraws in place; a word-sized chart animating on every update would distract from the number beside it.
|
|
45
46
|
- **activity** · Sparkline · _no motion_ — No indeterminate state.
|
|
@@ -14,7 +14,7 @@ Use Spinner for short indeterminate waits where the user cannot know exact progr
|
|
|
14
14
|
|
|
15
15
|
- Do: Use it in compact panels or places where layout is already stable. In a button, use Button's loading prop rather than placing a Spinner by hand — it disables the button, sets aria-busy and sizes the spinner to the button's icon.
|
|
16
16
|
- Do: Pair it with accessible loading text when the surrounding label is not enough.
|
|
17
|
-
- Don't: Do not use Spinner as the only content for long waits or full page loading.
|
|
17
|
+
- Don't: Do not use Spinner as the only content for long waits or full page loading. → use **Skeleton**, **PageLoader**
|
|
18
18
|
- Don't: Do not use it without an accessible name or a nearby status message. A spinning icon is invisible to a screen reader, so the wait is announced as nothing at all.
|
|
19
19
|
|
|
20
20
|
## Props
|
|
@@ -15,8 +15,8 @@ Use Stepper above or beside a flow that is split into a known, small number of n
|
|
|
15
15
|
- Do: Drive it from one number (`current`) and override a single step's `status` only for what a number cannot say — a completed step that now needs attention is `status="error"`.
|
|
16
16
|
- Do: Name steps with a word or two ("Shipping", "Payment"). Go vertical when steps need a description or there are more than five; horizontal titles truncate first.
|
|
17
17
|
- Do: Give completed steps `onSelect` only when going back to edit them actually works, and keep the user's later answers when they do.
|
|
18
|
-
- Don't: Do not use Stepper for a task with unknown length or for a percentage. That is Progress; a stepper promises a fixed set of named stops.
|
|
19
|
-
- Don't: Do not use it as navigation between independent sections. Sections the user can visit in any order are Tabs; a stepper implies each step depends on the one before.
|
|
18
|
+
- Don't: Do not use Stepper for a task with unknown length or for a percentage. That is Progress; a stepper promises a fixed set of named stops. → use **Progress**
|
|
19
|
+
- Don't: Do not use it as navigation between independent sections. Sections the user can visit in any order are Tabs; a stepper implies each step depends on the one before. → use **Tabs**
|
|
20
20
|
- Don't: Do not make upcoming steps pressable to skip ahead. A step that looks reachable but is not is worse than one that is plainly locked.
|
|
21
21
|
|
|
22
22
|
### Anatomy
|
|
@@ -15,7 +15,7 @@ Use Switch for immediate on/off settings that take effect as a persistent prefer
|
|
|
15
15
|
- Do: Use it for binary settings where the label describes the enabled state.
|
|
16
16
|
- Do: Make the current state obvious through both position and accessible text.
|
|
17
17
|
- Don't: Do not phrase the label as a negative, such as "Don't send read receipts". Turning off a negative is a double negative, and the user has to solve it before they can act.
|
|
18
|
-
- Don't: Do not use Switch for one-time agreement, multi-select choices, or submit-only booleans.
|
|
18
|
+
- Don't: Do not use Switch for one-time agreement, multi-select choices, or submit-only booleans. → use **Checkbox**, **Radio**
|
|
19
19
|
- Don't: Do not use a Switch whose effect is not immediately visible somewhere. A toggle flipped with no observable consequence gives no confirmation it worked, so the user flips it again to check.
|
|
20
20
|
|
|
21
21
|
## Props
|
package/llms/components/table.md
CHANGED
|
@@ -16,7 +16,7 @@ Use Table for structured data with consistent columns and rows. Use DataTable wh
|
|
|
16
16
|
- Do: Keep cells concise and scannable; compose Badge, StatusDot, Avatar, or Button only when they clarify the row.
|
|
17
17
|
- Do: Use the built-in horizontal frame for overflow instead of squeezing columns until content becomes unreadable.
|
|
18
18
|
- Do: Put a table in a card with `<CardContent flush>`. The first and last columns then take the card's own padding (`--table-edge-px`), so they line up with the CardHeader at any density. Never hand-set `pl-6`/`pr-6` on edge cells.
|
|
19
|
-
- Don't: Use Table for heterogeneous items with no stable columns. Use Card, Entity, or a list composition instead.
|
|
19
|
+
- Don't: Use Table for heterogeneous items with no stable columns. Use Card, Entity, or a list composition instead. → use **Card**, **Entity**
|
|
20
20
|
- Don't: Put every possible action in every row. Use one primary row action, an OverflowMenu, or row detail navigation.
|
|
21
21
|
|
|
22
22
|
### Anatomy
|
|
@@ -50,6 +50,13 @@ Use Table for structured data with consistent columns and rows. Use DataTable wh
|
|
|
50
50
|
|
|
51
51
|
`border`, `muted`, `muted-foreground`
|
|
52
52
|
|
|
53
|
+
## Motion
|
|
54
|
+
|
|
55
|
+
- **arrival** · Root · _no motion_ — A table renders with the page it sits in. Staggering rows in delays reading data someone opened to scan.
|
|
56
|
+
- **response** · TableRow · `motion-interactive` — Body rows ease to a muted wash under the pointer. Across a wide row, the wash is what keeps the eye on the right line, and eased it follows the cursor down the table rather than strobing.
|
|
57
|
+
- **transition** · TableCell · `motion-interactive` — A `data-selected` row's fill eases in on every cell together, so the selected row reads as one band being set rather than a stamp.
|
|
58
|
+
- **activity** · Root · _no motion_ — Table is markup. Loading rows are Skeletons, which carry the shimmer.
|
|
59
|
+
|
|
53
60
|
## Accessibility
|
|
54
61
|
|
|
55
62
|
- Renders semantic native table elements (thead/tbody/tfoot/th/td/caption) preserving built-in table semantics.
|
package/llms/components/tabs.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
Tabs over Base UI Tabs (roving focus, arrow-key nav, ARIA tablist wiring) in two styles: underline (page sections) or pill (compact switchers). Tabs (Root) + TabsList + TabsTrigger + TabsPanel.
|
|
5
|
+
Tabs over Base UI Tabs (roving focus, arrow-key nav, ARIA tablist wiring) in two styles: underline (page sections) or pill (compact switchers), with one indicator that glides to the active tab. Tabs (Root) + TabsList + TabsTrigger + TabsPanel.
|
|
6
6
|
|
|
7
7
|
**Keywords:** tabs, tablist, panels, segmented content
|
|
8
8
|
|
|
@@ -15,8 +15,8 @@ Use Tabs to switch between related panels within the same page context.
|
|
|
15
15
|
- Do: Use tabs when each panel is peer content and users may compare or switch often.
|
|
16
16
|
- Do: Keep tab labels short and make the active tab unmistakable.
|
|
17
17
|
- Do: Use variant="pill" for a short switcher inside a card or toolbar (Preview / Code, List / Grid); keep the default underline for sections of a page.
|
|
18
|
-
- Don't: Do not use Tabs for unrelated pages or primary navigation that needs distinct URLs.
|
|
19
|
-
- Don't: Do not use tabs for sequential steps. Tabs are peer views the user returns to in any order; a process with an order needs a stepper, or the user will skip ahead.
|
|
18
|
+
- Don't: Do not use Tabs for unrelated pages or primary navigation that needs distinct URLs. → use **SideNav**, **NavigationMenu**
|
|
19
|
+
- Don't: Do not use tabs for sequential steps. Tabs are peer views the user returns to in any order; a process with an order needs a stepper, or the user will skip ahead. → use **Stepper**
|
|
20
20
|
|
|
21
21
|
### Anatomy
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ Use Tabs to switch between related panels within the same page context.
|
|
|
31
31
|
|
|
32
32
|
| Name | Type | Description |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger, which takes the theme's button shape (--button-radius). Triggers inherit the variant. |
|
|
34
|
+
| TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger, which takes the theme's button shape (--button-radius). Triggers inherit the variant. It renders the gliding indicator (Base UI Tabs.Indicator) itself, so do not add one. |
|
|
35
35
|
| TabsTrigger | Base UI Tabs.Tab props (value, disabled) + className | A single tab button. value pairs it with the panel of the same value. |
|
|
36
36
|
| TabsPanel | Base UI Tabs.Panel props (value, keepMounted) + className | The content shown when the matching trigger is active. |
|
|
37
37
|
| TabsContent | Alias of TabsPanel | Re-export of TabsPanel for consumers that prefer this name. |
|
|
@@ -41,17 +41,27 @@ Use Tabs to switch between related panels within the same page context.
|
|
|
41
41
|
| Name | Type | Default | Description |
|
|
42
42
|
| --- | --- | --- | --- |
|
|
43
43
|
| Tabs (Root) | Base UI Tabs.Root props (value, defaultValue, onValueChange, orientation) | defaultValue: 0 | Controls the active tab. Uncontrolled via defaultValue; controlled via value + onValueChange. |
|
|
44
|
+
| motion | "full" \| "subtle" \| "off" | "full" | On Tabs: how much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
44
45
|
|
|
45
46
|
## Tokens used
|
|
46
47
|
|
|
47
48
|
`border`, `muted`, `muted-foreground`, `foreground`, `ring`
|
|
48
49
|
|
|
50
|
+
## Motion
|
|
51
|
+
|
|
52
|
+
- **arrival** · Root · _no motion_ — Tabs arrive with the section they organise. An entrance of their own would announce navigation chrome louder than the content it navigates.
|
|
53
|
+
- **response** · TabsTrigger · `motion-interactive` — The label steps from muted to foreground under the pointer; the ease is what makes that read as an answer rather than a repaint.
|
|
54
|
+
- **transition** · Indicator · `motion-glide` — One underline (or pill) travels to the new tab and stretches to its width. A mark that teleports makes the reader re-find the selection on every click; one that moves is tracked without looking. Expressive: motion="subtle" lands it in place. Each tab's own mark is the fallback until the indicator has measured, so the server render is already right.
|
|
55
|
+
- **transition** · TabsPanel · `animate-fade-in` — The incoming panel fades in, opacity only, so a switch between two similar panels still registers. Skipped on first render (activation direction "none") so a page of tabs does not fade in on load, and the outgoing panel leaves layout at once so a quick second click never stacks both.
|
|
56
|
+
- **activity** · Root · _no motion_ — Switching tabs is synchronous. A panel that loads data shows its own Skeleton or Spinner inside the panel, where the wait is.
|
|
57
|
+
|
|
49
58
|
## Accessibility
|
|
50
59
|
|
|
51
60
|
- Base UI provides the tablist/tab/tabpanel roles, aria-selected, and roving tabindex.
|
|
52
61
|
- Arrow keys move between tabs; Home/End jump to first/last; panels are linked via aria-controls.
|
|
53
62
|
- Active tab styling reads from the data-active attribute; disabled tabs from data-disabled. Disabled tabs are skipped by arrow-key navigation.
|
|
54
63
|
- Keyboard focus shows a ring on the focused trigger in both variants; the panel remains focusable.
|
|
64
|
+
- The gliding indicator is role="presentation" decoration; aria-selected on the tab carries the state.
|
|
55
65
|
|
|
56
66
|
## Related
|
|
57
67
|
|
|
@@ -82,3 +92,11 @@ import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
|
|
|
82
92
|
|
|
83
93
|
<Tabs defaultValue="overview"><TabsList><TabsTrigger value="overview">Overview</TabsTrigger><TabsTrigger value="billing" disabled>Billing</TabsTrigger></TabsList><TabsPanel value="overview">Overview content</TabsPanel></Tabs>
|
|
84
94
|
```
|
|
95
|
+
|
|
96
|
+
### Subtle motion
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
|
|
100
|
+
|
|
101
|
+
<Tabs defaultValue="table" motion="subtle"><TabsList><TabsTrigger value="table">Table</TabsTrigger><TabsTrigger value="chart">Chart</TabsTrigger></TabsList><TabsPanel value="table">Rows</TabsPanel><TabsPanel value="chart">Trend</TabsPanel></Tabs>
|
|
102
|
+
```
|
|
@@ -49,6 +49,14 @@ Use ThemeSelector wherever a person picks a theme. The default `cards` layout is
|
|
|
49
49
|
|
|
50
50
|
`input`, `card`, `border`, `background`, `primary`, `primary-foreground`, `accent`, `muted`, `muted-foreground`, `ring`
|
|
51
51
|
|
|
52
|
+
## Motion
|
|
53
|
+
|
|
54
|
+
- **arrival** · Root · _no motion_ — It sits in a settings panel or a popover, which owns the entrance.
|
|
55
|
+
- **response** · Option (list) · `motion-interactive` — List rows ease their hover fill like Menu and Select rows, so a theme list beside a menu reads as the same kind of row.
|
|
56
|
+
- **response** · Name (grid) · `motion-interactive` — In the grid, the theme's name brightens on hover, eased. The preview tile's outline steps rather than easing — outline is not in the recipe's transition list — so the name carries the motion.
|
|
57
|
+
- **transition** · Check (list) · `motion-interactive` — The check fades into the chosen row and out of the old one, so the choice reads as moving, at the same moment the whole interface repaints in the new theme.
|
|
58
|
+
- **activity** · Root · _no motion_ — Applying a theme is synchronous: the whole interface repaints at once, and that repaint is the feedback.
|
|
59
|
+
|
|
52
60
|
## Accessibility
|
|
53
61
|
|
|
54
62
|
- The container is role="radiogroup" with an aria-label; each card is a role="radio" button with aria-checked reflecting the selected theme.
|
|
@@ -25,12 +25,21 @@ Use ThemeSwitcher for light, dark, and system color mode preference.
|
|
|
25
25
|
| value | "light" \| "dark" \| "system" | — | The currently selected color preference. |
|
|
26
26
|
| onValueChange | (value: ThemeMode) => void | — | Called with the next color preference when an option is chosen. |
|
|
27
27
|
| label | string | "Theme" | Accessible label for the radiogroup. |
|
|
28
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
28
29
|
| className | string | — | Merged onto the outer radiogroup container. |
|
|
29
30
|
|
|
30
31
|
## Tokens used
|
|
31
32
|
|
|
32
33
|
`border`, `card`, `muted-foreground`, `foreground`, `accent`, `accent-foreground`, `ring`
|
|
33
34
|
|
|
35
|
+
## Motion
|
|
36
|
+
|
|
37
|
+
- **arrival** · Root · _no motion_ — It arrives with the header or settings row it lives in; a theme control that announces itself competes with the page it themes.
|
|
38
|
+
- **response** · Option · `motion-interactive` — The icon steps from muted to foreground under the pointer, which is what tells an icon-only segment it is a control.
|
|
39
|
+
- **response** · Option · `motion-press` — A settle under the press confirms the tap on a 28px target before the theme change repaints the page around it.
|
|
40
|
+
- **transition** · Active pill · `motion-glide` — One pill slides to the chosen mode, stepping by option + gap on the spacing scale, so light → dark reads as the same control moving, not three icons trading a fill. Expressive: motion="subtle" lands it in place.
|
|
41
|
+
- **activity** · Root · _no motion_ — Changing the theme is synchronous; the repaint is the feedback.
|
|
42
|
+
|
|
34
43
|
## Accessibility
|
|
35
44
|
|
|
36
45
|
- The container is role="radiogroup" with an aria-label; each option is role="radio" with aria-checked reflecting the selected value.
|
|
@@ -69,3 +78,11 @@ import { ThemeSwitcher } from "@sableui/react"
|
|
|
69
78
|
|
|
70
79
|
<ThemeSwitcher value="light" onValueChange={setTheme} />
|
|
71
80
|
```
|
|
81
|
+
|
|
82
|
+
### Subtle motion
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { ThemeSwitcher } from "@sableui/react"
|
|
86
|
+
|
|
87
|
+
<ThemeSwitcher value={theme} onValueChange={setTheme} motion="subtle" />
|
|
88
|
+
```
|
|
@@ -15,8 +15,8 @@ Use ThinkingOrb while an AI agent is working and there is nothing specific to re
|
|
|
15
15
|
- Do: Reserve it for the single active agent state — the composer, the streaming message header, a standalone thinking screen. One orb on screen reads as intent; several read as decoration.
|
|
16
16
|
- Do: Switch mode to say what kind of work is happening: orbit for generic thinking, globe while searching or retrieving, mesh while relating sources or running tools. Pair it with a text status so the phase is legible without decoding the animation.
|
|
17
17
|
- Do: Use size="sm" inline in a message row or composer and size="lg" only when the orb is the main thing on screen. The size steps are separately tuned, not scaled, so the small one stays legible.
|
|
18
|
-
- Don't: Do not put one on every row of a list or in a repeated skeleton. Each orb runs its own animation frame loop painting hundreds of arcs; use Skeleton for content-shaped waits and Spinner inside buttons.
|
|
19
|
-
- Don't: Do not use it for a determinate wait. It carries no sense of how far along anything is — reach for Progress when a percentage exists.
|
|
18
|
+
- Don't: Do not put one on every row of a list or in a repeated skeleton. Each orb runs its own animation frame loop painting hundreds of arcs; use Skeleton for content-shaped waits and Spinner inside buttons. → use **Skeleton**, **Spinner**
|
|
19
|
+
- Don't: Do not use it for a determinate wait. It carries no sense of how far along anything is — reach for Progress when a percentage exists. → use **Progress**
|
|
20
20
|
- Don't: Do not rely on the animation alone to announce the wait. Give the label a short constant phrase (it is the role=status announcement) or place a status message next to it.
|
|
21
21
|
- Don't: Do not pass a label that changes while the orb is up. It is the accessible name of a live region, so every change re-announces the whole thing — feed it a per-token phase and a screen reader interrupts on each delta: "Chromatic drift" → "Chromatic drift · rgb-shift" → "Chromatic drift · rgb-shift → grain", which is unusable. The label names the wait ("Thinking"); the phase belongs in adjacent text, as in the "Inline with a status line" example. If the phase must be announced, announce it once when the turn settles from a separate live region — not per update.
|
|
22
22
|
|
package/llms/components/toast.md
CHANGED
|
@@ -15,7 +15,7 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
|
|
|
15
15
|
- Do: Mount one ToastProvider near the app root before any code calls toast() or useToast().
|
|
16
16
|
- Do: Use toast.promise for async actions so loading, success, and error states stay tied to one notification.
|
|
17
17
|
- Do: Keep toast copy short and outcome-focused. Add an action only when it is immediately useful, such as Undo.
|
|
18
|
-
- Don't: Use Toast for critical blockers or persistent outages. Use Banner or Dialog when the user must notice and act.
|
|
18
|
+
- Don't: Use Toast for critical blockers or persistent outages. Use Banner or Dialog when the user must notice and act. → use **Banner**, **Dialog**
|
|
19
19
|
- Don't: Fire multiple toasts for one operation. Update the same toast when a task progresses.
|
|
20
20
|
- Don't: Put the only path to an action in a toast that auto-dismisses. An Undo that expires on a timer is data loss on a schedule, so error toasts and any toast carrying actionProps default to timeout 0 and wait to be read — pass an explicit timeout only when the action is also reachable somewhere else. When a timeout exists at all, 5s is the floor: anything shorter cannot be read and acted on.
|
|
21
21
|
|
|
@@ -15,7 +15,7 @@ Use Toggle for button-like controls that can be pressed on or off within a toolb
|
|
|
15
15
|
- Do: Use it for transient UI modes such as bold, preview, pin, or favorite.
|
|
16
16
|
- Do: Make the pressed state visually and programmatically clear.
|
|
17
17
|
- Do: Use ToggleGroup appearance="segmented" for a single-select view switch (List / Grid). It is the same capsule ThemeSwitcher wears — do not hand-build a track around a row of toggles.
|
|
18
|
-
- Don't: Do not use Toggle for persistent application settings; use Switch.
|
|
18
|
+
- Don't: Do not use Toggle for persistent application settings; use Switch. → use **Switch**
|
|
19
19
|
- Don't: Do not leave an icon-only Toggle without an accessible name. It announces as "button, pressed", which tells a screen-reader user the state but never what the state applies to.
|
|
20
20
|
|
|
21
21
|
### Anatomy
|
|
@@ -30,15 +30,16 @@ Use Toggle for button-like controls that can be pressed on or off within a toolb
|
|
|
30
30
|
|
|
31
31
|
| Name | Type | Description |
|
|
32
32
|
| --- | --- | --- |
|
|
33
|
-
| ToggleGroup | Base UI ToggleGroup props (value, defaultValue, onValueChange, multiple, disabled, orientation, loopFocus) + appearance?: "default" \| "segmented" + className | Groups child Toggles and shares pressed state across them — each Toggle inside carries a value. Defaults to multiple: false (single-select) — the fit for view-mode switches and segmented controls; pass multiple to allow several pressed at once, e.g. a row of formatting toggles. |
|
|
33
|
+
| ToggleGroup | Base UI ToggleGroup props (value, defaultValue, onValueChange, multiple, disabled, orientation, loopFocus) + appearance?: "default" \| "segmented" + motion?: "full" \| "subtle" \| "off" + className | Groups child Toggles and shares pressed state across them — each Toggle inside carries a value. Defaults to multiple: false (single-select) — the fit for view-mode switches and segmented controls; pass multiple to allow several pressed at once, e.g. a row of formatting toggles. |
|
|
34
34
|
| ToggleGroupItem | Alias of Toggle | Re-export of Toggle for group composition — a Toggle carrying a value is the group item. |
|
|
35
35
|
|
|
36
36
|
## Props
|
|
37
37
|
|
|
38
38
|
| Name | Type | Default | Description |
|
|
39
39
|
| --- | --- | --- | --- |
|
|
40
|
-
| appearance | "default" \| "segmented" | "default" | On ToggleGroup: `default` is a loose row of toggles; `segmented` draws the capsule segmented control (one track, the pressed option filled) shared with ThemeSwitcher. Toggles inside inherit it. |
|
|
40
|
+
| appearance | "default" \| "segmented" | "default" | On ToggleGroup: `default` is a loose row of toggles; `segmented` draws the capsule segmented control (one track, the pressed option filled) shared with ThemeSwitcher. Single-select segmented draws ONE fill that glides to the pressed option and resizes to its label; multi-select keeps a fill per pressed option. Toggles inside inherit it. |
|
|
41
41
|
| multiple | boolean | false | On ToggleGroup: allow several toggles pressed at once. The default is single-select, which behaves like a radio group. |
|
|
42
|
+
| motion | "full" \| "subtle" \| "off" | "full" | On ToggleGroup: how much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
42
43
|
| Toggle | Base UI Toggle props (pressed, defaultPressed, onPressedChange, disabled, value) + className | defaultPressed: false | A two-state button. Uncontrolled via defaultPressed; controlled via pressed + onPressedChange. Pass value to identify it inside a ToggleGroup. |
|
|
43
44
|
|
|
44
45
|
## Variants
|
|
@@ -49,6 +50,14 @@ Use Toggle for button-like controls that can be pressed on or off within a toolb
|
|
|
49
50
|
|
|
50
51
|
`muted-foreground`, `foreground`, `accent`, `accent-foreground`, `card`, `border`, `ring`
|
|
51
52
|
|
|
53
|
+
## Motion
|
|
54
|
+
|
|
55
|
+
- **arrival** · Toggle · _no motion_ — Toggles arrive with the toolbar or header they sit in. Animating each one in would make a row of controls ripple for no reason.
|
|
56
|
+
- **response** · Toggle · `motion-interactive` — Hover and the pressed fill ease rather than flip, so pressing reads as the same button changing state, not a different button appearing.
|
|
57
|
+
- **response** · Toggle · `motion-press` — The small settle under the finger confirms the press landed before the state change is read — the difference matters most on touch, where there is no hover.
|
|
58
|
+
- **transition** · Segmented fill · `motion-glide` — In a single-select segmented group one fill travels to the chosen option and stretches to its label, so a view switch reads as one control now pointing elsewhere. Measured, because labels differ in width. Expressive: motion="subtle" lands it in place.
|
|
59
|
+
- **activity** · Toggle · _no motion_ — A toggle commits instantly. Work that follows from the new state shows where its result appears, not on the control.
|
|
60
|
+
|
|
52
61
|
## Accessibility
|
|
53
62
|
|
|
54
63
|
- Base UI sets aria-pressed on each Toggle and manages the pressed/disabled states.
|
|
@@ -86,6 +95,14 @@ import { Icon, Toggle, ToggleGroup } from "@sableui/react"
|
|
|
86
95
|
<ToggleGroup appearance="segmented" defaultValue={["list"]} aria-label="View"><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>
|
|
87
96
|
```
|
|
88
97
|
|
|
98
|
+
### Segmented with labels, subtle motion
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { Toggle, ToggleGroup } from "@sableui/react"
|
|
102
|
+
|
|
103
|
+
<ToggleGroup appearance="segmented" motion="subtle" defaultValue={["week"]} aria-label="Range"><Toggle value="day">Day</Toggle><Toggle value="week">Week</Toggle><Toggle value="month">Month</Toggle></ToggleGroup>
|
|
104
|
+
```
|
|
105
|
+
|
|
89
106
|
### Multi-select group
|
|
90
107
|
|
|
91
108
|
```tsx
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# ToolCall
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
One tool an agent called, as a quiet row inside its turn: a status glyph, what it did, the tool's name and where it is — with the arguments and result behind a disclosure.
|
|
6
|
+
|
|
7
|
+
**Keywords:** tool call, function call, tool use, agent step, mcp, ai, tool result, agent activity
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use it for each tool call in an assistant's turn, so the person can see what the agent did and check any step. The row is the summary — glyph, a plain-words title, the tool's name, the status — and stays one line, so a turn with ten calls is still scannable. Input and output sit behind the disclosure, because they are read rarely and, when they are, in full.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Give it a `title` in plain words, in the tense of the status: "Searching the docs" while running, "Searched the docs" once done. The `name` stays beside it in mono for the person who wants to know exactly which tool ran.
|
|
16
|
+
- Do: Drive `status` from the call's real state and keep the same element mounted as it changes. The spinner swaps for the outcome in place, and the change is announced to screen readers once.
|
|
17
|
+
- Do: Pass `error` with `status="error"`. It shows under the row without opening anything, because a failure is the one result everyone needs to read.
|
|
18
|
+
- Do: Pass a rendered result as `children` when the output is something to look at — a list of hits, a diff — rather than data to inspect. It replaces the output's code view, and `input` still shows above it.
|
|
19
|
+
- Don't: Do not put the approve and deny buttons in a ToolCall. A call held for permission is `status="awaiting"` here, and the decision itself is ApprovalBlock, which says what will happen and asks once.
|
|
20
|
+
- Don't: Do not use it for the agent's thinking or a progress note. A wait with no tool behind it is ThinkingOrb or a Spinner with a status line, and a row that claims a tool ran when none did makes every other row less trustworthy. → use **ThinkingOrb**, **Spinner**
|
|
21
|
+
- Don't: Do not open every call by default. A turn whose calls all show their JSON buries the answer; leave `defaultOpen` for the one call the person is likely to check, if any.
|
|
22
|
+
|
|
23
|
+
### Anatomy
|
|
24
|
+
|
|
25
|
+
| Part | Required | Description |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Status glyph | yes | Spinner while running, then the outcome: check, warning, clock (awaiting approval) or ×. The only part that moves. |
|
|
28
|
+
| Title | no | What the call does, in plain words. Without one, the tool's name leads. |
|
|
29
|
+
| Name | yes | The tool's own name in mono, after the title. Hidden below the sm breakpoint when a title is present. |
|
|
30
|
+
| Status | yes | One word for where the call is: Running, Needs approval, Done, Failed, Denied. |
|
|
31
|
+
| Error line | no | The failure message, under the row and always visible. |
|
|
32
|
+
| Details | no | Input and output as code, or a rendered result. A disclosure; the row is its trigger. |
|
|
33
|
+
|
|
34
|
+
## Props
|
|
35
|
+
|
|
36
|
+
| Name | Type | Default | Description |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| name | string | — | The tool's own name, as the model called it (`search_docs`). Shown in mono. |
|
|
39
|
+
| title | ReactNode | — | What the call does, in words a person reads. Leads the row; omit it and `name` leads. |
|
|
40
|
+
| status | "running" \| "awaiting" \| "complete" \| "error" \| "denied" | "running" | Where the call is. `awaiting` is held for the person's approval; `denied` is one they declined. |
|
|
41
|
+
| statusLabel | string | — | Overrides the status word, for another language or a more specific phrase. |
|
|
42
|
+
| input | unknown | — | The arguments. A string shows as is; anything else as formatted JSON. |
|
|
43
|
+
| output | unknown | — | The result, shown the same way as `input`. |
|
|
44
|
+
| error | ReactNode | — | What went wrong, for `status="error"`. Always visible under the row. |
|
|
45
|
+
| children | ReactNode | — | A rendered result in place of `output`'s code view. |
|
|
46
|
+
| defaultOpen | boolean | false | Open the details on mount. |
|
|
47
|
+
| open | boolean | — | Controlled open state of the details. |
|
|
48
|
+
| onOpenChange | (open: boolean) => void | — | Called when the details open or close. |
|
|
49
|
+
|
|
50
|
+
## Variants
|
|
51
|
+
|
|
52
|
+
- **status**: running, awaiting, complete, error, denied
|
|
53
|
+
|
|
54
|
+
## Tokens used
|
|
55
|
+
|
|
56
|
+
`card`, `border`, `destructive`, `muted`, `muted-foreground`, `foreground`, `tone-warning`, `ring`
|
|
57
|
+
|
|
58
|
+
## Motion
|
|
59
|
+
|
|
60
|
+
- **arrival** · Root · _no motion_ — It arrives as part of the assistant's turn, and the turn's own arrival covers it. A second entrance per call would make a busy turn twitch.
|
|
61
|
+
- **response** · Header · `motion-interactive` — The header is the disclosure trigger but reads as a status row. The hover step is what tells the person it opens.
|
|
62
|
+
- **transition** · Status glyph · `motion-icon-swap` — The spinner becoming a check or a warning is the moment the call settles, and it is the one change in the row worth catching from the corner of the eye. The swap reads as one mark replaced, not a flicker.
|
|
63
|
+
- **transition** · Details · `motion-disclosure` — The height change connects the code that appears to the row that was pressed, in a thread where several rows look alike.
|
|
64
|
+
- **activity** · Status glyph · `motion-spin` — While the call runs, the spinner is what separates working from stuck. It pauses once the call settles, so a long thread of finished calls costs nothing.
|
|
65
|
+
|
|
66
|
+
## Accessibility
|
|
67
|
+
|
|
68
|
+
- With details, the header is a button with aria-expanded (Base UI Collapsible); its name is the title, the tool name and the status word, so it reads as one sentence.
|
|
69
|
+
- A status change after mount is announced once through a polite role=status region ("Searched the docs: Done"). The status a call mounts with is not announced, so a thread restored from history stays quiet.
|
|
70
|
+
- The glyph is aria-hidden: the status word beside it carries the meaning, so colour and shape are never the only signal.
|
|
71
|
+
|
|
72
|
+
## Related
|
|
73
|
+
|
|
74
|
+
- Components: Message, ThinkingOrb, StreamingText, CodeBlock, Collapsible, InlineStatus
|
|
75
|
+
|
|
76
|
+
## Examples
|
|
77
|
+
|
|
78
|
+
### A finished call
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { ToolCall } from "@sableui/react"
|
|
82
|
+
|
|
83
|
+
<ToolCall
|
|
84
|
+
name="search_docs"
|
|
85
|
+
title="Searched the docs"
|
|
86
|
+
status="complete"
|
|
87
|
+
input={{ query: "date range picker" }}
|
|
88
|
+
output={{ hits: ["DatePicker", "Calendar", "DateTile"] }}
|
|
89
|
+
/>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Running, then failed
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { ToolCall } from "@sableui/react"
|
|
96
|
+
|
|
97
|
+
<div className="flex flex-col gap-2">
|
|
98
|
+
<ToolCall name="read_file" title="Reading package.json" status="running" input={{ path: "package.json" }} />
|
|
99
|
+
<ToolCall
|
|
100
|
+
name="run_tests"
|
|
101
|
+
title="Ran the tests"
|
|
102
|
+
status="error"
|
|
103
|
+
error="3 of 412 tests failed in date-picker.test.tsx."
|
|
104
|
+
input={{ filter: "date-picker" }}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Waiting for approval
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { ToolCall } from "@sableui/react"
|
|
113
|
+
|
|
114
|
+
<ToolCall name="send_email" title="Send the invoice to Ada" status="awaiting" input={{ to: "ada@example.com", subject: "INV-1042" }} />
|
|
115
|
+
```
|
|
@@ -18,7 +18,7 @@ Use Toolbar when three or more related controls sit together and act on the same
|
|
|
18
18
|
- Do: Compose triggers and toggles through ToolbarButton's render prop — render={<MenuTrigger />}, render={<PopoverTrigger />}, or render={<Toggle value="bold" />} inside a ToggleGroup — so they join the toolbar's arrow-key order.
|
|
19
19
|
- Don't: Put a bare Button, Toggle or Input inside a Toolbar. It renders, but it is not a toolbar item: the arrow keys skip it and it takes its own tab stop, which is the inconsistency Toolbar exists to remove. Use ToolbarButton, ToolbarInput, or render through ToolbarButton.
|
|
20
20
|
- Don't: Wrap one or two buttons, or a dialog's Cancel/Confirm pair, in a Toolbar. Those are independent actions; arrow-key movement there is a surprise, not a shortcut. A plain row of Buttons is right.
|
|
21
|
-
- Don't: Use Toolbar for navigation between pages or views. Links that go somewhere are Tabs, SideNav or Breadcrumbs; ToolbarLink is for the odd link that sits among actions, like Help.
|
|
21
|
+
- Don't: Use Toolbar for navigation between pages or views. Links that go somewhere are Tabs, SideNav or Breadcrumbs; ToolbarLink is for the odd link that sits among actions, like Help. → use **Tabs**, **SideNav**, **Breadcrumbs**
|
|
22
22
|
- Don't: Give toolbar buttons primary or destructive fills by default. A toolbar is a quiet strip of tools; the ghost default keeps it from competing with the page's one primary action.
|
|
23
23
|
|
|
24
24
|
### Anatomy
|
|
@@ -14,7 +14,7 @@ Use Tooltip for brief nonessential hints tied to a specific control or piece of
|
|
|
14
14
|
|
|
15
15
|
- Do: Use it to name icon-only controls or clarify compact UI when the label cannot be visible.
|
|
16
16
|
- Do: Keep tooltip content short and noninteractive.
|
|
17
|
-
- Don't: Do not put critical instructions, errors, or interactive content inside Tooltip.
|
|
17
|
+
- Don't: Do not put critical instructions, errors, or interactive content inside Tooltip. → use **Popover**, **InlineStatus**
|
|
18
18
|
- Don't: Do not let a tooltip serve as an icon button's accessible name. A tooltip is not a name — without its own aria-label the button announces as "button", and the tooltip never opens for a screen reader at all.
|
|
19
19
|
|
|
20
20
|
### Anatomy
|
|
@@ -35,7 +35,8 @@ Use Tooltip for brief nonessential hints tied to a specific control or piece of
|
|
|
35
35
|
| TooltipTrigger | Base UI Trigger props + render | The anchor element. Use render={<Button/>} to compose with another component. |
|
|
36
36
|
| TooltipContent | Base UI Popup props + side \| align \| sideOffset + className | Portals + positions + paints the popup. side ("top" \| "bottom" \| "left" \| "right" \| "inline-*") picks which side of the trigger to render against, defaults to "top", and auto-flips on collision; align ("start" \| "center" \| "end") defaults to "center"; sideOffset (gap between trigger and popup, in pixels) defaults to 6. |
|
|
37
37
|
| TooltipArrow | Base UI Arrow props + className | Optional pointer connecting the popup to its trigger — a popover-coloured square turned 45° that carries the border on its outward edges. Place inside TooltipContent. |
|
|
38
|
-
| SimpleTooltip | Base UI Tooltip.Root props + content: ReactNode + children: ReactElement + side \| align \| sideOffset | Shorthand wrapping Root + Trigger + Content for the common case. The single child becomes the trigger (via Base UI render); content sets the body; side / align / sideOffset forward to the content with the same defaults. Composition parts remain for advanced use. |
|
|
38
|
+
| SimpleTooltip | Base UI Tooltip.Root props + content: ReactNode + children: ReactElement + side \| align \| sideOffset | Shorthand wrapping Root + Trigger + Content for the common case. The single child becomes the trigger (via Base UI render); content sets the body; side / align / sideOffset forward to the content with the same defaults. Composition parts remain for advanced use. Inside a TooltipGroup it becomes a trigger of the group's shared tooltip instead. |
|
|
39
|
+
| TooltipGroup | { children: ReactNode; motion?: "full" \| "subtle" \| "off" } | One tooltip shared by every SimpleTooltip inside it — for a toolbar or a row of icon buttons. Moving the pointer along the row glides the tooltip to the next trigger and swaps its label (old fades out, new fades in, each sliding toward the direction of travel), instead of closing one tooltip and opening another a few pixels away. Renders no element. Each SimpleTooltip keeps its own content / side / align / sideOffset and `disabled`; root props such as open / onOpenChange belong to the shared tooltip and are ignored inside a group. `motion` turns it down: `subtle` jumps between triggers and only cross-fades the label. The composable Tooltip parts are unaffected. |
|
|
39
40
|
|
|
40
41
|
## Props
|
|
41
42
|
|
|
@@ -53,6 +54,7 @@ Use Tooltip for brief nonessential hints tied to a specific control or piece of
|
|
|
53
54
|
- **arrival** · Content · `motion-pop` — The transform-origin follows the anchor, so the tooltip appears to grow out of the element it describes. That is what binds it to its trigger — a bubble that materialises at full size near two possible triggers belongs to neither.
|
|
54
55
|
- **response** · Content · _no motion_ — The tooltip is not a hover target; pointing at it is how you dismiss it. Styling it for the pointer would suggest otherwise.
|
|
55
56
|
- **transition** · Content · `motion-pop` — Same recipe, exit direction — it collapses back toward the anchor. Content that fades from a fixed size on close reads as a second, unrelated element disappearing.
|
|
57
|
+
- **transition** · TooltipGroup · _custom (outside the recipe layer)_ — Moving between triggers in a group, the one tooltip glides to the next trigger, resizes to its label and cross-fades the text with a short slide toward the direction of travel. Separate tooltips would close here and open there, two surfaces blinking side by side; the glide says it is the same hint following the pointer. Base UI's handle + Viewport supply the shared popup and the previous/current content; the timing is the expressive glide (slow, emphasized), so `subtle` keeps only the cross-fade.
|
|
56
58
|
- **activity** · Content · _no motion_ — Tooltip copy is resident before the tooltip opens. Anything that has to be fetched is a Popover with its own loading state.
|
|
57
59
|
|
|
58
60
|
## Accessibility
|
|
@@ -91,3 +93,11 @@ import { Button, Icon, SimpleTooltip } from "@sableui/react"
|
|
|
91
93
|
|
|
92
94
|
<SimpleTooltip content="Saved to your workspace."><Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button></SimpleTooltip>
|
|
93
95
|
```
|
|
96
|
+
|
|
97
|
+
### One tooltip gliding along a toolbar (TooltipGroup)
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Icon, SimpleTooltip, Toolbar, ToolbarButton, TooltipGroup } from "@sableui/react"
|
|
101
|
+
|
|
102
|
+
<TooltipGroup><Toolbar aria-label="Formatting"><SimpleTooltip content="Bold"><ToolbarButton size="icon-sm" aria-label="Bold"><Icon name="bold" /></ToolbarButton></SimpleTooltip><SimpleTooltip content="Italic"><ToolbarButton size="icon-sm" aria-label="Italic"><Icon name="italic" /></ToolbarButton></SimpleTooltip><SimpleTooltip content="Insert link"><ToolbarButton size="icon-sm" aria-label="Insert link"><Icon name="link" /></ToolbarButton></SimpleTooltip></Toolbar></TooltipGroup>
|
|
103
|
+
```
|
package/llms/components/tree.md
CHANGED
|
@@ -61,11 +61,19 @@ Use Tree for the hierarchy panel in a workspace: layers, objects, an inspector o
|
|
|
61
61
|
| disclosureIcon | ReactNode | — | Replaces TreeRow's semantic chevron-down icon. Omit to use the chevron-down from the app's IconProvider set. |
|
|
62
62
|
| actions | ReactNode | — | Right-rail controls for a TreeRow: visibility, solo, a row menu. |
|
|
63
63
|
| label | ReactNode | — | TreeAddRow's visible label. Name what gets added — 'Add effect', not 'Add'. |
|
|
64
|
+
| motion | "full" \| "subtle" \| "off" | "full" | On Tree. 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` expanded rows appear at once instead of unfolding; the chevron still turns. |
|
|
64
65
|
|
|
65
66
|
## Tokens used
|
|
66
67
|
|
|
67
68
|
`panel-raised`, `hairline`, `foreground`, `muted-foreground`, `primary`, `ring`
|
|
68
69
|
|
|
70
|
+
## Motion
|
|
71
|
+
|
|
72
|
+
- **arrival** · TreeRow · `motion-arrive` — Rows a user's expand reveals fade down into place in document order (a capped 18ms stagger), so the eye can follow where the children came from. An animation (motion-arrive) rather than a transition, because the rows already wear motion-interactive and a second transition list would fight it. Only on a user expand of a `subtree` — a tree that mounts open shows its rows at once, and rows added later to an open folder do not cascade. Expressive: `subtle` shows them at once.
|
|
73
|
+
- **response** · TreeRow · `motion-interactive` — Hover and selection fills ease, so sweeping the pointer down a dense panel reads as a highlight following it rather than rows flickering.
|
|
74
|
+
- **transition** · Disclosure · `motion-interactive` — The chevron turns with the rows arriving under it; motion-interactive lists `rotate`, which is what -rotate-90 writes, so it no longer flips in one frame.
|
|
75
|
+
- **activity** · Tree · _no motion_ — A tree has no indeterminate state of its own; a branch that loads its children should render a TreeEmpty or a loading row inside it.
|
|
76
|
+
|
|
69
77
|
## Accessibility
|
|
70
78
|
|
|
71
79
|
- Tree implements the ARIA tree view pattern: Tree is role="tree", each TreeRow and TreeAddRow is role="treeitem" with aria-level, expandable rows carry aria-expanded, and a TreeSection is a role="group" labelled by its heading. Rows stay flat in the DOM — hierarchy is aria-level — so the fills span the panel's full width.
|
package/llms/components/video.md
CHANGED
|
@@ -30,6 +30,14 @@ Use Video to embed playable or previewable video media inside a page.
|
|
|
30
30
|
|
|
31
31
|
`border`, `overlay`, `overlay-foreground`, `ring`
|
|
32
32
|
|
|
33
|
+
## Motion
|
|
34
|
+
|
|
35
|
+
- **arrival** · Root · _no motion_ — The frame arrives with its page. Once there is a source, the poster and controls are the browser's.
|
|
36
|
+
- **response** · Play button · `motion-interactive` — With `onRequestPlay`, the play disc firms its scrim on hover, eased, so the mark reads as pressable over any image.
|
|
37
|
+
- **response** · Play button · `motion-press` — Loading a source can take a moment; the settle on press confirms the tap before the player appears.
|
|
38
|
+
- **transition** · Root · _no motion_ — Placeholder to player is a swap of elements: the native video mounts where the poster was. Animating the swap would delay the playback the person just asked for.
|
|
39
|
+
- **activity** · Root · _no motion_ — Buffering belongs to the native controls, which draw their own indicator. A second loader over the frame would compete with the browser's.
|
|
40
|
+
|
|
33
41
|
## Accessibility
|
|
34
42
|
|
|
35
43
|
- Mounts a native <video> element, preserving built-in keyboard controls when controls is enabled.
|