@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
|
@@ -34,6 +34,10 @@ var otpFieldMeta = {
|
|
|
34
34
|
guidance: "dont",
|
|
35
35
|
description: 'Do not use OtpField for a password, an arbitrary-length PIN, or anything the user types from memory. Slots promise a fixed length; use Input with `type="password"` and `autocomplete="current-password"` instead.'
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
guidance: "do",
|
|
39
|
+
description: "Clear the error as soon as the user edits the code (`onValueChange` \u2192 reset the error), and set it again when the next code is refused. Each refusal is then a fresh change to invalid, so the field shakes for every wrong code, not only the first."
|
|
40
|
+
},
|
|
37
41
|
{
|
|
38
42
|
guidance: "dont",
|
|
39
43
|
description: "Do not intercept paste or clear the field on a wrong code before the user has seen the error. Blocking paste breaks SMS autofill and password managers, and wiping the slots erases the evidence of which digit was wrong."
|
|
@@ -123,7 +127,13 @@ var otpFieldMeta = {
|
|
|
123
127
|
{
|
|
124
128
|
name: "aria-invalid",
|
|
125
129
|
type: "boolean",
|
|
126
|
-
description: "Marks every slot invalid (destructive border). `<Field error>` sets it for you."
|
|
130
|
+
description: "Marks every slot invalid (destructive border and digits). Turning it on after mount shakes the field once. `<Field error>` sets it for you."
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "motion",
|
|
134
|
+
type: '"full" | "subtle" | "off"',
|
|
135
|
+
default: '"full"',
|
|
136
|
+
description: "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 \u2014 it never overrides a reader's reduce-motion setting. The shake is the flourish; the destructive colour stays at every level."
|
|
127
137
|
},
|
|
128
138
|
{
|
|
129
139
|
name: "disabled / readOnly / required / name / id / form",
|
|
@@ -160,6 +170,12 @@ var otpFieldMeta = {
|
|
|
160
170
|
part: "Slot",
|
|
161
171
|
why: "A filled slot strengthens its border, so a pasted code visibly lands in every slot at once. Easing it reads as the code arriving rather than the field flickering."
|
|
162
172
|
},
|
|
173
|
+
{
|
|
174
|
+
slot: "transition",
|
|
175
|
+
recipe: "motion-shake",
|
|
176
|
+
part: "Root",
|
|
177
|
+
why: "A refused code shakes the field once as it turns invalid \u2014 the short horizontal no that makes a second wrong code feel noticed rather than ignored. It never carries the refusal alone: the digits and borders turn destructive and the Field says why. It plays on the change to invalid, not on mount, and is expressive, so `subtle` keeps only the colour."
|
|
178
|
+
},
|
|
163
179
|
{
|
|
164
180
|
slot: "activity",
|
|
165
181
|
recipe: "none",
|
|
@@ -171,7 +187,8 @@ var otpFieldMeta = {
|
|
|
171
187
|
'The root is a role=group named by the field label; the first slot shares the label through `id`/`htmlFor`, and later slots are announced by position ("Character 2 of 6").',
|
|
172
188
|
"Only one slot is in the tab order. Arrow keys, Home/End and Backspace move between slots; Tab leaves the whole field.",
|
|
173
189
|
'The first slot carries autocomplete="one-time-code" and the numeric virtual keyboard, so iOS and Android offer the SMS code above the keyboard.',
|
|
174
|
-
"Slots render at 20px (`text-mono-lg`), above the 16px floor that stops iOS Safari zooming on focus."
|
|
190
|
+
"Slots render at 20px (`text-mono-lg`), above the 16px floor that stops iOS Safari zooming on focus.",
|
|
191
|
+
"An invalid code is never signalled by motion alone: every slot carries aria-invalid, the digits and borders turn destructive, and the Field's error message is wired through aria-describedby. The shake resolves instantly under reduced motion and `subtle`."
|
|
175
192
|
],
|
|
176
193
|
examples: [
|
|
177
194
|
{
|
|
@@ -186,6 +203,10 @@ var otpFieldMeta = {
|
|
|
186
203
|
label: "With an error",
|
|
187
204
|
code: '<Field label="Verification code" error="That code has expired. Request a new code and try again."><OtpField length={6} groupSize={3} defaultValue="482913" /></Field>'
|
|
188
205
|
},
|
|
206
|
+
{
|
|
207
|
+
label: "Verify on completion, shake on a wrong code",
|
|
208
|
+
code: 'const [code, setCode] = useState("")\nconst [wrong, setWrong] = useState(false)\n<Field label="Verification code" error={wrong ? "That code does not match. Check the message and try again." : undefined}><OtpField length={6} groupSize={3} value={code} onValueChange={(next) => { setCode(next); setWrong(false) }} onValueComplete={(value) => setWrong(value !== expected)} /></Field>'
|
|
209
|
+
},
|
|
189
210
|
{
|
|
190
211
|
label: "Alphanumeric",
|
|
191
212
|
code: '<Field label="Pairing code"><OtpField length={8} groupSize={4} validationType="alphanumeric" /></Field>'
|
|
@@ -83,6 +83,32 @@ var panelGroupMeta = {
|
|
|
83
83
|
description: "Controlled width for a Panel, clamped and optionally persisted to localStorage (only when `storageKey` is passed). Read through useSyncExternalStore, so there is no server/client hydration mismatch and no setState in an effect; do not hand-roll a localStorage read during render or a write from an effect. Without `storageKey` the width is kept in memory per hook instance. The third element is the `{ min, max }` it clamps to: spread it onto the paired PanelResizer so the separator's aria-valuemin/max and Home/End match the real clamp."
|
|
84
84
|
}
|
|
85
85
|
],
|
|
86
|
+
motion: [
|
|
87
|
+
{
|
|
88
|
+
slot: "arrival",
|
|
89
|
+
recipe: "none",
|
|
90
|
+
part: "Root",
|
|
91
|
+
why: "Panels are the app's frame, present from first paint. A dock that slid in on load would make the workspace look like it is still assembling."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
slot: "response",
|
|
95
|
+
recipe: "motion-interactive",
|
|
96
|
+
part: "PanelResizer",
|
|
97
|
+
why: "The hairline widens into a visible bar on hover and turns primary on keyboard focus, eased, so a 1px edge says it can be grabbed before the pointer commits."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
slot: "transition",
|
|
101
|
+
recipe: "none",
|
|
102
|
+
part: "Panel",
|
|
103
|
+
why: "Width tracks the pointer one-to-one while dragging, and keyboard steps land at once. An eased width would trail the cursor, and resizing is a layout change: easing it would reflow the canvas beside the panel on every frame."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
slot: "activity",
|
|
107
|
+
recipe: "none",
|
|
108
|
+
part: "Root",
|
|
109
|
+
why: "Layout chrome has no working state."
|
|
110
|
+
}
|
|
111
|
+
],
|
|
86
112
|
a11y: [
|
|
87
113
|
"PanelResizer is a role=separator with aria-orientation, aria-valuenow/min/max, and is focusable \u2014 a pointer-only resizer is a keyboard trap.",
|
|
88
114
|
"Arrow keys resize by `step`; Home and End jump to the minimum and maximum.",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/components/panel-section/panel-section.tsx
|
|
4
|
+
import { useState } from "react";
|
|
4
5
|
import { Icon } from "../../icon/index.js";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
7
|
import { beginScrub } from "../../lib/scrub-gesture.js";
|
|
@@ -110,16 +111,42 @@ function PropertyPair({ labels, className, children, ...props }) {
|
|
|
110
111
|
] });
|
|
111
112
|
}
|
|
112
113
|
PropertyPair.displayName = "PropertyPair";
|
|
113
|
-
function PropertyGroup({
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
function PropertyGroup({
|
|
115
|
+
label,
|
|
116
|
+
summary,
|
|
117
|
+
className,
|
|
118
|
+
children,
|
|
119
|
+
open: openProp,
|
|
120
|
+
defaultOpen = false,
|
|
121
|
+
onOpenChange,
|
|
122
|
+
...props
|
|
123
|
+
}) {
|
|
124
|
+
const [openState, setOpenState] = useState(defaultOpen);
|
|
125
|
+
const open = openProp ?? openState;
|
|
126
|
+
return /* @__PURE__ */ jsxs(
|
|
127
|
+
Collapsible,
|
|
128
|
+
{
|
|
129
|
+
className: cn("border-t border-hairline", className),
|
|
130
|
+
open: openProp,
|
|
131
|
+
defaultOpen,
|
|
132
|
+
onOpenChange: (next, details) => {
|
|
133
|
+
setOpenState(next);
|
|
134
|
+
onOpenChange?.(next, details);
|
|
135
|
+
},
|
|
136
|
+
...props,
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "flex h-(--control-sm) w-full items-center gap-2 rounded-xs text-start motion-interactive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [
|
|
139
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-ui-md text-foreground", children: label }),
|
|
140
|
+
summary != null ? /* @__PURE__ */ jsx("span", { className: "flex min-w-0 shrink items-center gap-2 truncate text-ui-md text-muted-foreground", children: summary }) : null,
|
|
141
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "motion-icon-swap shrink-0 text-xs text-muted-foreground", children: [
|
|
142
|
+
/* @__PURE__ */ jsx("span", { "data-active": !open, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "plus" }) }),
|
|
143
|
+
/* @__PURE__ */ jsx("span", { "data-active": open, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "minus" }) })
|
|
144
|
+
] })
|
|
145
|
+
] }),
|
|
146
|
+
/* @__PURE__ */ jsx(CollapsiblePanel, { children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5 pt-0.5 pb-2", children }) })
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
);
|
|
123
150
|
}
|
|
124
151
|
PropertyGroup.displayName = "PropertyGroup";
|
|
125
152
|
export {
|
|
@@ -101,6 +101,38 @@ var panelSectionMeta = {
|
|
|
101
101
|
description: "A collapsible set of properties inside a section, at the panel's scale (an Accordion is page furniture). The row shows the label, then the summary (the group's value, readable while closed: a word, a few swatches, or both), then a + that turns to \u2212 when open. Groups open independently and stack with a hairline between them. Built on Collapsible."
|
|
102
102
|
}
|
|
103
103
|
],
|
|
104
|
+
motion: [
|
|
105
|
+
{
|
|
106
|
+
slot: "arrival",
|
|
107
|
+
recipe: "none",
|
|
108
|
+
part: "Root",
|
|
109
|
+
why: "Sections are part of the inspector they sit in and arrive with it. Animating each in would make a panel switch look like a page load."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
slot: "response",
|
|
113
|
+
recipe: "motion-interactive",
|
|
114
|
+
part: "PropertyGroup trigger",
|
|
115
|
+
why: "The fold row eases its focus ring, so arrowing down a dense inspector shows where the keyboard is without the ring flickering from row to row."
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
slot: "transition",
|
|
119
|
+
recipe: "motion-disclosure",
|
|
120
|
+
part: "PropertyGroup panel",
|
|
121
|
+
why: "The group opens by easing its height, so the rows below slide down to make room instead of jumping. In an inspector, the next section moving is otherwise how you lose your place."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
slot: "transition",
|
|
125
|
+
recipe: "motion-icon-swap",
|
|
126
|
+
part: "PropertyGroup indicator",
|
|
127
|
+
why: "The + collapses into \u2212 as the group opens, both glyphs stacked as one mark, so the indicator reads as this row changing state rather than a different icon landing."
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
slot: "activity",
|
|
131
|
+
recipe: "none",
|
|
132
|
+
part: "Root",
|
|
133
|
+
why: "The inspector edits values in place; nothing in it loads."
|
|
134
|
+
}
|
|
135
|
+
],
|
|
104
136
|
a11y: [
|
|
105
137
|
"PropertyGroup's row is a real button with aria-expanded (Collapsible); its summary is part of the button's name, so a closed group still announces its value.",
|
|
106
138
|
"PropertyRow renders a real <label>; pass htmlFor with the control's id so clicking the label focuses it.",
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/components/pie-chart/pie-chart.tsx
|
|
4
4
|
import { useId } from "react";
|
|
5
|
-
import {
|
|
5
|
+
import { ChartCenter } from "../../lib/chart/chart-center.js";
|
|
6
|
+
import { ChartFrame, chartMotion } from "../../lib/chart/chart-frame.js";
|
|
6
7
|
import { ChartLegend } from "../../lib/chart/chart-legend.js";
|
|
7
8
|
import { ChartTooltipContent } from "../../lib/chart/chart-tooltip.js";
|
|
8
|
-
import {
|
|
9
|
+
import { FILL_HOVER, dimmed } from "../../lib/chart/dim.js";
|
|
10
|
+
import { categoryText, chartFormatters } from "../../lib/chart/format.js";
|
|
9
11
|
import { arcPath, polar } from "../../lib/chart/geometry.js";
|
|
10
12
|
import { DRAW_DURATION, morphTransition, pathStyle } from "../../lib/chart/motion.js";
|
|
11
13
|
import {
|
|
@@ -30,7 +32,8 @@ function PieChart({
|
|
|
30
32
|
tooltip = true,
|
|
31
33
|
valueFormatter,
|
|
32
34
|
locale,
|
|
33
|
-
|
|
35
|
+
motion,
|
|
36
|
+
animate,
|
|
34
37
|
activeIndex,
|
|
35
38
|
defaultActiveIndex,
|
|
36
39
|
onActiveIndexChange
|
|
@@ -52,6 +55,7 @@ function PieChart({
|
|
|
52
55
|
});
|
|
53
56
|
});
|
|
54
57
|
const tooltipOptions = typeof tooltip === "object" ? tooltip : {};
|
|
58
|
+
const level = chartMotion(motion, animate);
|
|
55
59
|
const ratio = donut === true ? 0.6 : typeof donut === "number" ? Math.min(0.9, Math.max(0, donut)) : 0;
|
|
56
60
|
const geometry = (size) => {
|
|
57
61
|
const margin = labels === "outside" ? 30 : 8;
|
|
@@ -105,7 +109,7 @@ function PieChart({
|
|
|
105
109
|
className: cn("h-64", className),
|
|
106
110
|
count: data.length,
|
|
107
111
|
interactive: tooltip !== false,
|
|
108
|
-
|
|
112
|
+
motion: level,
|
|
109
113
|
activeIndex,
|
|
110
114
|
defaultActiveIndex,
|
|
111
115
|
onActiveIndexChange,
|
|
@@ -117,6 +121,10 @@ function PieChart({
|
|
|
117
121
|
rows: data.map((_, i) => [styles[i].label, ...keys.map((_2, r) => fmt.full(rings[r][i].v))])
|
|
118
122
|
},
|
|
119
123
|
legend: legend ?? labels === "none" ? /* @__PURE__ */ jsx(ChartLegend, { items: names.map((n, i) => ({ key: n, label: styles[i].label, color: styles[i].color, icon: styles[i].icon })) }) : null,
|
|
124
|
+
overlay: centerLabel && ratio > 0 ? ({ size }) => {
|
|
125
|
+
const g = geometry(size);
|
|
126
|
+
return /* @__PURE__ */ jsx(ChartCenter, { center: centerLabel, x: g.cx, y: g.cy, locale, motion: level });
|
|
127
|
+
} : void 0,
|
|
120
128
|
children: ({ size, activeIndex: active }) => {
|
|
121
129
|
const g = geometry(size);
|
|
122
130
|
const maskId = `${uid}-sweep`;
|
|
@@ -148,8 +156,13 @@ function PieChart({
|
|
|
148
156
|
d,
|
|
149
157
|
strokeWidth: separator ? 2 : 0,
|
|
150
158
|
strokeLinejoin: "round",
|
|
151
|
-
className: cn(separator && "stroke-(--sable-surface-bg,var(--background))"
|
|
152
|
-
style: {
|
|
159
|
+
className: cn(separator && "stroke-(--sable-surface-bg,var(--background))"),
|
|
160
|
+
style: {
|
|
161
|
+
...pathStyle(d),
|
|
162
|
+
// Stepped back toward the surface rather than faded (see lib/chart/dim).
|
|
163
|
+
fill: active != null && active !== i ? dimmed(styles[i].color, 60) : styles[i].color,
|
|
164
|
+
transition: `${morphTransition()}, ${FILL_HOVER}`
|
|
165
|
+
}
|
|
153
166
|
},
|
|
154
167
|
`${r}-${names[i]}`
|
|
155
168
|
);
|
|
@@ -175,11 +188,7 @@ function PieChart({
|
|
|
175
188
|
},
|
|
176
189
|
`l-${names[i]}`
|
|
177
190
|
);
|
|
178
|
-
}) : null
|
|
179
|
-
centerLabel && g.inner > 0 ? /* @__PURE__ */ jsxs("text", { x: g.cx, y: g.cy, textAnchor: "middle", className: "[[data-reveal=out]_&]:opacity-0", style: { transition: morphTransition() }, children: [
|
|
180
|
-
/* @__PURE__ */ jsx("tspan", { x: g.cx, dy: "0.35em", className: "fill-foreground text-heading-2 tabular-nums", children: centerValueText(centerLabel.value, locale) }),
|
|
181
|
-
centerLabel.label ? /* @__PURE__ */ jsx("tspan", { x: g.cx, dy: "1.9em", className: "fill-muted-foreground text-caption", children: centerLabel.label }) : null
|
|
182
|
-
] }) : null
|
|
191
|
+
}) : null
|
|
183
192
|
] });
|
|
184
193
|
}
|
|
185
194
|
}
|
|
@@ -127,11 +127,18 @@ var pieChartMeta = {
|
|
|
127
127
|
default: "runtime locale",
|
|
128
128
|
description: "BCP 47 locale for default number and date formatting."
|
|
129
129
|
},
|
|
130
|
+
{
|
|
131
|
+
name: "motion",
|
|
132
|
+
type: '"full" | "subtle" | "off"',
|
|
133
|
+
default: '"full"',
|
|
134
|
+
description: "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 \u2014 it never overrides a reader's reduce-motion setting. The sweep, the slice morph and the rolling centre number are the flourish; the hover pull-out and dim are the feedback `subtle` keeps."
|
|
135
|
+
},
|
|
130
136
|
{
|
|
131
137
|
name: "animate",
|
|
132
138
|
type: "boolean",
|
|
133
139
|
default: "true",
|
|
134
|
-
description:
|
|
140
|
+
description: 'Sweep the slices in clockwise when the chart scrolls into view. `animate={false}` is `motion="subtle"`.',
|
|
141
|
+
deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
|
|
135
142
|
},
|
|
136
143
|
{
|
|
137
144
|
name: "activeIndex",
|
|
@@ -168,19 +175,25 @@ var pieChartMeta = {
|
|
|
168
175
|
slot: "arrival",
|
|
169
176
|
recipe: "custom",
|
|
170
177
|
part: "Slices",
|
|
171
|
-
why:
|
|
178
|
+
why: 'Once on screen the slices are revealed by a mask that sweeps clockwise from 12 o\'clock \u2014 the whole is read as it fills. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
|
|
172
179
|
},
|
|
173
180
|
{
|
|
174
181
|
slot: "response",
|
|
175
182
|
recipe: "none",
|
|
176
183
|
part: "Active slice",
|
|
177
|
-
why: "The hovered slice pulls outward and the rest dim at once; the geometry change itself eases (see transition)."
|
|
184
|
+
why: "The hovered slice pulls outward and the rest dim at once (mixed toward the surface, not faded); the geometry change itself eases (see transition). Essential: `subtle` keeps it."
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
slot: "transition",
|
|
188
|
+
recipe: "custom",
|
|
189
|
+
part: "Centre value",
|
|
190
|
+
why: "A numeric centre value rolls to its new total digit by digit (NumberTicker) when the data changes, so the headline is seen moving with the slices instead of being found different afterwards. Expressive: under `subtle` it updates in place."
|
|
178
191
|
},
|
|
179
192
|
{
|
|
180
193
|
slot: "transition",
|
|
181
194
|
recipe: "custom",
|
|
182
195
|
part: "Slices",
|
|
183
|
-
why: "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."
|
|
196
|
+
why: "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."
|
|
184
197
|
},
|
|
185
198
|
{
|
|
186
199
|
slot: "activity",
|
|
@@ -98,6 +98,38 @@ var pillMeta = {
|
|
|
98
98
|
"muted-foreground",
|
|
99
99
|
"foreground"
|
|
100
100
|
],
|
|
101
|
+
motion: [
|
|
102
|
+
{
|
|
103
|
+
slot: "arrival",
|
|
104
|
+
recipe: "none",
|
|
105
|
+
part: "Root",
|
|
106
|
+
why: "Pills arrive with the filter row or the content they annotate. A chip that animated in alone would read as a filter someone just added."
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
slot: "response",
|
|
110
|
+
recipe: "motion-interactive",
|
|
111
|
+
part: "Root",
|
|
112
|
+
why: "The hover wash eases, so sweeping the pointer along a filter row reads as one cursor moving rather than chips flashing in turn."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
slot: "response",
|
|
116
|
+
recipe: "motion-press",
|
|
117
|
+
part: "Root",
|
|
118
|
+
why: "A pill often toggles a filter that takes a moment to reach the results; the settle on press confirms the click landed before they change."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
slot: "transition",
|
|
122
|
+
recipe: "motion-interactive",
|
|
123
|
+
part: "Root",
|
|
124
|
+
why: "`selected` lifts the chip onto its selected surface on the same ease, so toggling reads as this chip changing state, not a different chip appearing in its place."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
slot: "activity",
|
|
128
|
+
recipe: "none",
|
|
129
|
+
part: "Root",
|
|
130
|
+
why: "A chip is a choice, not a process. If applying it is slow, the results show it."
|
|
131
|
+
}
|
|
132
|
+
],
|
|
101
133
|
a11y: [
|
|
102
134
|
'Renders a native <button type="button"> for focus and keyboard activation.',
|
|
103
135
|
"Selected state is exposed via aria-pressed."
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/components/radar-chart/radar-chart.tsx
|
|
4
|
-
import { ChartFrame } from "../../lib/chart/chart-frame.js";
|
|
4
|
+
import { ChartFrame, chartMotion } from "../../lib/chart/chart-frame.js";
|
|
5
5
|
import { ChartLegend } from "../../lib/chart/chart-legend.js";
|
|
6
6
|
import { ChartTooltipContent } from "../../lib/chart/chart-tooltip.js";
|
|
7
7
|
import { approxTextWidth, categoryText, chartFormatters } from "../../lib/chart/format.js";
|
|
@@ -29,7 +29,8 @@ function RadarChart({
|
|
|
29
29
|
tooltip = true,
|
|
30
30
|
valueFormatter,
|
|
31
31
|
locale,
|
|
32
|
-
|
|
32
|
+
motion,
|
|
33
|
+
animate,
|
|
33
34
|
activeIndex,
|
|
34
35
|
defaultActiveIndex,
|
|
35
36
|
onActiveIndexChange
|
|
@@ -80,7 +81,7 @@ function RadarChart({
|
|
|
80
81
|
className: cn("h-72", className),
|
|
81
82
|
count: n,
|
|
82
83
|
interactive: tooltip !== false,
|
|
83
|
-
animate,
|
|
84
|
+
motion: chartMotion(motion, animate),
|
|
84
85
|
activeIndex,
|
|
85
86
|
defaultActiveIndex,
|
|
86
87
|
onActiveIndexChange,
|
|
@@ -121,11 +121,18 @@ var radarChartMeta = {
|
|
|
121
121
|
default: "runtime locale",
|
|
122
122
|
description: "BCP 47 locale for default number and date formatting."
|
|
123
123
|
},
|
|
124
|
+
{
|
|
125
|
+
name: "motion",
|
|
126
|
+
type: '"full" | "subtle" | "off"',
|
|
127
|
+
default: '"full"',
|
|
128
|
+
description: "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 \u2014 it never overrides a reader's reduce-motion setting. The polygons growing in and morphing are the flourish; the hover highlight is the feedback `subtle` keeps."
|
|
129
|
+
},
|
|
124
130
|
{
|
|
125
131
|
name: "animate",
|
|
126
132
|
type: "boolean",
|
|
127
133
|
default: "true",
|
|
128
|
-
description:
|
|
134
|
+
description: 'Grow the polygons from the centre when the chart scrolls into view. `animate={false}` is `motion="subtle"`.',
|
|
135
|
+
deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
|
|
129
136
|
},
|
|
130
137
|
{
|
|
131
138
|
name: "activeIndex",
|
|
@@ -162,7 +169,7 @@ var radarChartMeta = {
|
|
|
162
169
|
slot: "arrival",
|
|
163
170
|
recipe: "custom",
|
|
164
171
|
part: "Polygon",
|
|
165
|
-
why:
|
|
172
|
+
why: 'Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
|
|
166
173
|
},
|
|
167
174
|
{
|
|
168
175
|
slot: "response",
|
|
@@ -174,7 +181,7 @@ var radarChartMeta = {
|
|
|
174
181
|
slot: "transition",
|
|
175
182
|
recipe: "custom",
|
|
176
183
|
part: "Polygon",
|
|
177
|
-
why: "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."
|
|
184
|
+
why: "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."
|
|
178
185
|
},
|
|
179
186
|
{
|
|
180
187
|
slot: "activity",
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/components/radial-chart/radial-chart.tsx
|
|
4
|
-
import {
|
|
4
|
+
import { ChartCenter } from "../../lib/chart/chart-center.js";
|
|
5
|
+
import { ChartFrame, chartMotion } from "../../lib/chart/chart-frame.js";
|
|
5
6
|
import { ChartLegend } from "../../lib/chart/chart-legend.js";
|
|
6
7
|
import { ChartTooltipContent } from "../../lib/chart/chart-tooltip.js";
|
|
7
|
-
import {
|
|
8
|
+
import { STROKE_HOVER, dimmed } from "../../lib/chart/dim.js";
|
|
9
|
+
import { categoryText, chartFormatters } from "../../lib/chart/format.js";
|
|
8
10
|
import { polar, strokeArcPath } from "../../lib/chart/geometry.js";
|
|
9
|
-
import { drawTransition,
|
|
11
|
+
import { drawTransition, pathStyle, stagger } from "../../lib/chart/motion.js";
|
|
10
12
|
import {
|
|
11
13
|
categoryStyle,
|
|
12
14
|
numberValue
|
|
@@ -30,7 +32,8 @@ function RadialChart({
|
|
|
30
32
|
tooltip = true,
|
|
31
33
|
valueFormatter,
|
|
32
34
|
locale,
|
|
33
|
-
|
|
35
|
+
motion,
|
|
36
|
+
animate,
|
|
34
37
|
activeIndex,
|
|
35
38
|
defaultActiveIndex,
|
|
36
39
|
onActiveIndexChange
|
|
@@ -45,6 +48,7 @@ function RadialChart({
|
|
|
45
48
|
const start = -span / 2 + (sweep >= 360 ? span / 2 : 0);
|
|
46
49
|
const tooltipOptions = typeof tooltip === "object" ? tooltip : {};
|
|
47
50
|
const gauge = sweep < 360;
|
|
51
|
+
const level = chartMotion(motion, animate);
|
|
48
52
|
const geometry = (size) => {
|
|
49
53
|
const outer = gauge ? Math.min(size.width / 2, size.height * (sweep <= 180 ? 0.92 : 0.6)) - 8 : Math.min(size.width, size.height) / 2 - 8;
|
|
50
54
|
const cx = size.width / 2;
|
|
@@ -102,7 +106,7 @@ function RadialChart({
|
|
|
102
106
|
className: cn("h-64", className),
|
|
103
107
|
count: data.length,
|
|
104
108
|
interactive: tooltip !== false,
|
|
105
|
-
|
|
109
|
+
motion: level,
|
|
106
110
|
activeIndex,
|
|
107
111
|
defaultActiveIndex,
|
|
108
112
|
onActiveIndexChange,
|
|
@@ -114,6 +118,10 @@ function RadialChart({
|
|
|
114
118
|
rows: data.map((_, i) => [styles[i].label, fmt.full(values[i])])
|
|
115
119
|
},
|
|
116
120
|
legend: legend ?? !labels ? /* @__PURE__ */ jsx(ChartLegend, { items: names.map((n, i) => ({ key: n, label: styles[i].label, color: styles[i].color, icon: styles[i].icon })) }) : null,
|
|
121
|
+
overlay: centerLabel ? ({ size }) => {
|
|
122
|
+
const g = geometry(size);
|
|
123
|
+
return gauge && sweep <= 180 ? /* @__PURE__ */ jsx(ChartCenter, { center: centerLabel, x: g.cx, y: g.cy - 6, placement: "gauge", locale, motion: level }) : /* @__PURE__ */ jsx(ChartCenter, { center: centerLabel, x: g.cx, y: g.cy, locale, motion: level });
|
|
124
|
+
} : void 0,
|
|
117
125
|
children: ({ size, activeIndex: active }) => {
|
|
118
126
|
const g = geometry(size);
|
|
119
127
|
const ringCount = stacked ? 1 : data.length;
|
|
@@ -140,8 +148,14 @@ function RadialChart({
|
|
|
140
148
|
fill: "none",
|
|
141
149
|
strokeWidth: g.thickness,
|
|
142
150
|
strokeLinecap: stacked ? "butt" : "round",
|
|
143
|
-
className:
|
|
144
|
-
style: {
|
|
151
|
+
className: "[stroke-dasharray:1] [stroke-dashoffset:0] [[data-reveal=out]_&]:[stroke-dashoffset:1]",
|
|
152
|
+
style: {
|
|
153
|
+
...pathStyle(d),
|
|
154
|
+
// Stepped back toward the surface rather than faded: a faded ring
|
|
155
|
+
// lets its muted track show through and turns muddy (lib/chart/dim).
|
|
156
|
+
stroke: active != null && active !== a.i ? dimmed(styles[a.i].color, 60) : styles[a.i].color,
|
|
157
|
+
transition: `${drawTransition(stagger(a.i, 90))}, ${STROKE_HOVER}`
|
|
158
|
+
}
|
|
145
159
|
},
|
|
146
160
|
names[a.i]
|
|
147
161
|
);
|
|
@@ -149,21 +163,7 @@ function RadialChart({
|
|
|
149
163
|
labels && !stacked ? arcs.map((a) => {
|
|
150
164
|
const [x, y] = polar(g.cx, g.cy, radiusOf(g, a.ring), start);
|
|
151
165
|
return /* @__PURE__ */ jsx("text", { x: x - 8, y, dy: "0.32em", textAnchor: "end", className: "fill-muted-foreground text-caption", children: styles[a.i].label }, `l${a.i}`);
|
|
152
|
-
}) : null
|
|
153
|
-
centerLabel ? /* @__PURE__ */ jsxs(
|
|
154
|
-
"text",
|
|
155
|
-
{
|
|
156
|
-
x: g.cx,
|
|
157
|
-
y: gauge && sweep <= 180 ? g.cy - 6 : g.cy,
|
|
158
|
-
textAnchor: "middle",
|
|
159
|
-
className: "[[data-reveal=out]_&]:opacity-0",
|
|
160
|
-
style: { transition: morphTransition() },
|
|
161
|
-
children: [
|
|
162
|
-
/* @__PURE__ */ jsx("tspan", { x: g.cx, dy: gauge && sweep <= 180 ? centerLabel.label ? "-1.4em" : "0" : "0.35em", className: "fill-foreground text-heading-2 tabular-nums", children: centerValueText(centerLabel.value, locale) }),
|
|
163
|
-
centerLabel.label ? /* @__PURE__ */ jsx("tspan", { x: g.cx, dy: gauge && sweep <= 180 ? "1.6em" : "1.9em", className: "fill-muted-foreground text-caption", children: centerLabel.label }) : null
|
|
164
|
-
]
|
|
165
|
-
}
|
|
166
|
-
) : null
|
|
166
|
+
}) : null
|
|
167
167
|
] });
|
|
168
168
|
}
|
|
169
169
|
}
|
|
@@ -133,11 +133,18 @@ var radialChartMeta = {
|
|
|
133
133
|
default: "runtime locale",
|
|
134
134
|
description: "BCP 47 locale for default number and date formatting."
|
|
135
135
|
},
|
|
136
|
+
{
|
|
137
|
+
name: "motion",
|
|
138
|
+
type: '"full" | "subtle" | "off"',
|
|
139
|
+
default: '"full"',
|
|
140
|
+
description: "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 \u2014 it never overrides a reader's reduce-motion setting. The draw-on, the ring morph and the rolling centre number are the flourish; the hover dim is the feedback `subtle` keeps."
|
|
141
|
+
},
|
|
136
142
|
{
|
|
137
143
|
name: "animate",
|
|
138
144
|
type: "boolean",
|
|
139
145
|
default: "true",
|
|
140
|
-
description:
|
|
146
|
+
description: 'Draw the rings round from their start when the chart scrolls into view. `animate={false}` is `motion="subtle"`.',
|
|
147
|
+
deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
|
|
141
148
|
},
|
|
142
149
|
{
|
|
143
150
|
name: "activeIndex",
|
|
@@ -174,19 +181,25 @@ var radialChartMeta = {
|
|
|
174
181
|
slot: "arrival",
|
|
175
182
|
recipe: "custom",
|
|
176
183
|
part: "Ring",
|
|
177
|
-
why:
|
|
184
|
+
why: 'Once on screen each ring draws round from its start (stroke-dashoffset on a round-capped arc), staggered outside in. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
|
|
178
185
|
},
|
|
179
186
|
{
|
|
180
187
|
slot: "response",
|
|
181
188
|
recipe: "none",
|
|
182
189
|
part: "Active ring",
|
|
183
|
-
why: "The hovered ring stays full strength and the others dim at once."
|
|
190
|
+
why: "The hovered ring stays full strength and the others dim at once \u2014 mixed toward the surface rather than faded, so a dimmed ring stays solid over its track instead of turning muddy. Essential: `subtle` keeps it."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
slot: "transition",
|
|
194
|
+
recipe: "custom",
|
|
195
|
+
part: "Centre value",
|
|
196
|
+
why: "A numeric centre value rolls to its new total digit by digit (NumberTicker) when the data changes, so the headline is seen moving with the rings instead of being found different afterwards. Expressive: under `subtle` it updates in place."
|
|
184
197
|
},
|
|
185
198
|
{
|
|
186
199
|
slot: "transition",
|
|
187
200
|
recipe: "custom",
|
|
188
201
|
part: "Ring",
|
|
189
|
-
why: "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."
|
|
202
|
+
why: "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."
|
|
190
203
|
},
|
|
191
204
|
{
|
|
192
205
|
slot: "activity",
|
|
@@ -67,6 +67,32 @@ var radioMeta = {
|
|
|
67
67
|
}
|
|
68
68
|
],
|
|
69
69
|
tokensUsed: ["input", "card", "primary", "ring"],
|
|
70
|
+
motion: [
|
|
71
|
+
{
|
|
72
|
+
slot: "arrival",
|
|
73
|
+
recipe: "none",
|
|
74
|
+
part: "Root",
|
|
75
|
+
why: "Radios arrive with the form they belong to. Animating each one in turns a question into a title sequence."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
slot: "response",
|
|
79
|
+
recipe: "motion-interactive",
|
|
80
|
+
part: "Root",
|
|
81
|
+
why: "The ring's edge and focus ring ease, so arrowing through a group \u2014 which moves the selection with focus \u2014 reads as one choice moving down the list."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
slot: "transition",
|
|
85
|
+
recipe: "motion-interactive",
|
|
86
|
+
part: "Indicator",
|
|
87
|
+
why: "The dot grows from the centre on a spring and shrinks out of the radio being left. A group's selection moves from one option to another, and the paired grow and shrink is what makes it read as moving rather than two dots blinking."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
slot: "activity",
|
|
91
|
+
recipe: "none",
|
|
92
|
+
part: "Root",
|
|
93
|
+
why: "A radio commits instantly. A slow write belongs to the surrounding form's pending state, not the control."
|
|
94
|
+
}
|
|
95
|
+
],
|
|
70
96
|
a11y: [
|
|
71
97
|
"Base UI provides the radiogroup/radio roles, aria-checked, hidden native inputs, and roving tabindex.",
|
|
72
98
|
"Arrow keys move and select between items; Space selects the focused item; Tab enters/leaves the group.",
|