@sableui/react 0.13.2 → 0.15.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 +172 -0
- package/CONTRIBUTING.md +6 -4
- package/DESIGN.md +10 -3
- package/dist/blocks/approval-block.js +93 -0
- package/dist/blocks/approval-block.meta.js +23 -0
- package/dist/blocks/index.d.ts +27 -1
- package/dist/blocks/index.js +8 -0
- package/dist/blocks/message-reactions-block.js +87 -0
- package/dist/blocks/message-reactions-block.meta.js +23 -0
- package/dist/components/accordion/accordion.meta.js +8 -2
- package/dist/components/area-chart/area-chart.meta.js +10 -3
- package/dist/components/attachment/attachment.meta.js +26 -0
- 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/bubble/bubble.js +105 -5
- package/dist/components/bubble/bubble.meta.js +42 -6
- package/dist/components/bubble/bubble.variants.js +1 -1
- package/dist/components/bubble/index.js +2 -0
- package/dist/components/button/button.js +21 -4
- package/dist/components/button/button.meta.js +7 -7
- 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 +32 -0
- 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 +32 -0
- package/dist/components/color-field/color-field.meta.js +26 -0
- package/dist/components/combobox/combobox.meta.js +44 -0
- package/dist/components/command-menu/command-menu.js +8 -3
- package/dist/components/command-menu/command-menu.meta.js +38 -0
- 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/data-table/data-table.js +6 -2
- package/dist/components/data-table/data-table.meta.js +32 -0
- package/dist/components/entity/entity.meta.js +26 -0
- package/dist/components/feedback/feedback.js +120 -47
- package/dist/components/feedback/feedback.meta.js +55 -4
- package/dist/components/input-group/input-group.meta.js +27 -1
- package/dist/components/like-button/index.js +7 -0
- package/dist/components/like-button/like-button.js +89 -0
- package/dist/components/like-button/like-button.meta.js +132 -0
- package/dist/components/line-chart/line-chart.meta.js +10 -3
- package/dist/components/middle-truncate/middle-truncate.meta.js +1 -1
- package/dist/components/note/note.meta.js +32 -0
- 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/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 +32 -0
- 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 +26 -0
- package/dist/components/radio-card/radio-card.meta.js +38 -0
- package/dist/components/reaction-picker/index.js +13 -0
- package/dist/components/reaction-picker/reaction-picker.js +104 -0
- package/dist/components/reaction-picker/reaction-picker.meta.js +156 -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/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 +32 -0
- 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/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/table/table.meta.js +26 -0
- package/dist/components/tabs/tabs.js +27 -8
- package/dist/components/tabs/tabs.meta.js +46 -3
- 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/toast/toast.js +25 -13
- package/dist/components/toast/toast.meta.js +7 -1
- package/dist/components/toggle/toggle.js +28 -5
- package/dist/components/toggle/toggle.meta.js +44 -2
- 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/tooltip/index.js +3 -1
- package/dist/components/tooltip/tooltip.js +84 -0
- package/dist/components/tooltip/tooltip.meta.js +16 -1
- 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 +130 -104
- package/dist/foundations/motion.js +69 -3
- package/dist/icon-sets/reicon.js +11 -1
- package/dist/index.d.ts +907 -104
- package/dist/index.js +6 -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 +5 -0
- package/dist/motion/Burst.js +79 -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 +17 -2
- package/dist/motion/useAutoSize.js +42 -0
- package/dist/motion/useShake.js +25 -0
- package/dist/styles.css +439 -5
- package/llms/blocks/approval-block.md +128 -0
- package/llms/blocks/message-reactions-block.md +116 -0
- package/llms/components/accordion.md +3 -2
- package/llms/components/area-chart.md +4 -3
- package/llms/components/attachment.md +7 -0
- 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/bubble.md +17 -7
- 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 +8 -0
- package/llms/components/code-block.md +8 -0
- package/llms/components/collapsible.md +8 -0
- package/llms/components/color-field.md +7 -0
- package/llms/components/combobox.md +10 -0
- package/llms/components/command-menu.md +9 -0
- package/llms/components/confirm-button.md +92 -0
- package/llms/components/data-table.md +8 -0
- package/llms/components/entity.md +7 -0
- package/llms/components/feedback.md +23 -3
- package/llms/components/input-group.md +8 -1
- package/llms/components/like-button.md +91 -0
- package/llms/components/line-chart.md +4 -3
- package/llms/components/middle-truncate.md +1 -1
- package/llms/components/note.md +8 -0
- 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/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 +8 -0
- 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 +7 -0
- package/llms/components/reaction-picker.md +115 -0
- 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/show-more.md +9 -0
- package/llms/components/side-nav.md +8 -0
- package/llms/components/sidebar.md +9 -0
- package/llms/components/slider.md +7 -0
- package/llms/components/sparkline.md +3 -2
- package/llms/components/table.md +7 -0
- package/llms/components/tabs.md +20 -2
- package/llms/components/theme-selector.md +8 -0
- package/llms/components/theme-switcher.md +17 -0
- package/llms/components/toast.md +2 -1
- package/llms/components/toggle.md +19 -2
- package/llms/components/tool-call.md +115 -0
- package/llms/components/tooltip.md +11 -1
- package/llms/components/tree.md +8 -0
- package/llms/components/video.md +8 -0
- package/llms/foundations/motion.md +64 -3
- package/llms/reference/utilities.md +26 -1
- package/llms/tasks/reviewing-motion.md +14 -0
- package/llms-full.txt +1337 -69
- package/llms.txt +19 -11
- package/package.json +1 -1
- package/registry.json +3040 -124
- package/targets/ios.json +45 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# MessageReactionsBlock
|
|
2
|
+
|
|
3
|
+
A short thread you can react to: right-click or long-press a bubble for a menu with a row of quick reactions above Reply and Copy, or use the add-reaction pill under a message. A new reaction blooms in and sprays copies of its emoji.
|
|
4
|
+
|
|
5
|
+
**Intent:** Reach for this when a conversation excerpt should be reactable — a team thread, a comment card, a support transcript. A read-only excerpt is ChatThreadBlock; a working chat with a composer and history is the ChatPanel page shell.
|
|
6
|
+
|
|
7
|
+
**Block:** Social & messaging · card · designed for 18rem–24rem wide (fills its container up to the max)
|
|
8
|
+
|
|
9
|
+
## Notes
|
|
10
|
+
|
|
11
|
+
- Two ways in, on purpose: the right-click / long-press menu is fast once you know it is there, and the add-reaction pill is how a first-time reader finds reactions at all.
|
|
12
|
+
- The quick row in the menu is MenuReactions (checkbox menu items) and the one in the pill's popover is ReactionPicker (a toolbar), so each is reachable by the keyboard model of the surface it sits in.
|
|
13
|
+
- Only the reader's own reaction celebrates. Counts from other people roll quietly, and the reactions a page loads with never play.
|
|
14
|
+
- The add-reaction pill appears only on messages that already have reactions; a message with none is reacted to from its menu, which keeps quiet rows quiet.
|
|
15
|
+
|
|
16
|
+
## Accessibility
|
|
17
|
+
|
|
18
|
+
- Each bubble is focusable, and its menu opens on right click, long press, Shift+F10 or the Menu key; its reactions are checkbox items reporting whether the reader has used each one.
|
|
19
|
+
- Each reaction pill is a toggle named by its meaning and count ("Party, 3") with `aria-pressed`; the emoji and the burst are hidden from assistive tech.
|
|
20
|
+
- Under reduced motion the reaction appears and the count changes at once; nothing else is lost.
|
|
21
|
+
|
|
22
|
+
## Source
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Bubble, BubbleReaction, BubbleReactionAdd, BubbleReactions, Card, CardContent, CardFooter, cn, ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, DEFAULT_REACTIONS, MenuReactions, Message } from "@sableui/react"
|
|
26
|
+
|
|
27
|
+
const messages: { id: string; text: string; mine: boolean; reactions: Record<string, number> }[] = [
|
|
28
|
+
{ id: "m1", text: "The new onboarding flow is live on staging 🚀", mine: false, reactions: { "🎉": 2, "👀": 1 } },
|
|
29
|
+
{ id: "m2", text: "Clicked through it twice. The progress bar is so smooth.", mine: true, reactions: {} },
|
|
30
|
+
{ id: "m3", text: "Ship it Friday?", mine: false, reactions: { "👍": 1 } },
|
|
31
|
+
]
|
|
32
|
+
const hint = "Right-click or long-press a message to react."
|
|
33
|
+
// Every emoji that can appear needs a name: it is the pill's accessible label.
|
|
34
|
+
const labels: Record<string, string> = {
|
|
35
|
+
...Object.fromEntries(DEFAULT_REACTIONS.map((r) => [r.emoji, r.label])),
|
|
36
|
+
"👀": "Eyes",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface MessageReactionsBlockProps {
|
|
40
|
+
className?: string
|
|
41
|
+
/** false drops the card's surface and border. Keep it framed on a muted page: incoming bubbles are the muted surface too. */
|
|
42
|
+
framed?: boolean
|
|
43
|
+
/** Called when the reader adds or removes a reaction, with the message, the emoji and whether it is now on. */
|
|
44
|
+
onReact?: (messageId: string, emoji: string, on: boolean) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function MessageReactionsBlock({ className, framed = true, onReact }: MessageReactionsBlockProps) {
|
|
48
|
+
// The reader's own reactions, per message. Everyone else's are the counts above.
|
|
49
|
+
const [mine, setMine] = useState<Record<string, string[]>>({ m1: ["🎉"] })
|
|
50
|
+
const toggle = (messageId: string, emoji: string) => {
|
|
51
|
+
const on = !(mine[messageId] ?? []).includes(emoji)
|
|
52
|
+
// Decided again from `prev`, so two toggles in one batch cannot add an emoji twice.
|
|
53
|
+
setMine((prev) => {
|
|
54
|
+
const current = prev[messageId] ?? []
|
|
55
|
+
const next = current.includes(emoji) ? current.filter((e) => e !== emoji) : [...current, emoji]
|
|
56
|
+
return { ...prev, [messageId]: next }
|
|
57
|
+
})
|
|
58
|
+
onReact?.(messageId, emoji, on)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Card variant={framed ? "default" : "flat"} className={cn("w-full max-w-sm", className)}>
|
|
63
|
+
<CardContent className="flex flex-col gap-3">
|
|
64
|
+
{messages.map((m) => {
|
|
65
|
+
const align = m.mine ? "end" : "start"
|
|
66
|
+
const chosen = mine[m.id] ?? []
|
|
67
|
+
// Others' counts, plus the reader's own: a reaction only the reader gave
|
|
68
|
+
// is a pill of its own, ordered after the ones already there.
|
|
69
|
+
const counts: Record<string, number> = { ...m.reactions }
|
|
70
|
+
for (const emoji of chosen) counts[emoji] = (counts[emoji] ?? 0)
|
|
71
|
+
const pills = Object.entries(counts).map(([emoji, others]) => ({
|
|
72
|
+
emoji,
|
|
73
|
+
active: chosen.includes(emoji),
|
|
74
|
+
count: others + (chosen.includes(emoji) ? 1 : 0),
|
|
75
|
+
}))
|
|
76
|
+
return (
|
|
77
|
+
<Message key={m.id} messageId={m.id} align={m.mine ? "end" : undefined}>
|
|
78
|
+
<ContextMenu>
|
|
79
|
+
<ContextMenuTrigger
|
|
80
|
+
// Focusable, so Shift+F10 or the Menu key opens the same menu.
|
|
81
|
+
render={<Bubble tabIndex={0} variant={m.mine ? "primary" : undefined} align={align} />}
|
|
82
|
+
>
|
|
83
|
+
{m.text}
|
|
84
|
+
</ContextMenuTrigger>
|
|
85
|
+
<ContextMenuContent>
|
|
86
|
+
<MenuReactions selected={chosen} onSelect={(emoji) => toggle(m.id, emoji)} />
|
|
87
|
+
<ContextMenuSeparator />
|
|
88
|
+
<ContextMenuItem>Reply</ContextMenuItem>
|
|
89
|
+
<ContextMenuItem>Copy text</ContextMenuItem>
|
|
90
|
+
</ContextMenuContent>
|
|
91
|
+
</ContextMenu>
|
|
92
|
+
{/* Rendered even while empty (it hides itself), so the first reaction still celebrates. */}
|
|
93
|
+
<BubbleReactions align={align}>
|
|
94
|
+
{pills.map((p) => (
|
|
95
|
+
<BubbleReaction
|
|
96
|
+
key={p.emoji}
|
|
97
|
+
emoji={p.emoji}
|
|
98
|
+
label={labels[p.emoji]}
|
|
99
|
+
count={p.count}
|
|
100
|
+
active={p.active}
|
|
101
|
+
onClick={() => toggle(m.id, p.emoji)}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
{pills.length > 0 ? (
|
|
105
|
+
<BubbleReactionAdd selected={chosen} onSelect={(emoji) => toggle(m.id, emoji)} />
|
|
106
|
+
) : null}
|
|
107
|
+
</BubbleReactions>
|
|
108
|
+
</Message>
|
|
109
|
+
)
|
|
110
|
+
})}
|
|
111
|
+
</CardContent>
|
|
112
|
+
<CardFooter className="text-caption text-muted-foreground">{hint}</CardFooter>
|
|
113
|
+
</Card>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
@@ -52,6 +52,7 @@ Use Accordion to reveal a vertical set of related sections while keeping the sur
|
|
|
52
52
|
- **arrival** · Root · _no motion_ — An accordion is page furniture that is present from first paint. Animating it in would stage content the reader is already scanning past.
|
|
53
53
|
- **response** · AccordionTrigger · `motion-interactive` — The whole header row is the hit target, and it looks like a heading rather than a control. The hover shift is what tells the reader it can be clicked before they click it.
|
|
54
54
|
- **transition** · AccordionPanel · `motion-disclosure` — Panel height is the only cue that content came from THIS header rather than appearing at the bottom of the page. Snapping it open moves everything below by a screenful with nothing connecting cause to effect.
|
|
55
|
+
- **transition** · Chevron · `motion-interactive` — The chevron turning is the header's half of the same event as the panel opening. motion-interactive lists `rotate` (what Tailwind's rotate-* writes), so it turns with the panel instead of flipping 180° in one frame beside an easing panel. A custom `indicator` that rotates should carry motion-interactive itself.
|
|
55
56
|
- **activity** · Root · _no motion_ — Nothing here is indeterminate — the panel content is already resident. A panel that loads on expand should put its own loader inside the panel.
|
|
56
57
|
|
|
57
58
|
## Accessibility
|
|
@@ -60,7 +61,7 @@ Use Accordion to reveal a vertical set of related sections while keeping the sur
|
|
|
60
61
|
- Arrow keys move roving focus between triggers; Home/End jump to the first/last; loopFocus wraps by default.
|
|
61
62
|
- Each trigger toggles its panel with Enter/Space; the panel is announced as a labelled region.
|
|
62
63
|
- A disabled AccordionItem dims and is skipped by keyboard focus while neighbouring items stay interactive.
|
|
63
|
-
- The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible
|
|
64
|
+
- The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible; both ride motion-interactive, so reduced motion stills them with everything else.
|
|
64
65
|
|
|
65
66
|
## Related
|
|
66
67
|
|
|
@@ -89,5 +90,5 @@ import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "@sab
|
|
|
89
90
|
```tsx
|
|
90
91
|
import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Icon } from "@sableui/react"
|
|
91
92
|
|
|
92
|
-
<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground
|
|
93
|
+
<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground motion-interactive [[data-panel-open]_&]:rotate-45" />}>Can I cancel anytime?</AccordionTrigger><AccordionPanel>Yes — from billing settings.</AccordionPanel></AccordionItem></Accordion>
|
|
93
94
|
```
|
|
@@ -34,7 +34,8 @@ Use AreaChart for volume over time — visitors, revenue, usage — where the fi
|
|
|
34
34
|
| grid | boolean | true (false for horizontal bars) | Hairline gridlines along the value axis. |
|
|
35
35
|
| xAxis | boolean | true for columns; false for horizontal bars | The horizontal axis. |
|
|
36
36
|
| yAxis | boolean | false for columns; true for horizontal bars | The vertical axis. |
|
|
37
|
-
|
|
|
37
|
+
| 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 arrival (draw-on, rising fill) and the data-change morph are the flourish; the gliding cursor and tooltip are the feedback `subtle` keeps. |
|
|
38
|
+
| animate | boolean | true | Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. `animate={false}` is `motion="subtle"`. |
|
|
38
39
|
| activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
|
|
39
40
|
| defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
|
|
40
41
|
| onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
|
|
@@ -48,9 +49,9 @@ Use AreaChart for volume over time — visitors, revenue, usage — where the fi
|
|
|
48
49
|
|
|
49
50
|
## Motion
|
|
50
51
|
|
|
51
|
-
- **arrival** · Area + line · _custom (outside the recipe layer)_ — Once on screen, each line draws on (stroke-dashoffset) and its fill rises from the baseline, series staggered — the trend is read as it is drawn. On the motion tokens; reduced motion shows the finished chart.
|
|
52
|
+
- **arrival** · Area + line · _custom (outside the recipe layer)_ — Once on screen, each line draws on (stroke-dashoffset) and its fill rises from the baseline, series staggered — the trend is read as it is drawn. 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
53
|
- **response** · Cursor + tooltip · _no motion_ — Hover and focus move the cursor and tooltip instantly — a lagging cursor reads as a slow chart.
|
|
53
|
-
- **transition** · Area + line · _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.
|
|
54
|
+
- **transition** · Area + line · _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
55
|
- **activity** · AreaChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
|
|
55
56
|
|
|
56
57
|
## Accessibility
|
|
@@ -54,6 +54,13 @@ The card a file takes in a conversation — in the composer while it uploads, an
|
|
|
54
54
|
|
|
55
55
|
`card`, `border`, `border-strong`, `muted`, `muted-foreground`, `foreground`, `destructive`, `overlay`, `overlay-foreground`, `ring`
|
|
56
56
|
|
|
57
|
+
## Motion
|
|
58
|
+
|
|
59
|
+
- **arrival** · Root · _no motion_ — An attachment lands as part of a composer or a message, and that surface owns the entrance. A card that animated in on its own would read as a second, separate event — as if another file had been added.
|
|
60
|
+
- **response** · Root · `motion-interactive` — With `onOpen`, the edge steps up under the pointer; it is the only cue that the card itself opens the file, not just its actions. Eased, it reads as the card answering the cursor rather than a border flickering as the pointer crosses a busy composer.
|
|
61
|
+
- **transition** · Root · `motion-interactive` — Uploading to failed re-colours the edge and fill without changing the shape. The ease keeps it legible as the same file turning red; snapped, a failed card looks like a different card replaced the one that was sent.
|
|
62
|
+
- **activity** · Progress · `motion-pulse` — While uploading with no known `progress`, the bar is indeterminate and pulses (Progress's own recipe); with a number, the fill eases toward each new value. A still bar at a fixed width is indistinguishable from an upload that has stalled.
|
|
63
|
+
|
|
57
64
|
## Accessibility
|
|
58
65
|
|
|
59
66
|
- The full-card trigger is a real stretched `<button>` with an accessible label, not a click handler on a div — so it is one tab stop, keyboard-activatable, and focus-ringed.
|
|
@@ -34,7 +34,8 @@ Use BarChart to compare amounts across categories — the default chart for "how
|
|
|
34
34
|
| grid | boolean | true (false for horizontal bars) | Hairline gridlines along the value axis. |
|
|
35
35
|
| xAxis | boolean | true for columns; false for horizontal bars | The horizontal axis. |
|
|
36
36
|
| yAxis | boolean | false for columns; true for horizontal bars | The vertical axis. |
|
|
37
|
-
|
|
|
37
|
+
| 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 arrival (bars rising in sequence) and the data-change morph are the flourish; the hover dim and gliding cursor are the feedback `subtle` keeps. |
|
|
38
|
+
| animate | boolean | true | Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. `animate={false}` is `motion="subtle"`. |
|
|
38
39
|
| activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
|
|
39
40
|
| defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
|
|
40
41
|
| onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
|
|
@@ -52,9 +53,9 @@ Use BarChart to compare amounts across categories — the default chart for "how
|
|
|
52
53
|
|
|
53
54
|
## Motion
|
|
54
55
|
|
|
55
|
-
- **arrival** · Bar · _custom (outside the recipe layer)_ — Once on screen, bars rise from the baseline in sequence on the emphasized easing (scale from the zero line) and their labels fade in. On the motion tokens; reduced motion shows the finished chart.
|
|
56
|
-
- **response** · Cursor + tooltip · _no motion_ — The hovered category gets a band behind it and the other bars dim at once — a lagging highlight reads as a slow chart.
|
|
57
|
-
- **transition** · Bar · _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.
|
|
56
|
+
- **arrival** · Bar · _custom (outside the recipe layer)_ — Once on screen, bars rise from the baseline in sequence on the emphasized easing (scale from the zero line) and their labels fade 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.
|
|
57
|
+
- **response** · Cursor + tooltip · _no motion_ — The hovered category gets a band behind it and the other bars dim at once — a lagging highlight reads as a slow chart. The dim mixes each bar toward the surface instead of fading it, so gridlines never strike through a dimmed column and stacked segments stay solid. Essential: `subtle` keeps it.
|
|
58
|
+
- **transition** · Bar · _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.
|
|
58
59
|
- **activity** · BarChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
|
|
59
60
|
|
|
60
61
|
## Accessibility
|
|
@@ -34,6 +34,14 @@ Use BottomNav for a small set of top-level mobile destinations that should remai
|
|
|
34
34
|
|
|
35
35
|
`background`, `border`, `muted-foreground`, `foreground`, `ring`
|
|
36
36
|
|
|
37
|
+
## Motion
|
|
38
|
+
|
|
39
|
+
- **arrival** · Root · _no motion_ — Fixed app chrome, present from the first paint of every screen. Animating it in on navigation would make each route change look like the app restarting.
|
|
40
|
+
- **response** · BottomNavItem · `motion-interactive` — The ink steps up on hover and the focus ring eases in, so a pointer or a keyboard on a tablet gets the same answer as every other control in the app.
|
|
41
|
+
- **response** · BottomNavItem · `motion-press` — On a phone there is no hover to say a tap landed, and a route can take a beat to paint. The settle on press is the receipt; without it people tap the tab a second time.
|
|
42
|
+
- **transition** · BottomNavItem · `motion-interactive` — Changing tab eases the active ink out of one item and into the next. It is a cross-fade, not a travelling indicator: each item owns its colour, so there is no single mark to glide.
|
|
43
|
+
- **activity** · Root · _no motion_ — Navigation has no working state of its own. A slow route shows its pending state in the page it is loading, not in the tab bar.
|
|
44
|
+
|
|
37
45
|
## Accessibility
|
|
38
46
|
|
|
39
47
|
- BottomNav renders a <nav> landmark.
|
|
@@ -45,6 +45,13 @@ Use Breadcrumbs to show the user's location inside a hierarchy and provide light
|
|
|
45
45
|
|
|
46
46
|
`muted-foreground`, `foreground`
|
|
47
47
|
|
|
48
|
+
## Motion
|
|
49
|
+
|
|
50
|
+
- **arrival** · Root · _no motion_ — The trail is page furniture, rendered with the page header. An entrance would replay on every navigation, which is exactly when the reader looks at it to get their bearings.
|
|
51
|
+
- **response** · BreadcrumbLink · `motion-interactive` — Ancestors are muted until hovered or focused. Easing the ink makes the trail read as links answering the pointer; a hard snap across a row of small text looks like flicker.
|
|
52
|
+
- **transition** · Root · _no motion_ — A breadcrumb has no state of its own. The current page changes by navigation, which replaces the trail; animating between two trails would imply the path itself moved.
|
|
53
|
+
- **activity** · Root · _no motion_ — It never works in the background. A route that is still loading says so in the page, not in its address.
|
|
54
|
+
|
|
48
55
|
## Accessibility
|
|
49
56
|
|
|
50
57
|
- The <nav> carries aria-label="Breadcrumb" so assistive tech announces the landmark.
|
|
@@ -28,14 +28,16 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
|
|
|
28
28
|
| --- | --- | --- |
|
|
29
29
|
| Bubble | yes | The surface itself. Holds the variant, the side, and the tail. |
|
|
30
30
|
| BubbleReactions | no | The row of reaction pills, just under the bubble at the message column's small gap, so it reads as belonging to that message rather than the next row. |
|
|
31
|
-
| BubbleReaction | no | One reaction pill — emoji, count (hidden at 1), and pressed state. |
|
|
31
|
+
| BubbleReaction | no | One reaction pill — emoji, count (hidden at 1), and pressed state. Celebrates when it becomes the reader's own. |
|
|
32
|
+
| BubbleReactionAdd | no | The add-reaction pill at the end of the row: opens a ReactionPicker above it. |
|
|
32
33
|
|
|
33
34
|
## Subcomponents
|
|
34
35
|
|
|
35
36
|
| Name | Type | Description |
|
|
36
37
|
| --- | --- | --- |
|
|
37
38
|
| BubbleReactions | { align?: "start" \| "end" } & ComponentPropsWithRef<"div"> | Row container for reaction pills. Match `align` to the Bubble so the row hangs off the same edge. |
|
|
38
|
-
| BubbleReaction | { emoji: ReactNode; count?: number; active?: boolean } & ComponentPropsWithRef<"button"> | One reaction pill. A toggle, not a label: `active` drives `aria-pressed` as well as the styling, and `count` is hidden at 1 because the pill already says "one". |
|
|
39
|
+
| BubbleReaction | { emoji: ReactNode; label?: string; count?: number; active?: boolean; motion?: MotionLevel } & ComponentPropsWithRef<"button"> | One reaction pill. A toggle, not a label: `active` drives `aria-pressed` as well as the styling, `label` names it ("Party, 3"), and `count` is hidden at 1 because the pill already says "one". When it turns on — pressed, or newly added already on — the emoji blooms, copies of it burst out and the count rolls; pills the page loads with stay still. |
|
|
40
|
+
| BubbleReactionAdd | { reactions?: Reaction[]; selected?: string[]; onSelect?: (emoji: string) => void; label?: string; motion?: MotionLevel } & ComponentPropsWithRef<"button"> | The add-reaction pill: a smiley that opens a ReactionPicker above it. Choosing an emoji closes the picker and calls `onSelect`; add the emoji to the row and its new pill celebrates on arrival. |
|
|
39
41
|
|
|
40
42
|
## Props
|
|
41
43
|
|
|
@@ -56,16 +58,23 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
|
|
|
56
58
|
|
|
57
59
|
`muted`, `foreground`, `primary`, `primary-foreground`, `border`, `card`, `card-foreground`, `accent`, `accent-foreground`, `muted-foreground`, `ring`
|
|
58
60
|
|
|
61
|
+
## Motion
|
|
62
|
+
|
|
63
|
+
- **arrival** · Bubble · _no motion_ — A bubble arrives with its Message row, which owns how a new message enters the transcript. A reaction pill that the reader just added arrives with its own celebration (below).
|
|
64
|
+
- **response** · BubbleReaction · `motion-bloom` — Reacting is the reader's choice, so it is answered like a like: the emoji grows from nothing past its size and settles, while copies of it spray outward and fade (Burst with the emoji as its particle, the way chat apps answer a reaction). Plays when a pill turns on and when a pill mounts already on into a row that has rendered — never for the pills a page loads with. Pills inherit Pill's motion-interactive and motion-press.
|
|
65
|
+
- **transition** · BubbleReaction · _custom (outside the recipe layer)_ — The count rolls with NumberTicker as people react. Turning a reaction off is quiet: the tint eases out and the count rolls back, with no burst. Under `subtle` or reduced motion the bloom and the spray are dropped and the tint and count alone answer.
|
|
66
|
+
- **activity** · Bubble · _no motion_ — A bubble is static content; a message still being written is StreamingText or a Marker, not a state of the surface.
|
|
67
|
+
|
|
59
68
|
## Accessibility
|
|
60
69
|
|
|
61
70
|
- The surface is a plain container with no imposed role — a bubble is presentation, and announcing one as a region would put a landmark around every line of a conversation.
|
|
62
|
-
- Reaction pills are real buttons reporting `aria-pressed`, so the reader's own reaction is state rather than colour alone
|
|
71
|
+
- Reaction pills are real buttons reporting `aria-pressed`, so the reader's own reaction is state rather than colour alone. Give each a `label`: it is named "Party, 3", with the emoji and its burst hidden from AT. The add-reaction pill is named "Add reaction" and opens a toolbar picker.
|
|
63
72
|
- `whitespace-pre-wrap` and `break-words` keep pasted logs, long URLs, and hard-wrapped text inside the surface instead of overflowing the conversation.
|
|
64
73
|
- Every variant meets AA against its own surface, including `primary`, which pairs the primary fill with its paired foreground token rather than a hardcoded white.
|
|
65
74
|
|
|
66
75
|
## Related
|
|
67
76
|
|
|
68
|
-
- Components: Message, MessageScroller, Marker, Attachment
|
|
77
|
+
- Components: Message, MessageScroller, Marker, Attachment, ReactionPicker
|
|
69
78
|
|
|
70
79
|
## Examples
|
|
71
80
|
|
|
@@ -96,13 +105,14 @@ import { Avatar, Bubble, CodeBlock, Message } from "@sableui/react"
|
|
|
96
105
|
### With reactions
|
|
97
106
|
|
|
98
107
|
```tsx
|
|
99
|
-
import { Bubble, BubbleReaction, BubbleReactions, Message } from "@sableui/react"
|
|
108
|
+
import { Bubble, BubbleReaction, BubbleReactionAdd, BubbleReactions, Message } from "@sableui/react"
|
|
100
109
|
|
|
101
110
|
<Message messageId="m3">
|
|
102
111
|
<Bubble>Shipped 0.45.0.</Bubble>
|
|
103
112
|
<BubbleReactions>
|
|
104
|
-
<BubbleReaction emoji="🎉" count={3} active onClick={() => toggle("🎉")} />
|
|
105
|
-
<BubbleReaction emoji="👀" count={1} onClick={() => toggle("👀")} />
|
|
113
|
+
<BubbleReaction emoji="🎉" label="Party" count={3} active onClick={() => toggle("🎉")} />
|
|
114
|
+
<BubbleReaction emoji="👀" label="Eyes" count={1} onClick={() => toggle("👀")} />
|
|
115
|
+
<BubbleReactionAdd selected={mine} onSelect={toggle} />
|
|
106
116
|
</BubbleReactions>
|
|
107
117
|
</Message>
|
|
108
118
|
```
|
|
@@ -13,7 +13,7 @@ Use Button when the user commits an action, submits a form, opens UI, or confirm
|
|
|
13
13
|
### Best practices
|
|
14
14
|
|
|
15
15
|
- Do: Use a single primary button in a view or dense panel so the action hierarchy stays obvious.
|
|
16
|
-
- Do: For a copy control, pair an icon Button with `useCopy(text, { timeout? })`. It takes the text when the hook is called and returns `{ copy, copied }`; `copy()` takes no argument, so pass the value the button copies to the hook, not to `copy`. Text that costs something to produce (a file to fetch) goes in as a function, `useCopy(() => fetch(url).then((r) => r.text()))`, called only on click.
|
|
16
|
+
- Do: For a copy control, pair an icon Button with `useCopy(text, { timeout? })`. It takes the text when the hook is called and returns `{ copy, copied }`; `copy()` takes no argument, so pass the value the button copies to the hook, not to `copy`. Text that costs something to produce (a file to fetch) goes in as a function, `useCopy(() => fetch(url).then((r) => r.text()))`, called only on click. Keep both glyphs mounted in a `motion-icon-swap` cell so the copy mark visibly becomes a check (a `copied ? "check" : "copy"` swap snaps between frames and is easy to miss), and say "Copied" in a polite `role="status"` region: the glyph change is silent to a screen reader.
|
|
17
17
|
- Do: Write labels as actions, such as "Save changes" or "Delete draft", instead of vague labels like "OK".
|
|
18
18
|
- Do: Use loading with loadingLabel for async work so the action cannot double-fire and the state is announced.
|
|
19
19
|
- Do: Put icons in iconStart / iconEnd rather than as children, so the button applies its optical padding. Start icons say what the action is (plus, share); end icons say where it goes (chevron-right, external).
|
|
@@ -41,10 +41,10 @@ Use Button when the user commits an action, submits a form, opens UI, or confirm
|
|
|
41
41
|
| variant | "primary" \| "secondary" \| "ghost" \| "outline" \| "destructive" \| "link" \| "floating" \| "field" \| "on-color" \| "on-color-outline" \| "on-color-ghost" | "primary" | Visual emphasis tier. One primary per screen. The on-color tiers are for buttons ON a coloured fill: they paint with the inherited text colour (the fill's ink) — on-color is solid (ink background, fill-coloured label via the optional --on-color-fill var, else black/white), on-color-outline and on-color-ghost are transparent with an ink label. Never use them on page or card surfaces. floating is for a control that sits OVER content (carousel arrows, a marquee pause, a jump-to-latest over a conversation): its own edge, lift and translucent popover fill, so it reads on arbitrary content beneath. field is for a button docked in a workspace panel among xs fields: the inset panel-field fill and the field's corner, so a picker trigger or an add action reads as one of the row's controls; pair it with size xs or icon-xs. |
|
|
42
42
|
| size | "xs" \| "sm" \| "md" \| "cta" \| "icon" \| "icon-sm" \| "icon-xs" | "md" | Reads the density ladder, so every value differs by register: page 28/32/40/44, workspace 24/28/32/36. xs and icon-xs were added in 0.31.0 for the workspace control floor and are the sizes tool chrome wants; md is the default everywhere. icon/icon-sm/icon-xs are square. |
|
|
43
43
|
| render | ReactElement \| (props, state) => ReactElement | — | Compose with another element/component through the Base UI render prop. |
|
|
44
|
-
| loading | boolean | false | Show a
|
|
44
|
+
| loading | boolean | false | Show a Spinner while an action is in flight and refuse presses. The spinner takes the iconStart slot when there is one; otherwise it lays over the label, which fades out in place, so the button keeps its width. Sets aria-busy and aria-disabled, never the native disabled: disabling the focused button sent focus to <body>, so a sheet the action opened had nowhere to return it. |
|
|
45
45
|
| onClick | (event: MouseEvent<HTMLButtonElement>) => unknown | — | May return a promise: the button shows `loading` until it settles and ignores further presses meanwhile, from the first click on. Prefer it to a `busy` flag in state, which takes effect a render late (three fast clicks ran three times). |
|
|
46
46
|
| badge | ReactNode | — | A corner indicator (usually <NotificationBadge dot /> or a count) overlaid on the top-end corner of the glyph. For icon-only sizes; never hand-position a badge over a Button with absolute offsets. |
|
|
47
|
-
| loadingLabel | ReactNode | — | Optional text shown in place of children while loading. |
|
|
47
|
+
| loadingLabel | ReactNode | — | Optional text shown in place of children while loading, with the spinner beside it. The width follows the new text, so prefer no loadingLabel where the row must not move. |
|
|
48
48
|
| iconStart | ReactNode | — | Icon before the label. The button takes one 2px step off its start padding so icon + label read as centred. Replaced by the spinner while loading. |
|
|
49
49
|
| iconEnd | ReactNode | — | Icon after the label (disclosure, external link, next). Same optical correction on the end side. |
|
|
50
50
|
| bleed | "start" \| "end" \| "both" \| "none" | — | Optical alignment for a borderless button (ghost, link, on-color-ghost): a negative inline margin of exactly its own padding on that side — --button-px, or the icon side's optical padding when an icon sits there; an icon-only size bleeds by its inset to the glyph — so the label (or glyph) lands on the content edge. Hit area and hover/focus fill keep their full size. Logical sides, so it mirrors in RTL. A no-op on variants with a visible edge (primary, secondary, outline, destructive, floating, field, on-color, on-color-outline): a filled button aligns its edge, not its label. Unset, a borderless button renders data-bleed="auto" and SectionHeader, PageHeader and CardAction bleed it when it is their outermost child; none opts out. |
|
|
@@ -63,7 +63,7 @@ Use Button when the user commits an action, submits a form, opens UI, or confirm
|
|
|
63
63
|
- **arrival** · Root · _no motion_ — A button belongs to the layout it ships in. Whatever staged that region owns the entrance; a button that fades in on its own is a button that is briefly un-clickable for no stated reason.
|
|
64
64
|
- **response** · Root · `motion-interactive` — Hover, focus ring and disabled fade all ease on one declaration. This is the reference case for the whole recipe: it lists scale and translate alongside the colours specifically so motion-press can add a transform without a second, conflicting transition-property.
|
|
65
65
|
- **response** · Root · `motion-press` — The 0.97 settle on pointer-down is the only confirmation the press registered before the action's own result arrives — which on a slow network is the difference between waiting and clicking again. It skips disabled elements, so a dead button stays visibly dead.
|
|
66
|
-
- **transition** ·
|
|
66
|
+
- **transition** · Spinner · `motion-appear` — Going busy is the one state change a button has. The spinner fades in where it mounts — in the icon slot, or laid over a plain label that fades out beneath it and keeps its place — so the width never changes and the row does not shift under the pointer that just pressed it. The label fades back when the work settles. A toggling control is a Switch or a Toggle; those carry their own transition.
|
|
67
67
|
- **activity** · Root · _no motion_ — Pending work is passed in via `loading`, which renders a Spinner — the activity motion lives there rather than being duplicated here.
|
|
68
68
|
|
|
69
69
|
## Accessibility
|
|
@@ -118,10 +118,10 @@ import { Button, Icon, Menu, MenuContent, MenuItem, MenuTrigger, Row } from "@sa
|
|
|
118
118
|
### Copy to clipboard
|
|
119
119
|
|
|
120
120
|
```tsx
|
|
121
|
-
import { Button, Icon, useCopy } from "@sableui/react"
|
|
121
|
+
import { Button, Icon, Row, useCopy } from "@sableui/react"
|
|
122
122
|
|
|
123
123
|
const { copy, copied } = useCopy(address)
|
|
124
|
-
<Button variant="ghost" size="icon-sm" aria-label="Copy address" onClick={copy}><Icon name={copied
|
|
124
|
+
<Row gap={1}><Button variant="ghost" size="icon-sm" aria-label="Copy address" 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>
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
### As link
|
|
@@ -30,6 +30,7 @@ Use Calendar to browse or choose dates in a month grid, especially when relative
|
|
|
30
30
|
| disabled | Matcher \| Matcher[] | — | Days matching this are dimmed and non-selectable (e.g. { before: new Date() }). |
|
|
31
31
|
| showOutsideDays | boolean | true | Render leading/trailing days from adjacent months, dimmed. |
|
|
32
32
|
| bare | boolean | false | Drop the card frame (border, fill, padding) so the grid sits flush inside a surface that already has one, such as a Popover. DatePicker renders its Calendar this way. |
|
|
33
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the month slide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
33
34
|
| className | string | — | Merged onto the bordered card container that wraps the grid. |
|
|
34
35
|
| classNames | Partial<ClassNames> | — | Per-part class overrides merged over the themed defaults. Themed slots: months, month, nav, button_previous, button_next, month_caption, caption_label, month_grid, weekdays, weekday, week, day, day_button, range_start, range_middle, range_end, selected, today, outside, disabled, hidden. Selection/state slots (selected/today/range_*/outside/disabled) style the day CELL; the inner day button is targeted via the [&>button] descendant. |
|
|
35
36
|
| components | Partial<CustomComponents> | — | Render overrides for RDP parts. Chevron is supplied by default and merged with yours. |
|
|
@@ -42,6 +43,14 @@ Use Calendar to browse or choose dates in a month grid, especially when relative
|
|
|
42
43
|
|
|
43
44
|
`card`, `card-foreground`, `border`, `primary`, `primary-foreground`, `accent`, `accent-foreground`, `muted-foreground`, `ring`
|
|
44
45
|
|
|
46
|
+
## Motion
|
|
47
|
+
|
|
48
|
+
- **arrival** · Root · _no motion_ — Inline, it arrives with its form; in a DatePicker the Popover carries the entrance. A grid that animated in by itself would put a second motion on the same opening.
|
|
49
|
+
- **response** · Day · `motion-interactive` — Days ease their hover wash, and the month arrows are ghost Buttons on Button's own recipe. Across a seven-column grid the pointer crosses many cells at once, and an eased wash reads as a cursor travelling rather than a grid flashing.
|
|
50
|
+
- **transition** · Day · `motion-interactive` — Picking a day eases the primary fill onto it and off the previous one, so a click reads as the selection moving rather than two cells flipping. A range's band is painted on the cells, which carry the same recipe, so the band eases along with its endpoints as the range grows.
|
|
51
|
+
- **transition** · Month · _custom (outside the recipe layer)_ — Paging months slides the outgoing grid toward the edge it is leaving by and brings the new one in from the other, over a short travel with a fade (react-day-picker's `animate` mode on Sable keyframes in styles.css), so forward and back read as direction rather than a grid swapping in one frame. RDP adds a single class per part, which a recipe utility cannot be; mirrored in RTL. Expressive: Calendar turns `animate` on only at full motion, and an explicit `animate` prop still wins.
|
|
52
|
+
- **activity** · Root · _no motion_ — Days are resident data; nothing in the grid loads. Availability fetched per month belongs to the surface around the calendar.
|
|
53
|
+
|
|
45
54
|
## Accessibility
|
|
46
55
|
|
|
47
56
|
- react-day-picker manages the roving-tabindex grid, arrow-key navigation, and ARIA roles/labels for the calendar.
|
|
@@ -36,11 +36,20 @@ Use Carousel for a curated, finite sequence of media or cards that benefits from
|
|
|
36
36
|
| label | string | — | Accessible label for the carousel region. |
|
|
37
37
|
| slideClassName | string | — | Class for each slide wrapper (e.g. an aspect ratio). |
|
|
38
38
|
| arrowClassName | string | — | Class for both prev/next arrow buttons. Merged through cn(), so a radius or shadow utility replaces the default rounded-full shadow-md rather than stacking with it. |
|
|
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 glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. Under subtle the slides still travel and the active dot snaps; off also cuts between slides and pauses autoplay. |
|
|
39
40
|
|
|
40
41
|
## Tokens used
|
|
41
42
|
|
|
42
43
|
`border`, `ring`, `popover`, `popover-foreground`, `foreground`, `muted-foreground`
|
|
43
44
|
|
|
45
|
+
## Motion
|
|
46
|
+
|
|
47
|
+
- **arrival** · Root · _no motion_ — A carousel arrives with its section. Its content already moves on every advance; an entrance on top would make the first view feel like an advance nobody asked for.
|
|
48
|
+
- **response** · Dot · `motion-interactive` — The dot's 24px hit area eases its focus ring and the dot brightens under the pointer, which is how a 6px mark tells you it is a button.
|
|
49
|
+
- **transition** · Track · _custom (outside the recipe layer)_ — The slide track's translate is set inline from the measured slide pitch, on --motion-duration-slow and the emphasized easing, and switched off during a drag so the track follows the finger. A recipe cannot carry a measured, drag-interrupted offset. Essential: it is the answer to "next", so it plays under subtle.
|
|
50
|
+
- **transition** · Dot · `motion-glide` — The active dot stretches to a bar while the one it left shrinks, on one timeline, so the row keeps its length and the mark reads as travelling with the slide. It used to change width unanimated and jumped while the slides beside it glided. Expressive: motion="subtle" snaps it.
|
|
51
|
+
- **activity** · Root · _no motion_ — Autoplay's progress is the slides themselves advancing and the play/pause control; a separate timer animation would put a second clock beside the first.
|
|
52
|
+
|
|
44
53
|
## Accessibility
|
|
45
54
|
|
|
46
55
|
- Region carries aria-roledescription='carousel'; each slide is a group with aria-roledescription='slide' and an 'N of M' label; clones are aria-hidden.
|
|
@@ -85,3 +94,11 @@ import { Carousel } from "@sableui/react"
|
|
|
85
94
|
|
|
86
95
|
<Carousel loop autoplay={4000} counter>{slides}</Carousel>
|
|
87
96
|
```
|
|
97
|
+
|
|
98
|
+
### Subtle motion
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { Carousel } from "@sableui/react"
|
|
102
|
+
|
|
103
|
+
<Carousel loop motion="subtle" label="Gallery">{slides}</Carousel>
|
|
104
|
+
```
|
|
@@ -58,6 +58,7 @@ Pairs with MessageScroller to make a working chat. A single surface rather than
|
|
|
58
58
|
| stopLabel | string | "Stop generating" | Accessible label for the stop button. |
|
|
59
59
|
| footer | ReactNode \| ((state: { sendOn: "enter" \| "mod-enter" }) => ReactNode) | — | Helper text or counter below the controls. For a keyboard hint pass a function: it receives the send key in effect (touch turns `enter` into `mod-enter`), and returning null removes the row, e.g. `footer={({ sendOn }) => sendOn === "enter" ? "Enter to send" : null}`. |
|
|
60
60
|
| fieldRef | Ref<HTMLTextAreaElement> | — | Ref to the underlying textarea — for focusing after a send, or on mount, and the extension point for anything that listens to the field. Pass `useCaretTrigger`'s `fieldRef` here for mentions and slash commands (CaretMenu). An Enter another listener already handled (`preventDefault()`) does not send. |
|
|
61
|
+
| 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 field snaps to its new height and the send/stop glyphs swap without the morph. |
|
|
61
62
|
| ...form props | Omit<ComponentPropsWithRef<"form">, "onSubmit" \| "defaultValue"> | — | Spread onto the form element. Use `onSend` rather than `onSubmit`. |
|
|
62
63
|
|
|
63
64
|
## Variants
|
|
@@ -68,6 +69,14 @@ Pairs with MessageScroller to make a working chat. A single surface rather than
|
|
|
68
69
|
|
|
69
70
|
`card`, `input`, `ring`, `destructive`, `foreground`, `muted-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`
|
|
70
71
|
|
|
72
|
+
## Motion
|
|
73
|
+
|
|
74
|
+
- **arrival** · Root · _no motion_ — The composer is the fixed foot of a thread, there before anything else is. An entrance would only delay the first keystroke.
|
|
75
|
+
- **response** · Root · `motion-interactive` — The surface's focus ring and invalid border ease in, so focusing the field reads as the whole box waking up rather than a frame flicker.
|
|
76
|
+
- **transition** · Field · _custom (outside the recipe layer)_ — As a line breaks or a paste lands, the field eases to its new height (up to its cap) instead of jumping, so the transcript above is visibly pushed rather than teleported. `field-sizing` alone cannot ease — its height stays `auto` — so a small hook measures and pins a pixel height. Expressive and short (`fast`): the text itself lands at once, and `subtle` snaps the box.
|
|
77
|
+
- **transition** · Send button · `motion-icon-swap` — Send turning into stop is the moment a reply starts, and stop turning back is the moment it is over. One button changes role in place — the fill eases, the arrow collapses into the square — so it reads as the same control changing what it does, not a different button appearing under the cursor.
|
|
78
|
+
- **activity** · Send button · `motion-spin` — Busy with no stop handler, Button's own loading Spinner replaces the arrow, so a send in flight is not mistaken for a disabled one.
|
|
79
|
+
|
|
71
80
|
## Accessibility
|
|
72
81
|
|
|
73
82
|
- A real `<form>`, so the send button is a submit button and the whole thing behaves as one control to assistive tech and to password managers alike.
|
|
@@ -43,6 +43,7 @@ Use CheckboxCard when an option needs more visual weight, supporting text, or ic
|
|
|
43
43
|
| tone | "neutral" \| "3" \| "2" \| "1" \| "info" \| "warning" | "neutral" | Semantic colour the checked state carries across the border, checkbox box, and icon. |
|
|
44
44
|
| value | string | — | Names the card inside a group-owned CheckboxGroup: the card is checked when the group's value array includes it. Also the value submitted with a form. Inside a CheckboxGroup, `name` stands in for `value` when `value` is omitted. |
|
|
45
45
|
| checked / onCheckedChange / defaultChecked / disabled | Base UI Checkbox.Root props | — | Standard checkbox state. Without a group-owned value, each card is independent — drive checked/onCheckedChange per item for multi-select. |
|
|
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. Under `subtle` the tick fades in instead of drawing itself. |
|
|
46
47
|
|
|
47
48
|
## Variants
|
|
48
49
|
|
|
@@ -52,6 +53,13 @@ Use CheckboxCard when an option needs more visual weight, supporting text, or ic
|
|
|
52
53
|
|
|
53
54
|
`input`, `card`, `ring`, `primary`, `primary-foreground`, `accent`, `foreground`, `muted-foreground`, `tone-3`, `tone-3-foreground`, `tone-2`, `tone-2-foreground`, `tone-1`, `tone-1-foreground`, `tone-info`, `tone-info-foreground`, `tone-warning`, `tone-warning-foreground`
|
|
54
55
|
|
|
56
|
+
## Motion
|
|
57
|
+
|
|
58
|
+
- **arrival** · Root · _no motion_ — It arrives with the form it belongs to. Animating each box in turns a settings list into a title sequence.
|
|
59
|
+
- **response** · Box · `motion-interactive` — The box fills with the primary colour as it is checked; easing the fill keeps the mark and the box changing as one event rather than two frames apart.
|
|
60
|
+
- **transition** · Mark · `motion-tick` — The check draws itself in from its leading stroke, so checking reads as the box being ticked rather than a glyph appearing. The dash wipes the same way. Unchecking only fades, so clearing a list is quick; under `subtle` checking fades too.
|
|
61
|
+
- **activity** · Root · _no motion_ — A checkbox commits instantly. A slow write belongs to the surrounding form's pending state, not the control.
|
|
62
|
+
|
|
55
63
|
## Accessibility
|
|
56
64
|
|
|
57
65
|
- Base UI provides the checkbox role, aria-checked, hidden native input, keyboard toggle, and data-[checked]/data-disabled state.
|
|
@@ -31,11 +31,19 @@ Use Checkbox for independent boolean choices or for selecting multiple options i
|
|
|
31
31
|
| name | string | — | Field name for the hidden input on form submit. |
|
|
32
32
|
| value | string | — | Value submitted with the form when checked. Inside a group-owned CheckboxGroup, it is the name the group's value array uses for this checkbox. |
|
|
33
33
|
| render | ReactElement \| (props, state) => ReactElement | — | Compose the root with another element through the Base UI render prop. |
|
|
34
|
+
| 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 tick fades in instead of drawing itself. |
|
|
34
35
|
|
|
35
36
|
## Tokens used
|
|
36
37
|
|
|
37
38
|
`card`, `input`, `primary`, `primary-foreground`, `ring`
|
|
38
39
|
|
|
40
|
+
## Motion
|
|
41
|
+
|
|
42
|
+
- **arrival** · Root · _no motion_ — It arrives with the form it belongs to. Animating each box in turns a settings list into a title sequence.
|
|
43
|
+
- **response** · Box · `motion-interactive` — The box fills with the primary colour as it is checked; easing the fill keeps the mark and the box changing as one event rather than two frames apart.
|
|
44
|
+
- **transition** · Mark · `motion-tick` — The check draws itself in from its leading stroke, so checking reads as the box being ticked rather than a glyph appearing. The dash wipes the same way. Unchecking only fades, so clearing a list is quick; under `subtle` checking fades too.
|
|
45
|
+
- **activity** · Root · _no motion_ — A checkbox commits instantly. A slow write belongs to the surrounding form's pending state, not the control.
|
|
46
|
+
|
|
39
47
|
## Accessibility
|
|
40
48
|
|
|
41
49
|
- Base UI renders a hidden native <input type=checkbox> for keyboard, focus, and form semantics.
|
|
@@ -45,10 +45,18 @@ Use CodeBlock for multiline code samples that users may read, inspect, or copy.
|
|
|
45
45
|
|
|
46
46
|
`border`, `muted`, `muted-foreground`, `foreground`
|
|
47
47
|
|
|
48
|
+
## Motion
|
|
49
|
+
|
|
50
|
+
- **arrival** · Root · _no motion_ — Code is reading content and arrives with the page or message it belongs to. An entrance of its own would delay the text someone came to copy.
|
|
51
|
+
- **response** · Copy button · `motion-interactive` — The copy button is a quiet ghost icon in the corner of a block. Its hover fill is what tells the pointer it is a control and not decoration.
|
|
52
|
+
- **transition** · Copy glyph · `motion-icon-swap` — The copy mark turning into a check is the only proof the clipboard took the text. Snapped between frames it was easy to miss at the edge of a block; the swap reads as one mark being replaced.
|
|
53
|
+
- **activity** · Root · _no motion_ — Copying is instant. There is no in-between worth showing, and a spinner for a clipboard write would read as a slow one.
|
|
54
|
+
|
|
48
55
|
## Accessibility
|
|
49
56
|
|
|
50
57
|
- Code renders inside a <pre><code> for correct semantics and screen-reader handling.
|
|
51
58
|
- Copy is the ghost icon Button with `useCopy`, which guards clipboard access for SSR safety.
|
|
59
|
+
- A successful copy is announced as "Copied" through a polite role=status region that is always mounted; the button keeps its label, which names what it does. Both glyphs are aria-hidden.
|
|
52
60
|
|
|
53
61
|
## Related
|
|
54
62
|
|
|
@@ -45,6 +45,14 @@ Use Collapsible for a single piece of optional content that can expand in place.
|
|
|
45
45
|
|
|
46
46
|
`foreground`, `muted-foreground`
|
|
47
47
|
|
|
48
|
+
## Motion
|
|
49
|
+
|
|
50
|
+
- **arrival** · Root · _no motion_ — A collapsible is part of the page it sits in, present from first paint. Its trigger is what the reader notices, not an entrance.
|
|
51
|
+
- **response** · CollapsibleTrigger · _no motion_ — The trigger is whatever the consumer renders (usually a Button), and that control brings its own response. Adding one here would double it.
|
|
52
|
+
- **transition** · CollapsiblePanel · `motion-disclosure` — The height change ties the content that appears to the trigger that was pressed; snapped open, everything below jumps with nothing linking cause to effect.
|
|
53
|
+
- **transition** · CollapsibleIndicator · `motion-interactive` — The chevron turns with the panel rather than flipping in one frame: motion-interactive lists `rotate`, the property rotate-180 writes.
|
|
54
|
+
- **activity** · Root · _no motion_ — Nothing here is indeterminate; content that loads on open should put its own loader inside the panel.
|
|
55
|
+
|
|
48
56
|
## Accessibility
|
|
49
57
|
|
|
50
58
|
- Base UI wires aria-expanded on the trigger and aria-controls/id between trigger and panel.
|
|
@@ -42,6 +42,13 @@ Use ColorField wherever a colour is a property: a canvas background, an effect p
|
|
|
42
42
|
|
|
43
43
|
`panel`, `panel-raised`, `hairline`, `hairline-strong`, `ring`, `popover`
|
|
44
44
|
|
|
45
|
+
## Motion
|
|
46
|
+
|
|
47
|
+
- **arrival** · Popover · `motion-pop` — The picker grows out of the swatch it edits (PopoverContent's recipe, its origin on the anchor). In a property panel with a column of swatches, that origin is what tells you which colour you are now editing.
|
|
48
|
+
- **response** · Swatch · `motion-interactive` — Preset swatches lift slightly under the pointer. They are 20px chips of colour with no label, and the eased lift is what tells you which one a click will take.
|
|
49
|
+
- **transition** · Handles · _no motion_ — The saturation, hue and alpha handles follow the pointer exactly. A handle that trailed the drag would misreport the colour under the cursor, and a swatch or a typed hex moving them is the value changing, which the field should show at once.
|
|
50
|
+
- **activity** · Root · _no motion_ — Picking is synchronous. The EyeDropper is the browser's own modal, with its own feedback.
|
|
51
|
+
|
|
45
52
|
## Accessibility
|
|
46
53
|
|
|
47
54
|
- The trigger's accessible name includes the current value, so a screen reader announces the colour rather than just "button" — the swatch itself conveys nothing without sight.
|
|
@@ -64,6 +64,16 @@ Use Combobox when users choose from a list that benefits from typing, filtering,
|
|
|
64
64
|
|
|
65
65
|
`input`, `card`, `foreground`, `muted-foreground`, `ring`, `destructive`, `popover`, `popover-foreground`, `border`, `accent`, `accent-foreground`, `primary`, `secondary`, `secondary-foreground`
|
|
66
66
|
|
|
67
|
+
## Motion
|
|
68
|
+
|
|
69
|
+
- **arrival** · ComboboxContent · `motion-pop` — The list grows from the field it filters, its origin on Base UI's anchor. With several comboboxes in a form, that is what attaches the open list to the one being typed in.
|
|
70
|
+
- **response** · ComboboxItem · `motion-interactive` — A row's ink eases as the pointer or the arrow keys reach it, so moving through a long filtered list reads as continuous movement rather than rows blinking.
|
|
71
|
+
- **response** · Highlight · _custom (outside the recipe layer)_ — One highlight travels to the active row (lib/highlight-glide: a measured translate on the motion tokens) instead of each row painting its own fill. When the list re-filters under the caret, the highlight sliding to the new match is what shows the keyboard target moved.
|
|
72
|
+
- **response** · Toggle · `motion-interactive` — The chevron toggle at the field's edge eases its ink on hover; the change is what tells you it is a separate target from the text box.
|
|
73
|
+
- **transition** · Toggle · `motion-interactive` — The chevron turns 180° as the list opens and closes (motion-interactive lists `rotate`), in step with the popup, so it reads as one control changing direction.
|
|
74
|
+
- **transition** · ComboboxContent · `motion-pop` — Closing shrinks back toward the field, returning the eye to the input that now holds the value.
|
|
75
|
+
- **activity** · Root · _no motion_ — Combobox ships no loading part. A list fetched as you type shows a Spinner (motion-spin) in ComboboxEmpty or the content — where the results will land — and only the caller knows a fetch is in flight.
|
|
76
|
+
|
|
67
77
|
## Accessibility
|
|
68
78
|
|
|
69
79
|
- Base UI provides role=combobox/listbox/option, roving tabindex, arrow-key navigation, and type-to-filter.
|
|
@@ -70,6 +70,15 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
70
70
|
|
|
71
71
|
`card`, `card-foreground`, `border`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`
|
|
72
72
|
|
|
73
|
+
## Motion
|
|
74
|
+
|
|
75
|
+
- **arrival** · Dialog · `motion-pop` — The palette opens on DialogContent's recipe over a fading backdrop. It is summoned by a keystroke from nowhere on screen, and the short scale-in is what says a layer arrived rather than the page changing. The inline CommandMenuPanel has no entrance: it is part of the page.
|
|
76
|
+
- **arrival** · Clear button · `motion-appear` — The clear button mounts on the first keystroke, beside the caret the eye is already on. Fading in, it reads as the field offering a way back instead of a control blinking into the header. Removal is instant, so an emptied query does not wait on its X.
|
|
77
|
+
- **response** · CommandItem · `motion-interactive` — The active row follows both the arrow keys and the pointer, and its fill eases between rows, so scanning results while typing reads as one highlight moving rather than rows flashing in turn.
|
|
78
|
+
- **response** · CommandMenuTrigger · `motion-interactive` — The search-shaped trigger eases its ink on hover like the field shell it imitates, so it reads as the field it opens.
|
|
79
|
+
- **transition** · Dialog · `motion-pop` — Closing on Enter or Esc shrinks the palette away fast and returns focus where it came from; the exit is the confirmation that the command ran or was dismissed.
|
|
80
|
+
- **activity** · List · _no motion_ — Results are filtered synchronously from the items passed in. Async results are the caller's to mark with a Spinner (motion-spin) in the list, where the person is already looking.
|
|
81
|
+
|
|
73
82
|
## Accessibility
|
|
74
83
|
|
|
75
84
|
- CommandMenuTrigger is a real <button> named by its visible placeholder, with aria-haspopup=dialog and aria-keyshortcuts (Meta+K on macOS, Control+K elsewhere). Its Kbd hint is aria-hidden, so the shortcut is announced once rather than read as part of the name.
|