@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
|
@@ -122,6 +122,44 @@ var radioCardMeta = {
|
|
|
122
122
|
"tone-info",
|
|
123
123
|
"tone-warning"
|
|
124
124
|
],
|
|
125
|
+
motion: [
|
|
126
|
+
{
|
|
127
|
+
slot: "arrival",
|
|
128
|
+
recipe: "none",
|
|
129
|
+
part: "Root",
|
|
130
|
+
why: "Cards arrive with the form. A plan picker that animated each card in would delay the choice it exists to make."
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
slot: "response",
|
|
134
|
+
recipe: "motion-interactive",
|
|
135
|
+
part: "Root",
|
|
136
|
+
why: "The edge steps toward the ring colour on hover, eased. On a card-sized target a hard snap across the whole border reads as the layout flickering."
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
slot: "response",
|
|
140
|
+
recipe: "motion-press",
|
|
141
|
+
part: "Root",
|
|
142
|
+
why: "The settle on press confirms a click anywhere in the card \u2014 the radio is small, and without it a click in the card's body can feel like it missed the control."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
slot: "transition",
|
|
146
|
+
recipe: "motion-interactive",
|
|
147
|
+
part: "Root",
|
|
148
|
+
why: "Checking eases the tone ring and wash onto the new card as the old one releases, so the choice reads as moving between cards rather than two cards repainting."
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
slot: "transition",
|
|
152
|
+
recipe: "motion-interactive",
|
|
153
|
+
part: "Indicator",
|
|
154
|
+
why: "The radio dot grows in on a spring, the same mark as Radio, so a card and a plain radio confirm a choice the same way."
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
slot: "activity",
|
|
158
|
+
recipe: "none",
|
|
159
|
+
part: "Root",
|
|
160
|
+
why: "A choice commits instantly. A slow write belongs to the surrounding form's pending state."
|
|
161
|
+
}
|
|
162
|
+
],
|
|
125
163
|
a11y: [
|
|
126
164
|
"Base UI provides the radiogroup/radio roles, aria-checked, hidden native inputs, and roving tabindex.",
|
|
127
165
|
"Arrow keys move and select between cards; Space selects the focused card; Tab enters/leaves the group.",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/components/reaction-picker/index.ts
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_REACTIONS,
|
|
4
|
+
MenuReactions,
|
|
5
|
+
ReactionPicker
|
|
6
|
+
} from "./reaction-picker.js";
|
|
7
|
+
import { reactionPickerMeta } from "./reaction-picker.meta.js";
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_REACTIONS,
|
|
10
|
+
MenuReactions,
|
|
11
|
+
ReactionPicker,
|
|
12
|
+
reactionPickerMeta
|
|
13
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/reaction-picker/reaction-picker.tsx
|
|
4
|
+
import { Menu as BaseMenu } from "@base-ui/react/menu";
|
|
5
|
+
import { cn } from "../../lib/cn.js";
|
|
6
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
7
|
+
import { Toolbar, ToolbarButton } from "../toolbar/toolbar.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var DEFAULT_REACTIONS = [
|
|
10
|
+
{ emoji: "\u{1F44D}", label: "Thumbs up" },
|
|
11
|
+
{ emoji: "\u2764\uFE0F", label: "Heart" },
|
|
12
|
+
{ emoji: "\u{1F602}", label: "Laughing" },
|
|
13
|
+
{ emoji: "\u{1F389}", label: "Party" },
|
|
14
|
+
{ emoji: "\u{1F62E}", label: "Surprised" },
|
|
15
|
+
{ emoji: "\u{1F622}", label: "Sad" },
|
|
16
|
+
{ emoji: "\u{1F64F}", label: "Thank you" }
|
|
17
|
+
];
|
|
18
|
+
var PRESSED = "aria-pressed:bg-primary/10 data-[checked]:bg-primary/10";
|
|
19
|
+
var MENU_CELL = `group/reaction grid size-(--control-md) shrink-0 cursor-pointer select-none place-items-center rounded-full outline-none sable-surface-interactive motion-interactive motion-press ${PRESSED}`;
|
|
20
|
+
function Glyph({ emoji, index }) {
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
"span",
|
|
23
|
+
{
|
|
24
|
+
"aria-hidden": true,
|
|
25
|
+
className: "motion-bloom",
|
|
26
|
+
style: { "--motion-bloom-delay": `${index * 24}ms` },
|
|
27
|
+
children: /* @__PURE__ */ jsx("span", { className: "block text-[calc(var(--control-md)*0.6)] leading-none transition-[scale] duration-(--motion-duration-fast) ease-standard group-hover/reaction:scale-125 group-focus-visible/reaction:scale-125 group-data-[highlighted]/reaction:scale-125", children: emoji })
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function ReactionPicker({
|
|
32
|
+
reactions = DEFAULT_REACTIONS,
|
|
33
|
+
selected = [],
|
|
34
|
+
onSelect,
|
|
35
|
+
motion,
|
|
36
|
+
className,
|
|
37
|
+
children,
|
|
38
|
+
"aria-label": ariaLabel = "Reactions",
|
|
39
|
+
...props
|
|
40
|
+
}) {
|
|
41
|
+
return /* @__PURE__ */ jsxs(
|
|
42
|
+
Toolbar,
|
|
43
|
+
{
|
|
44
|
+
"aria-label": ariaLabel,
|
|
45
|
+
"data-motion": motionAttribute(motion),
|
|
46
|
+
className: cn("gap-0.5", className),
|
|
47
|
+
...props,
|
|
48
|
+
children: [
|
|
49
|
+
reactions.map((reaction, index) => /* @__PURE__ */ jsx(
|
|
50
|
+
ToolbarButton,
|
|
51
|
+
{
|
|
52
|
+
size: "icon",
|
|
53
|
+
"aria-label": reaction.label,
|
|
54
|
+
"aria-pressed": selected.includes(reaction.emoji),
|
|
55
|
+
onClick: () => onSelect?.(reaction.emoji),
|
|
56
|
+
className: cn("group/reaction rounded-full", PRESSED),
|
|
57
|
+
children: /* @__PURE__ */ jsx(Glyph, { emoji: reaction.emoji, index })
|
|
58
|
+
},
|
|
59
|
+
reaction.emoji
|
|
60
|
+
)),
|
|
61
|
+
children
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
ReactionPicker.displayName = "ReactionPicker";
|
|
67
|
+
function MenuReactions({
|
|
68
|
+
reactions = DEFAULT_REACTIONS,
|
|
69
|
+
selected = [],
|
|
70
|
+
onSelect,
|
|
71
|
+
motion,
|
|
72
|
+
className,
|
|
73
|
+
"aria-label": ariaLabel = "Reactions",
|
|
74
|
+
...props
|
|
75
|
+
}) {
|
|
76
|
+
return /* @__PURE__ */ jsx(
|
|
77
|
+
BaseMenu.Group,
|
|
78
|
+
{
|
|
79
|
+
"aria-label": ariaLabel,
|
|
80
|
+
"data-motion": motionAttribute(motion),
|
|
81
|
+
className: cn("flex items-center gap-0.5", className),
|
|
82
|
+
...props,
|
|
83
|
+
children: reactions.map((reaction, index) => /* @__PURE__ */ jsx(
|
|
84
|
+
BaseMenu.CheckboxItem,
|
|
85
|
+
{
|
|
86
|
+
label: reaction.label,
|
|
87
|
+
"aria-label": reaction.label,
|
|
88
|
+
checked: selected.includes(reaction.emoji),
|
|
89
|
+
onCheckedChange: () => onSelect?.(reaction.emoji),
|
|
90
|
+
closeOnClick: true,
|
|
91
|
+
className: MENU_CELL,
|
|
92
|
+
children: /* @__PURE__ */ jsx(Glyph, { emoji: reaction.emoji, index })
|
|
93
|
+
},
|
|
94
|
+
reaction.emoji
|
|
95
|
+
))
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
MenuReactions.displayName = "MenuReactions";
|
|
100
|
+
export {
|
|
101
|
+
DEFAULT_REACTIONS,
|
|
102
|
+
MenuReactions,
|
|
103
|
+
ReactionPicker
|
|
104
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/components/reaction-picker/reaction-picker.meta.ts
|
|
2
|
+
var reactionPickerMeta = {
|
|
3
|
+
name: "ReactionPicker",
|
|
4
|
+
summary: "The quick row of emoji you react to a message with: a toolbar of large emoji toggles that bloom in one after another as it opens and magnify under the pointer, with MenuReactions as the same row inside a right-click menu.",
|
|
5
|
+
status: "stable",
|
|
6
|
+
keywords: [
|
|
7
|
+
"reaction",
|
|
8
|
+
"reactions",
|
|
9
|
+
"emoji",
|
|
10
|
+
"emoji picker",
|
|
11
|
+
"react",
|
|
12
|
+
"tapback",
|
|
13
|
+
"quick reactions",
|
|
14
|
+
"message actions",
|
|
15
|
+
"chat"
|
|
16
|
+
],
|
|
17
|
+
usage: {
|
|
18
|
+
description: "Use ReactionPicker wherever a reader chooses a reaction for something: in a Popover from an add-reaction control (BubbleReactionAdd is that, ready-made), or as a hover bar over a message. Use MenuReactions for the same row as the first group of the menu a message opens on right-click or long-press, above Reply and Copy. Both report which emoji the reader has already used, so choosing one again removes it.",
|
|
19
|
+
bestPractices: [
|
|
20
|
+
{
|
|
21
|
+
guidance: "do",
|
|
22
|
+
description: 'Keep the quick set short \u2014 six to eight. It is the reactions people actually use; a full emoji catalogue belongs behind a trailing "more" control passed as children.'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
guidance: "do",
|
|
26
|
+
description: "Give every reaction a `label`. It is the accessible name and the menu's typeahead, and an emoji's own spoken name differs between platforms."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
guidance: "do",
|
|
30
|
+
description: "Pass `selected` so the reader's own reactions show pressed, and toggle in `onSelect`: choosing a reaction you already gave takes it back."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
guidance: "do",
|
|
34
|
+
description: "Offer the right-click menu and a visible add-reaction control together. A context menu is invisible until you know it is there; the pill is how a first-time reader finds reactions."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
guidance: "dont",
|
|
38
|
+
description: "Do not put a ReactionPicker inside a Menu or ContextMenu. A toolbar is not a menu item, so the menu's arrow keys would skip it; use MenuReactions there.",
|
|
39
|
+
instead: ["MenuReactions"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
guidance: "dont",
|
|
43
|
+
description: "Do not use it to rate an answer or collect a verdict. Reactions are social and many-valued; a helpful / not-helpful question is Feedback.",
|
|
44
|
+
instead: ["Feedback"]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
anatomy: [
|
|
48
|
+
{
|
|
49
|
+
name: "ReactionPicker",
|
|
50
|
+
required: true,
|
|
51
|
+
description: "The toolbar: one tab stop, arrow keys move between emoji, each a toggle with `aria-pressed`."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "MenuReactions",
|
|
55
|
+
required: false,
|
|
56
|
+
description: "The same row as a group of checkbox menu items, for MenuContent and ContextMenuContent. Choosing one closes the menu."
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
related: {
|
|
61
|
+
components: ["Bubble", "ContextMenu", "Popover", "Toolbar", "Feedback"]
|
|
62
|
+
},
|
|
63
|
+
props: [
|
|
64
|
+
{
|
|
65
|
+
name: "reactions",
|
|
66
|
+
type: "readonly { emoji: string; label: string }[]",
|
|
67
|
+
default: "DEFAULT_REACTIONS",
|
|
68
|
+
description: "The choices, in order. The default is \u{1F44D} \u2764\uFE0F \u{1F602} \u{1F389} \u{1F62E} \u{1F622} \u{1F64F}."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "selected",
|
|
72
|
+
type: "readonly string[]",
|
|
73
|
+
default: "[]",
|
|
74
|
+
description: "The emoji the reader has already reacted with: shown pressed, and choosing one again removes it."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "onSelect",
|
|
78
|
+
type: "(emoji: string) => void",
|
|
79
|
+
description: "Called with the emoji the reader chose. Toggle it in or out of `selected`."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "children",
|
|
83
|
+
type: "ReactNode",
|
|
84
|
+
description: "ReactionPicker only: trailing controls after the emoji, such as a button that opens a full emoji picker."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "aria-label",
|
|
88
|
+
type: "string",
|
|
89
|
+
default: '"Reactions"',
|
|
90
|
+
description: "The name of the toolbar or menu group."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "motion",
|
|
94
|
+
type: '"full" | "subtle" | "off"',
|
|
95
|
+
default: '"full"',
|
|
96
|
+
description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps the press and drops the wave of emoji blooming in and the magnify, `off` stills it. It can only turn motion down."
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
subcomponents: [
|
|
100
|
+
{
|
|
101
|
+
name: "MenuReactions",
|
|
102
|
+
type: "{ reactions?; selected?; onSelect?; motion? } & ComponentProps<typeof Menu.Group>",
|
|
103
|
+
description: "The quick row as checkbox menu items, for the first group of a MenuContent or ContextMenuContent. Same props as ReactionPicker, without children."
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
tokensUsed: ["accent", "primary", "ring"],
|
|
107
|
+
motion: [
|
|
108
|
+
{
|
|
109
|
+
slot: "arrival",
|
|
110
|
+
recipe: "motion-bloom",
|
|
111
|
+
part: "Glyph",
|
|
112
|
+
why: "Opening the picker is the reader's own action, so the emoji answer it: each blooms in 24ms after the one before, a short wave from first to last. The surface it sits in (a Popover or a menu) arrives with its own motion-pop. Expressive only \u2014 under `subtle` the emoji are simply there."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
slot: "response",
|
|
116
|
+
recipe: "custom",
|
|
117
|
+
part: "Glyph",
|
|
118
|
+
why: "The emoji under the pointer or the keyboard highlight magnifies to 1.25 on a fast standard ease, so you can see which one a release will choose; each cell also carries motion-interactive and motion-press. The magnify rides an inner layer so it never fights the bloom over `scale`."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
slot: "transition",
|
|
122
|
+
recipe: "motion-interactive",
|
|
123
|
+
part: "Cell",
|
|
124
|
+
why: "Pressed and not pressed differ by a tint, eased by motion-interactive. The celebration belongs to the reaction that lands under the message (BubbleReaction), not to the picker that chose it."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
slot: "activity",
|
|
128
|
+
recipe: "none",
|
|
129
|
+
part: "Root",
|
|
130
|
+
why: "Choosing is instant; there is no in-between state to show."
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
a11y: [
|
|
134
|
+
"ReactionPicker is a Base UI toolbar: one tab stop, Left/Right move between emoji, Home/End jump to the ends.",
|
|
135
|
+
"Each emoji is a button named by its `label` and reporting `aria-pressed`, so a screen-reader user hears whether choosing it adds or removes their reaction. The glyph itself is aria-hidden.",
|
|
136
|
+
"MenuReactions renders checkbox menu items (`menuitemcheckbox` with `aria-checked`), reached by the menu's arrow keys and typeahead like every other row.",
|
|
137
|
+
"Under reduced motion the emoji appear at once and do not magnify; pressed state is carried by `aria-pressed` and a tint, never motion."
|
|
138
|
+
],
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
label: "In a popover",
|
|
142
|
+
code: '<Popover>\n <PopoverTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Add reaction" />}>\n <Icon name="emoji" />\n </PopoverTrigger>\n <PopoverContent side="top" className="w-auto rounded-full p-1">\n <ReactionPicker selected={mine} onSelect={toggle} />\n </PopoverContent>\n</Popover>'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: "In a right-click menu",
|
|
146
|
+
code: "<ContextMenu>\n <ContextMenuTrigger render={<Bubble />}>See you at 6.</ContextMenuTrigger>\n <ContextMenuContent>\n <MenuReactions selected={mine} onSelect={toggle} />\n <ContextMenuSeparator />\n <ContextMenuItem>Reply</ContextMenuItem>\n <ContextMenuItem>Copy text</ContextMenuItem>\n </ContextMenuContent>\n</ContextMenu>"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: "A custom set",
|
|
150
|
+
code: '<ReactionPicker\n reactions={[\n { emoji: "\u2705", label: "Done" },\n { emoji: "\u{1F440}", label: "Looking" },\n { emoji: "\u{1F680}", label: "Shipped" },\n ]}\n selected={mine}\n onSelect={toggle}\n/>'
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
export {
|
|
155
|
+
reactionPickerMeta
|
|
156
|
+
};
|
|
@@ -54,6 +54,38 @@ var relativeTimeMeta = {
|
|
|
54
54
|
}
|
|
55
55
|
],
|
|
56
56
|
tokensUsed: ["popover", "popover-foreground", "border", "ring"],
|
|
57
|
+
motion: [
|
|
58
|
+
{
|
|
59
|
+
slot: "arrival",
|
|
60
|
+
recipe: "none",
|
|
61
|
+
part: "Root",
|
|
62
|
+
why: "It is a word in a line of text and arrives with that line."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
slot: "response",
|
|
66
|
+
recipe: "none",
|
|
67
|
+
part: "Root",
|
|
68
|
+
why: "The only hover change on the timestamp is a dotted underline, and text-decoration-line is discrete: CSS cannot tween it, so a transition here would animate nothing and imply the snap had been handled."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
slot: "response",
|
|
72
|
+
recipe: "motion-pop",
|
|
73
|
+
part: "Tooltip",
|
|
74
|
+
why: "The pointer's real answer is the absolute time, which arrives in a Tooltip on its own fast pop. That short entrance is what makes the full date read as belonging to the word under the cursor."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
slot: "transition",
|
|
78
|
+
recipe: "none",
|
|
79
|
+
part: "Root",
|
|
80
|
+
why: "The label does not tick: it is computed when the component renders, so it never changes in front of the reader. A timestamp rolling from one minute to two would pull the eye off the content it dates."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
slot: "activity",
|
|
84
|
+
recipe: "none",
|
|
85
|
+
part: "Root",
|
|
86
|
+
why: "A timestamp has nothing to wait for."
|
|
87
|
+
}
|
|
88
|
+
],
|
|
57
89
|
a11y: [
|
|
58
90
|
"Renders a semantic <time> element with a machine-readable dateTime (ISO) attribute.",
|
|
59
91
|
"The <time> carries tabIndex=0 (it is not focusable natively), so the absolute time \u2014 exposed via Base UI's Tooltip, which opens on keyboard focus as well as hover and wires aria-describedby \u2014 is reachable from the keyboard.",
|
|
@@ -92,6 +92,38 @@ var scrollerMeta = {
|
|
|
92
92
|
wheelScroll: ["true", "false"]
|
|
93
93
|
},
|
|
94
94
|
tokensUsed: ["ring"],
|
|
95
|
+
motion: [
|
|
96
|
+
{
|
|
97
|
+
slot: "arrival",
|
|
98
|
+
recipe: "none",
|
|
99
|
+
part: "Root",
|
|
100
|
+
why: "It is a region, not an event; its content arrives with whatever fills it."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
slot: "response",
|
|
104
|
+
recipe: "motion-interactive",
|
|
105
|
+
part: "Viewport",
|
|
106
|
+
why: "The region is focusable so the keyboard can scroll it, and its focus ring eases in like any control's. It is the one sign the region, not the page, now takes the arrow keys."
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
slot: "response",
|
|
110
|
+
recipe: "custom",
|
|
111
|
+
part: "Scrollbar",
|
|
112
|
+
why: "The vertical bar (`scrollbar-quiet`) stays hidden until the region is hovered, focused or scrolled, then fades its thumb in on the motion tokens. A bar that appeared in one frame would flash every time the pointer crossed the list."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
slot: "transition",
|
|
116
|
+
recipe: "none",
|
|
117
|
+
part: "Edge fade",
|
|
118
|
+
why: "The edge fades switch on and off as you scroll, in the frame the content is already moving, so the change is carried by the scroll itself. They are a mask gradient, which CSS cannot interpolate without registered custom properties."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
slot: "activity",
|
|
122
|
+
recipe: "none",
|
|
123
|
+
part: "Root",
|
|
124
|
+
why: "A scroll region does no work of its own. Content still loading inside it brings its own Skeleton or Spinner."
|
|
125
|
+
}
|
|
126
|
+
],
|
|
95
127
|
a11y: [
|
|
96
128
|
"The edge fades are a CSS mask-image on the scrolling element, not overlay elements \u2014 there is nothing in the DOM to intercept a pointer or be read by assistive tech, and the content under a fade stays fully interactive.",
|
|
97
129
|
'Two boxes: a chrome box carrying `className` (border, background, radius, sizing) and, inside it, the element that actually scrolls and carries the fade mask. They are split because a `mask-image` applies to its element\'s own border \u2014 so with both on one box, a bordered Scroller watched its frame dissolve at the scrolling edge. The axis is published as `data-orientation`, not `aria-orientation`, which is invalid on `role="group"`.',
|
|
@@ -99,6 +99,38 @@ var scrubFieldMeta = {
|
|
|
99
99
|
{ name: "disabled", type: "boolean", description: "Disables typing and scrubbing." },
|
|
100
100
|
{ name: "id", type: "string", description: "Input id, for a PropertyRow's htmlFor." }
|
|
101
101
|
],
|
|
102
|
+
motion: [
|
|
103
|
+
{
|
|
104
|
+
slot: "arrival",
|
|
105
|
+
recipe: "none",
|
|
106
|
+
part: "Root",
|
|
107
|
+
why: "It arrives with the inspector row it sits in."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
slot: "response",
|
|
111
|
+
recipe: "motion-interactive",
|
|
112
|
+
part: "Root",
|
|
113
|
+
why: "The shell eases its focus ring like every field, so tabbing through a column of numbers shows where the keyboard is without flicker."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
slot: "response",
|
|
117
|
+
recipe: "motion-interactive",
|
|
118
|
+
part: "Handle",
|
|
119
|
+
why: "The handle's ink brightens while the field is hovered. It is the only sign that the icon or prefix can be dragged, which nobody would guess from a label."
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
slot: "transition",
|
|
123
|
+
recipe: "none",
|
|
124
|
+
part: "Value",
|
|
125
|
+
why: "The number follows the drag pixel for pixel. Easing it \u2014 or rolling it with a NumberTicker \u2014 would show a value the pointer has already left, in a control whose whole point is precision."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
slot: "activity",
|
|
129
|
+
recipe: "none",
|
|
130
|
+
part: "Root",
|
|
131
|
+
why: "Scrubbing is synchronous; there is nothing to wait for."
|
|
132
|
+
}
|
|
133
|
+
],
|
|
102
134
|
a11y: [
|
|
103
135
|
"The input carries the accessible name via `label`; the glyph is aria-hidden because it is decorative.",
|
|
104
136
|
"Every scrub gesture is reachable from the keyboard: ArrowUp/ArrowDown step by `step`, Shift+Arrow by ten steps.",
|
|
@@ -66,21 +66,23 @@ function SearchInput({
|
|
|
66
66
|
}
|
|
67
67
|
),
|
|
68
68
|
hasValue && !props.disabled ? (
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
69
|
+
// The wrapper carries the arrival, the button keeps its own response.
|
|
70
|
+
// motion-appear declares a transition list of its own, and on the
|
|
71
|
+
// Button it would fight motion-interactive for the one
|
|
72
|
+
// transition-property slot — whichever sorted last would win, and
|
|
73
|
+
// either the hover ease or the arrival would silently go. Removal
|
|
74
|
+
// stays instant: an emptied field should not wait on its X.
|
|
75
|
+
/* @__PURE__ */ jsx("span", { className: "absolute end-2 flex motion-appear", children: /* @__PURE__ */ jsx(
|
|
74
76
|
Button,
|
|
75
77
|
{
|
|
76
78
|
variant: "ghost",
|
|
77
79
|
size: "icon-xs",
|
|
78
80
|
onClick: handleClear,
|
|
79
81
|
"aria-label": "Clear search",
|
|
80
|
-
className: "
|
|
82
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
81
83
|
children: /* @__PURE__ */ jsx(Icon, { name: "xmark" })
|
|
82
84
|
}
|
|
83
|
-
)
|
|
85
|
+
) })
|
|
84
86
|
) : null
|
|
85
87
|
] })
|
|
86
88
|
);
|
|
@@ -98,6 +98,32 @@ var searchInputMeta = {
|
|
|
98
98
|
"ring",
|
|
99
99
|
"destructive"
|
|
100
100
|
],
|
|
101
|
+
motion: [
|
|
102
|
+
{
|
|
103
|
+
slot: "arrival",
|
|
104
|
+
recipe: "motion-appear",
|
|
105
|
+
part: "Clear button",
|
|
106
|
+
why: "The X mounts as the first character lands. Fading and settling in from a smaller scale keeps it from popping into the corner of the eye on every new search; removal is instant so an emptied field does not wait on it."
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
slot: "response",
|
|
110
|
+
recipe: "motion-interactive",
|
|
111
|
+
part: "Input",
|
|
112
|
+
why: "The focus ring and invalid border ease in, so focus reads as the field answering rather than a repaint."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
slot: "transition",
|
|
116
|
+
recipe: "none",
|
|
117
|
+
part: "Root",
|
|
118
|
+
why: "The field has no states of its own to move between \u2014 the clear button arriving is its only change, and arrival covers it."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
slot: "activity",
|
|
122
|
+
recipe: "none",
|
|
123
|
+
part: "Root",
|
|
124
|
+
why: "Searching is the results' job: a list skeleton or a Spinner beside them, not a field that animates while you type."
|
|
125
|
+
}
|
|
126
|
+
],
|
|
101
127
|
a11y: [
|
|
102
128
|
'Renders a native <input type="search"> \u2014 keyboard and form semantics come for free.',
|
|
103
129
|
"Associate a <label htmlFor> (or wrap) \u2014 the magnifier glyph is decorative, not a label.",
|
|
@@ -70,10 +70,7 @@ function ShowMore({
|
|
|
70
70
|
Icon,
|
|
71
71
|
{
|
|
72
72
|
name: "chevron-down",
|
|
73
|
-
className: cn(
|
|
74
|
-
"transition-transform duration-(--motion-duration-fast) ease-standard",
|
|
75
|
-
expanded && "rotate-180"
|
|
76
|
-
)
|
|
73
|
+
className: cn("motion-interactive", expanded && "rotate-180")
|
|
77
74
|
}
|
|
78
75
|
),
|
|
79
76
|
children: expanded ? "Show less" : "Show more"
|
|
@@ -58,6 +58,44 @@ var showMoreMeta = {
|
|
|
58
58
|
}
|
|
59
59
|
],
|
|
60
60
|
tokensUsed: ["background", "foreground", "ring"],
|
|
61
|
+
motion: [
|
|
62
|
+
{
|
|
63
|
+
slot: "arrival",
|
|
64
|
+
recipe: "none",
|
|
65
|
+
part: "Root",
|
|
66
|
+
why: "The preview arrives with the content it clips. Its fade and toggle appear only once a measurement proves the content overflows."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
slot: "response",
|
|
70
|
+
recipe: "motion-interactive",
|
|
71
|
+
part: "Toggle",
|
|
72
|
+
why: "The toggle is a link Button, so its hover and focus ease on Button's recipe."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
slot: "transition",
|
|
76
|
+
recipe: "custom",
|
|
77
|
+
part: "Content",
|
|
78
|
+
why: "The region eases its max-height between the collapsed and the measured full height on the slow motion token. motion-disclosure keys on Base UI's starting and ending styles, which this self-contained toggle does not have. The text unrolling in place keeps the reader's line; a snap drops them at a different scroll position."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
slot: "transition",
|
|
82
|
+
recipe: "motion-interactive",
|
|
83
|
+
part: "Fade",
|
|
84
|
+
why: "The bottom fade dissolves as the region opens and returns as it closes, so the cut edge and the reveal read as one movement."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
slot: "transition",
|
|
88
|
+
recipe: "motion-interactive",
|
|
89
|
+
part: "Chevron",
|
|
90
|
+
why: "The chevron turns 180\xB0 in step with the region (motion-interactive lists `rotate`), so the toggle visibly points at what it will do next."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
slot: "activity",
|
|
94
|
+
recipe: "none",
|
|
95
|
+
part: "Root",
|
|
96
|
+
why: "Expanding is local and instant; nothing loads."
|
|
97
|
+
}
|
|
98
|
+
],
|
|
61
99
|
a11y: [
|
|
62
100
|
"The toggle is a real <button> with aria-expanded reflecting the open state.",
|
|
63
101
|
"The fade overlay is pointer-events-none and decorative; it never traps clicks.",
|
|
@@ -4,19 +4,39 @@
|
|
|
4
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
5
5
|
import { createContext, useContext } from "react";
|
|
6
6
|
import { cn } from "../../lib/cn.js";
|
|
7
|
+
import { HighlightGlide } from "../../lib/highlight-glide.js";
|
|
8
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
7
9
|
import { sideNavItemIconVariants, sideNavItemVariants } from "./side-nav.variants.js";
|
|
8
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
11
|
var SideNavContext = createContext({
|
|
10
12
|
size: "md",
|
|
11
13
|
activeVariant: "content"
|
|
12
14
|
});
|
|
15
|
+
var GLIDE_HANDOFF = "[&[data-glide]_[data-active]]:bg-transparent";
|
|
16
|
+
var GLIDE_FILL = {
|
|
17
|
+
content: "rounded-sm bg-accent motion-glide",
|
|
18
|
+
sidebar: "rounded-sm bg-sidebar-accent motion-glide"
|
|
19
|
+
};
|
|
13
20
|
function SideNav({
|
|
14
21
|
className,
|
|
15
22
|
size = "md",
|
|
16
23
|
activeVariant = "content",
|
|
24
|
+
motion,
|
|
25
|
+
children,
|
|
17
26
|
...props
|
|
18
27
|
}) {
|
|
19
|
-
return /* @__PURE__ */ jsx(SideNavContext.Provider, { value: { size, activeVariant }, children: /* @__PURE__ */
|
|
28
|
+
return /* @__PURE__ */ jsx(SideNavContext.Provider, { value: { size, activeVariant }, children: /* @__PURE__ */ jsxs(
|
|
29
|
+
"nav",
|
|
30
|
+
{
|
|
31
|
+
"data-motion": motionAttribute(motion),
|
|
32
|
+
className: cn("flex flex-col gap-0.5", GLIDE_HANDOFF, className),
|
|
33
|
+
...props,
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsx(HighlightGlide, { follow: "data-active", timing: "selection", className: GLIDE_FILL[activeVariant] }),
|
|
36
|
+
children
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
) });
|
|
20
40
|
}
|
|
21
41
|
SideNav.displayName = "SideNav";
|
|
22
42
|
function SideNavGroup({
|
|
@@ -80,6 +80,12 @@ var sideNavMeta = {
|
|
|
80
80
|
type: '"content" | "sidebar"',
|
|
81
81
|
default: '"content"',
|
|
82
82
|
description: '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.'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "motion",
|
|
86
|
+
type: '"full" | "subtle" | "off"',
|
|
87
|
+
default: '"full"',
|
|
88
|
+
description: "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 \u2014 it never overrides a reader's reduce-motion setting."
|
|
83
89
|
}
|
|
84
90
|
],
|
|
85
91
|
subcomponents: [
|
|
@@ -94,6 +100,32 @@ var sideNavMeta = {
|
|
|
94
100
|
description: "A labelled section within a SideNav - groups settings into areas. label renders an optional sentence-case caption heading above the group's items."
|
|
95
101
|
}
|
|
96
102
|
],
|
|
103
|
+
motion: [
|
|
104
|
+
{
|
|
105
|
+
slot: "arrival",
|
|
106
|
+
recipe: "none",
|
|
107
|
+
part: "Root",
|
|
108
|
+
why: "In-page navigation arrives with the page it navigates."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
slot: "response",
|
|
112
|
+
recipe: "motion-interactive",
|
|
113
|
+
part: "SideNavItem",
|
|
114
|
+
why: "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."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
slot: "transition",
|
|
118
|
+
recipe: "motion-glide",
|
|
119
|
+
part: "SideNav",
|
|
120
|
+
why: "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 \u2014 font weight does not ease. Expressive: `subtle` moves it at once."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
slot: "activity",
|
|
124
|
+
recipe: "none",
|
|
125
|
+
part: "Root",
|
|
126
|
+
why: "Navigation chrome has no working state; a loading section shows it in the content."
|
|
127
|
+
}
|
|
128
|
+
],
|
|
97
129
|
a11y: [
|
|
98
130
|
"SideNav renders a <nav> landmark; pair multiple navs with aria-label to distinguish them.",
|
|
99
131
|
'The active item carries aria-current="page".',
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
3
|
var RAIL_ROW_INLINE = "gap-3 px-3";
|
|
4
4
|
var sideNavItemVariants = cva(
|
|
5
|
-
|
|
5
|
+
// `relative`: a row paints above SideNav's gliding current-page fill, which is
|
|
6
|
+
// absolutely positioned and earlier in the DOM.
|
|
7
|
+
"relative flex w-full items-center text-start motion-interactive focus-visible:outline-none focus-visible:ring-2 data-[active]:font-semibold",
|
|
6
8
|
{
|
|
7
9
|
variants: {
|
|
8
10
|
size: {
|