@noxlovette/material 0.9.2 → 0.9.4
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/claude-skill/material-design/references/motion-guide.md +7 -7
- package/dist/animation/TransitionPatterns.stories.svelte +2 -1
- package/dist/animation/containerTransform.js +3 -2
- package/dist/animation/presence.svelte.js +12 -1
- package/dist/animation/sharedAxisTransition.js +7 -5
- package/dist/animation/skeleton.d.ts +3 -2
- package/dist/animation/skeleton.js +27 -9
- package/dist/components/buttons/button-group/ButtonGroup.svelte +5 -3
- package/dist/components/buttons/connected/theme.d.ts +15 -15
- package/dist/components/buttons/connected/theme.js +20 -5
- package/dist/components/buttons/shapeMorph.js +12 -4
- package/dist/components/buttons/split-button/theme.d.ts +15 -15
- package/dist/components/buttons/split-button/theme.js +21 -15
- package/dist/components/buttons/theme.d.ts +40 -40
- package/dist/components/buttons/theme.js +29 -29
- package/dist/components/cards/theme.js +1 -1
- package/dist/components/chips/Chip.svelte +1 -1
- package/dist/components/chips/theme.js +1 -1
- package/dist/components/containers/bottom-sheet/theme.js +1 -1
- package/dist/components/containers/carousel/theme.js +1 -1
- package/dist/components/containers/context-menu/theme.js +2 -2
- package/dist/components/containers/dialogue/theme.js +1 -1
- package/dist/components/containers/list/theme.d.ts +3 -3
- package/dist/components/containers/list/theme.js +8 -8
- package/dist/components/containers/menu/theme.js +3 -3
- package/dist/components/date/theme.js +2 -2
- package/dist/components/forms/checkbox/theme.js +1 -1
- package/dist/components/forms/command/theme.js +1 -1
- package/dist/components/forms/search/theme.js +1 -1
- package/dist/components/forms/select/theme.js +2 -2
- package/dist/components/forms/slider/theme.js +7 -7
- package/dist/components/forms/textfield/theme.js +4 -4
- package/dist/components/forms/toggle-group/theme.js +2 -2
- package/dist/components/nav/appbar/theme.js +22 -6
- package/dist/components/nav/navbar/theme.js +1 -1
- package/dist/components/nav/tabs/theme.js +1 -1
- package/dist/components/snackbar/theme.js +1 -1
- package/dist/components/table/TableHeader.svelte +1 -1
- package/dist/components/toolbar/theme.js +2 -2
- package/dist/styles/components.css +7 -2
- package/dist/styles/icon.css +19 -0
- package/dist/styles/motion.css +17 -0
- package/dist/styles/rounding.css +4 -0
- package/dist/utils/icon/Icon.svelte +5 -5
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ Source: [m3.material.io → Transitions → Applying transitions](https://m3.mat
|
|
|
15
15
|
|
|
16
16
|
| Quality | M3 rule | What it means here |
|
|
17
17
|
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| **Follows accessibility settings** | With reduced motion on, use subtle fades instead of intense slides or scales, and turn off decorative effects such as parallax and shape morphing. |
|
|
18
|
+
| **Follows accessibility settings** | With reduced motion on, use subtle fades instead of intense slides or scales, and turn off decorative effects such as parallax and shape morphing. | The OS preference switches the transition primitives and CSS spatial tokens automatically (see [Reduced motion](#reduced-motion)). |
|
|
19
19
|
| **Consistent** | Use the same transition type for the same kind of change everywhere, so the app feels like one cohesive family. | Always use one primitive for one relationship. Every hierarchy step uses `sharedAxis` on the same axis, and every navbar/rail destination uses `fadeThrough`. Never mix patterns for the same kind of navigation. |
|
|
20
20
|
| **Stable layouts** | Use skeleton loaders so the layout holds still during a transition. Content shouldn't pop in or move around. | Reserve the loaded content's footprint with `{@attach skeleton}` placeholders, then reveal the content with `presence(…, enterExit.fade)`. The content shouldn't be conditionally inserted in a way that shifts its siblings. |
|
|
21
21
|
| **No jarring jump cuts** | By default, avoid instant screen swaps because they disorient users. A jump cut is fine only when pure efficiency matters most, such as opening a menu in a productivity app. | Surfaces and navigation always animate, which is why the ownership table below is mandatory. A jump cut has to be an explicit, documented choice, never the result of leaving the animation out. |
|
|
@@ -196,10 +196,10 @@ MDC's own durations/easings (`short1`…`extraLong4` × `emphasized`/`standard`)
|
|
|
196
196
|
|
|
197
197
|
## Reduced motion
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
The OS `prefers-reduced-motion: reduce` setting governs both the Motion primitives and the CSS tokens. Call sites use the same API regardless of the setting. See [Applying transitions](https://m3.material.io/styles/motion/transitions/applying-transitions) for the M3 rationale.
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
201
|
+
- `presence` keeps a short opacity fade for every `enterExit` preset, including sheets. Scale and translation stay at their settled values. `sharedAxis`, `lateral` and `fadeThrough` use an opacity-only fade; the region's box swaps immediately. `containerTransform` fades its snapshots without growing the group.
|
|
202
|
+
- `motion.css` makes the three spatial duration tokens `0ms` and maps their timing functions to non-overshooting effects curves. Color, opacity and state-layer transitions retain their effects springs.
|
|
203
|
+
- The `skeleton` attachment shows a static placeholder. It stops or resumes the pulse if the OS preference changes while mounted.
|
|
204
|
+
- Shape morphs in the shape library swap instantly, and ripples and decorative rotation stop. Button corner and button-group pressed feedback remain animated with a critically damped spring, so they do not overshoot.
|
|
205
|
+
- Storybook's **Motion/Transition patterns** page uses these same primitives, so it shows the reduced variants when the OS preference is on.
|
|
@@ -183,7 +183,8 @@
|
|
|
183
183
|
skeleton: {
|
|
184
184
|
id: 'skeleton',
|
|
185
185
|
title: 'Skeleton loaders',
|
|
186
|
-
summary:
|
|
186
|
+
summary:
|
|
187
|
+
'A placeholder pulses until real content fades in; it stays still with reduced motion.',
|
|
187
188
|
anchor: 'skeleton-loaders',
|
|
188
189
|
run: runSkeleton
|
|
189
190
|
}
|
|
@@ -76,11 +76,12 @@ export const containerTransform = (update, { from, to, spring = springTokens.spa
|
|
|
76
76
|
root.style.setProperty(SHADOW, shadowOf(target) ?? fromShadow ?? 'none');
|
|
77
77
|
};
|
|
78
78
|
// The class lets motion.css keep both snapshots at their width, clipped by the container.
|
|
79
|
-
const
|
|
79
|
+
const reduced = prefersReducedMotion();
|
|
80
|
+
const builder = animateView(updateAndFill, springTransition(reduced ? springTokens.effects : spring))
|
|
80
81
|
.add(from, to)
|
|
81
82
|
.class('md-container-transform');
|
|
82
83
|
// Reduced motion: the container doesn't grow; its two states just crossfade in place.
|
|
83
|
-
if (
|
|
84
|
+
if (reduced)
|
|
84
85
|
builder.layout({ duration: 0 });
|
|
85
86
|
return builder
|
|
86
87
|
.old({ opacity: [1, 0] }, springTransition(springTokens.effects))
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { animate } from 'motion';
|
|
2
2
|
import { untrack } from 'svelte';
|
|
3
|
+
import { enterExit } from './enterExit.js';
|
|
4
|
+
import { prefersReducedMotion } from './reducedMotion.js';
|
|
3
5
|
const fromTo = (from, to) => Object.fromEntries(Object.entries(to).map(([key, value]) => [key, [from[key], value]]));
|
|
4
6
|
/**
|
|
5
7
|
* Enter/exit attachment driven by Motion's `animate()`. Plays `transition.enter` from `hidden` to
|
|
@@ -33,7 +35,16 @@ export const presence = (isOpen, transition, onExitComplete) => (node) => {
|
|
|
33
35
|
$effect(() => {
|
|
34
36
|
const open = isOpen();
|
|
35
37
|
untrack(() => {
|
|
36
|
-
|
|
38
|
+
// Reduced motion keeps the mount/unmount cue, but never animates the preset's
|
|
39
|
+
// scale or translation (including the sheet presets that normally have no fade).
|
|
40
|
+
const active = prefersReducedMotion() ? enterExit.fade : transition;
|
|
41
|
+
const { hidden, shown, exited = hidden, enter, exit } = active;
|
|
42
|
+
if (active !== transition) {
|
|
43
|
+
const shownTransform = transition.shown.transform;
|
|
44
|
+
const settled = Array.isArray(shownTransform) ? shownTransform.at(-1) : shownTransform;
|
|
45
|
+
if (typeof settled === 'string')
|
|
46
|
+
node.style.transform = settled;
|
|
47
|
+
}
|
|
37
48
|
clearTimeout(settleTimer);
|
|
38
49
|
if (open) {
|
|
39
50
|
controls = animate(node, started ? shown : fromTo(hidden, shown), enter);
|
|
@@ -61,13 +61,15 @@ const fadeInPlace = (update, target, spring, incoming) => {
|
|
|
61
61
|
outgoing.transform = Array(2).fill(`translate(${dx}px, ${dy}px)`);
|
|
62
62
|
};
|
|
63
63
|
const builder = view(run, target, spring);
|
|
64
|
+
// The whole-page group also has a layout animation when no target was supplied.
|
|
65
|
+
builder.layout({ duration: 0 });
|
|
64
66
|
if (target)
|
|
65
|
-
builder.
|
|
67
|
+
builder.crop(false);
|
|
66
68
|
return builder.old(outgoing, fadeOut).new(...incoming);
|
|
67
69
|
};
|
|
68
70
|
/* Reduced motion: every navigation pattern becomes this fade, with no movement or scale. M3
|
|
69
71
|
swaps movement for a subtle fade rather than cutting. */
|
|
70
|
-
const reducedFade = (update, target
|
|
72
|
+
const reducedFade = (update, target) => fadeInPlace(update, target, springTokens.effects, [{ opacity: [0, 1] }, fadeInAfterOut]);
|
|
71
73
|
/**
|
|
72
74
|
* M3 forward and backward (shared axis): outgoing and incoming content travel together along one
|
|
73
75
|
* axis while fading through each other.
|
|
@@ -85,7 +87,7 @@ const reducedFade = (update, target, spring) => fadeInPlace(update, target, spri
|
|
|
85
87
|
*/
|
|
86
88
|
export const sharedAxis = (update, { target, axis = 'x', direction = 'forward', spring = springTokens.spatial } = {}) => {
|
|
87
89
|
if (prefersReducedMotion())
|
|
88
|
-
return reducedFade(update, target
|
|
90
|
+
return reducedFade(update, target);
|
|
89
91
|
const forward = direction === 'forward';
|
|
90
92
|
const [outgoing, incoming] = axis === 'z'
|
|
91
93
|
? [`scale(${forward ? 1.1 : 0.8})`, `scale(${forward ? 0.8 : 1.1})`]
|
|
@@ -116,7 +118,7 @@ export const sharedAxis = (update, { target, axis = 'x', direction = 'forward',
|
|
|
116
118
|
*/
|
|
117
119
|
export const lateral = (update, { target, axis = 'x', direction = 'forward', spring = springTokens.spatial } = {}) => {
|
|
118
120
|
if (prefersReducedMotion())
|
|
119
|
-
return reducedFade(update, target
|
|
121
|
+
return reducedFade(update, target);
|
|
120
122
|
const sign = direction === 'forward' ? 1 : -1;
|
|
121
123
|
const move = axis === 'y' ? 'translateY' : 'translateX';
|
|
122
124
|
const builder = view(update, target, spring)
|
|
@@ -144,7 +146,7 @@ export const lateral = (update, { target, axis = 'x', direction = 'forward', spr
|
|
|
144
146
|
*/
|
|
145
147
|
export const fadeThrough = (update, { target, spring = springTokens.spatial } = {}) => {
|
|
146
148
|
if (prefersReducedMotion())
|
|
147
|
-
return reducedFade(update, target
|
|
149
|
+
return reducedFade(update, target);
|
|
148
150
|
return fadeInPlace(update, target, spring, [
|
|
149
151
|
{ opacity: [0, 1], transform: ['scale(0.92)', 'scale(1)'] },
|
|
150
152
|
{ opacity: fadeInAfterOut }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Attachment } from 'svelte/attachments';
|
|
2
2
|
/**
|
|
3
|
-
* M3 skeleton loader: a placeholder pulses until real content replaces it
|
|
4
|
-
* for the content with `presence(…, enterExit.fade)` so the
|
|
3
|
+
* M3 skeleton loader: a placeholder pulses until real content replaces it, and stays static under
|
|
4
|
+
* reduced motion. Swap the placeholder for the content with `presence(…, enterExit.fade)` so the
|
|
5
|
+
* reveal is a fade, not a cut.
|
|
5
6
|
* https://m3.material.io/styles/motion/transitions/transition-patterns#skeleton-loaders
|
|
6
7
|
*
|
|
7
8
|
* M3 "stable layouts" (https://m3.material.io/styles/motion/transitions/applying-transitions): the placeholder takes the loaded
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { animate } from 'motion';
|
|
2
|
+
import { prefersReducedMotion } from './reducedMotion.js';
|
|
2
3
|
import { springTokens, springTransition } from './spring.js';
|
|
3
4
|
/* Hold at each end of the pulse — a critically damped spring alone settles in ~370ms, which reads
|
|
4
5
|
as flicker. Not an M3 value: tune by eye. */
|
|
5
6
|
const PULSE_HOLD_S = 0.5;
|
|
6
7
|
/**
|
|
7
|
-
* M3 skeleton loader: a placeholder pulses until real content replaces it
|
|
8
|
-
* for the content with `presence(…, enterExit.fade)` so the
|
|
8
|
+
* M3 skeleton loader: a placeholder pulses until real content replaces it, and stays static under
|
|
9
|
+
* reduced motion. Swap the placeholder for the content with `presence(…, enterExit.fade)` so the
|
|
10
|
+
* reveal is a fade, not a cut.
|
|
9
11
|
* https://m3.material.io/styles/motion/transitions/transition-patterns#skeleton-loaders
|
|
10
12
|
*
|
|
11
13
|
* M3 "stable layouts" (https://m3.material.io/styles/motion/transitions/applying-transitions): the placeholder takes the loaded
|
|
@@ -18,11 +20,27 @@ const PULSE_HOLD_S = 0.5;
|
|
|
18
20
|
* ```
|
|
19
21
|
*/
|
|
20
22
|
export const skeleton = (node) => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const media = typeof matchMedia === 'function' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
|
|
24
|
+
const initialOpacity = node.style.opacity;
|
|
25
|
+
let controls;
|
|
26
|
+
const sync = () => {
|
|
27
|
+
controls?.stop();
|
|
28
|
+
controls = undefined;
|
|
29
|
+
node.style.opacity = initialOpacity;
|
|
30
|
+
if (prefersReducedMotion())
|
|
31
|
+
return;
|
|
32
|
+
controls = animate(node, { opacity: [1, 0.4] }, {
|
|
33
|
+
...springTransition(springTokens.slowEffects),
|
|
34
|
+
repeat: Infinity,
|
|
35
|
+
repeatType: 'reverse',
|
|
36
|
+
repeatDelay: PULSE_HOLD_S
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
sync();
|
|
40
|
+
media?.addEventListener('change', sync);
|
|
41
|
+
return () => {
|
|
42
|
+
media?.removeEventListener('change', sync);
|
|
43
|
+
controls?.stop();
|
|
44
|
+
node.style.opacity = initialOpacity;
|
|
45
|
+
};
|
|
28
46
|
};
|
|
@@ -29,6 +29,7 @@ For a set of related options where one or more is selected, use `ConnectedButton
|
|
|
29
29
|
|
|
30
30
|
const GROW = 0.15;
|
|
31
31
|
const spring = springTransition(springTokens.fastSpatial);
|
|
32
|
+
const reducedSpring = springTransition(springTokens.fastEffects);
|
|
32
33
|
const reducedMotion = () =>
|
|
33
34
|
typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
34
35
|
|
|
@@ -57,7 +58,7 @@ For a set of related options where one or more is selected, use `ConnectedButton
|
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
const start = (el: HTMLElement) => {
|
|
60
|
-
if (orientation !== 'horizontal' || press
|
|
61
|
+
if (orientation !== 'horizontal' || press) return;
|
|
61
62
|
const width = widthOf(el);
|
|
62
63
|
const neighbours = [el.previousElementSibling, el.nextElementSibling].filter(isEnabled);
|
|
63
64
|
const share = (width * GROW) / Math.max(neighbours.length, 1);
|
|
@@ -70,7 +71,8 @@ For a set of related options where one or more is selected, use `ConnectedButton
|
|
|
70
71
|
})
|
|
71
72
|
]
|
|
72
73
|
};
|
|
73
|
-
for (const { el: item, to } of press.items)
|
|
74
|
+
for (const { el: item, to } of press.items)
|
|
75
|
+
animate(item, { width: `${to}px` }, reducedMotion() ? reducedSpring : spring);
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
const end = () => {
|
|
@@ -78,7 +80,7 @@ For a set of related options where one or more is selected, use `ConnectedButton
|
|
|
78
80
|
const { items } = press;
|
|
79
81
|
press = null;
|
|
80
82
|
for (const { el, width } of items) {
|
|
81
|
-
animate(el, { width: `${width}px` }, spring).then(() => {
|
|
83
|
+
animate(el, { width: `${width}px` }, reducedMotion() ? reducedSpring : spring).then(() => {
|
|
82
84
|
// Hand the width back to the layout, unless a new press picked this button up.
|
|
83
85
|
if (press?.items.some((i) => i.el === el)) return;
|
|
84
86
|
el.style.width = '';
|
|
@@ -39,23 +39,23 @@ export declare const connectedButtonGroupItem: import("tailwind-variants").TVRet
|
|
|
39
39
|
size: {
|
|
40
40
|
xs: {
|
|
41
41
|
base: string[];
|
|
42
|
-
icon: "size-
|
|
42
|
+
icon: "size-(--text-icon-20) text-icon-20";
|
|
43
43
|
};
|
|
44
44
|
sm: {
|
|
45
45
|
base: string[];
|
|
46
|
-
icon: "size-
|
|
46
|
+
icon: "size-(--text-icon-20) text-icon-20";
|
|
47
47
|
};
|
|
48
48
|
md: {
|
|
49
49
|
base: string[];
|
|
50
|
-
icon: "size-
|
|
50
|
+
icon: "size-(--text-icon-24) text-icon-24";
|
|
51
51
|
};
|
|
52
52
|
lg: {
|
|
53
53
|
base: string[];
|
|
54
|
-
icon: "size-
|
|
54
|
+
icon: "size-(--text-icon-32) text-icon-32";
|
|
55
55
|
};
|
|
56
56
|
xl: {
|
|
57
57
|
base: string[];
|
|
58
|
-
icon: "size-
|
|
58
|
+
icon: "size-(--text-icon-40) text-icon-40";
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
61
|
shape: {
|
|
@@ -73,23 +73,23 @@ export declare const connectedButtonGroupItem: import("tailwind-variants").TVRet
|
|
|
73
73
|
size: {
|
|
74
74
|
xs: {
|
|
75
75
|
base: string[];
|
|
76
|
-
icon: "size-
|
|
76
|
+
icon: "size-(--text-icon-20) text-icon-20";
|
|
77
77
|
};
|
|
78
78
|
sm: {
|
|
79
79
|
base: string[];
|
|
80
|
-
icon: "size-
|
|
80
|
+
icon: "size-(--text-icon-20) text-icon-20";
|
|
81
81
|
};
|
|
82
82
|
md: {
|
|
83
83
|
base: string[];
|
|
84
|
-
icon: "size-
|
|
84
|
+
icon: "size-(--text-icon-24) text-icon-24";
|
|
85
85
|
};
|
|
86
86
|
lg: {
|
|
87
87
|
base: string[];
|
|
88
|
-
icon: "size-
|
|
88
|
+
icon: "size-(--text-icon-32) text-icon-32";
|
|
89
89
|
};
|
|
90
90
|
xl: {
|
|
91
91
|
base: string[];
|
|
92
|
-
icon: "size-
|
|
92
|
+
icon: "size-(--text-icon-40) text-icon-40";
|
|
93
93
|
};
|
|
94
94
|
};
|
|
95
95
|
shape: {
|
|
@@ -107,23 +107,23 @@ export declare const connectedButtonGroupItem: import("tailwind-variants").TVRet
|
|
|
107
107
|
size: {
|
|
108
108
|
xs: {
|
|
109
109
|
base: string[];
|
|
110
|
-
icon: "size-
|
|
110
|
+
icon: "size-(--text-icon-20) text-icon-20";
|
|
111
111
|
};
|
|
112
112
|
sm: {
|
|
113
113
|
base: string[];
|
|
114
|
-
icon: "size-
|
|
114
|
+
icon: "size-(--text-icon-20) text-icon-20";
|
|
115
115
|
};
|
|
116
116
|
md: {
|
|
117
117
|
base: string[];
|
|
118
|
-
icon: "size-
|
|
118
|
+
icon: "size-(--text-icon-24) text-icon-24";
|
|
119
119
|
};
|
|
120
120
|
lg: {
|
|
121
121
|
base: string[];
|
|
122
|
-
icon: "size-
|
|
122
|
+
icon: "size-(--text-icon-32) text-icon-32";
|
|
123
123
|
};
|
|
124
124
|
xl: {
|
|
125
125
|
base: string[];
|
|
126
|
-
icon: "size-
|
|
126
|
+
icon: "size-(--text-icon-40) text-icon-40";
|
|
127
127
|
};
|
|
128
128
|
};
|
|
129
129
|
shape: {
|
|
@@ -29,23 +29,38 @@ export const connectedButtonGroupItem = tv({
|
|
|
29
29
|
variants: {
|
|
30
30
|
size: {
|
|
31
31
|
xs: {
|
|
32
|
-
base: [
|
|
32
|
+
base: [
|
|
33
|
+
buttonSizes.xs.base,
|
|
34
|
+
'[--cbg-inner:var(--radius-sm)] [--cbg-pressed:var(--radius-xs)]'
|
|
35
|
+
],
|
|
33
36
|
icon: buttonSizes.xs.icon
|
|
34
37
|
},
|
|
35
38
|
sm: {
|
|
36
|
-
base: [
|
|
39
|
+
base: [
|
|
40
|
+
buttonSizes.sm.base,
|
|
41
|
+
'[--cbg-inner:var(--radius-sm)] [--cbg-pressed:var(--radius-xs)]'
|
|
42
|
+
],
|
|
37
43
|
icon: buttonSizes.sm.icon
|
|
38
44
|
},
|
|
39
45
|
md: {
|
|
40
|
-
base: [
|
|
46
|
+
base: [
|
|
47
|
+
buttonSizes.md.base,
|
|
48
|
+
'[--cbg-inner:var(--radius-sm)] [--cbg-pressed:var(--radius-xs)]'
|
|
49
|
+
],
|
|
41
50
|
icon: buttonSizes.md.icon
|
|
42
51
|
},
|
|
43
52
|
lg: {
|
|
44
|
-
base: [
|
|
53
|
+
base: [
|
|
54
|
+
buttonSizes.lg.base,
|
|
55
|
+
'[--cbg-inner:var(--radius-lg)] [--cbg-pressed:var(--radius-md)]'
|
|
56
|
+
],
|
|
45
57
|
icon: buttonSizes.lg.icon
|
|
46
58
|
},
|
|
47
59
|
xl: {
|
|
48
|
-
base: [
|
|
60
|
+
base: [
|
|
61
|
+
buttonSizes.xl.base,
|
|
62
|
+
'[--cbg-inner:var(--radius-lg-increased)] [--cbg-pressed:var(--radius-lg)]'
|
|
63
|
+
],
|
|
49
64
|
icon: buttonSizes.xl.icon
|
|
50
65
|
}
|
|
51
66
|
},
|
|
@@ -25,6 +25,7 @@ const isDisabled = (el) => el.matches(':disabled, [aria-disabled="true"], [data-
|
|
|
25
25
|
export function shapeMorph(el, corners) {
|
|
26
26
|
const reduced = matchMedia('(prefers-reduced-motion: reduce)');
|
|
27
27
|
const spring = springTransition(springTokens.fastSpatial);
|
|
28
|
+
const reducedSpring = springTransition(springTokens.fastEffects);
|
|
28
29
|
const state = { pressed: false, hovered: false, focusVisible: false };
|
|
29
30
|
const resolve = () => {
|
|
30
31
|
const style = getComputedStyle(el);
|
|
@@ -43,10 +44,7 @@ export function shapeMorph(el, corners) {
|
|
|
43
44
|
return;
|
|
44
45
|
targets[i] = to;
|
|
45
46
|
controls[i]?.stop();
|
|
46
|
-
|
|
47
|
-
values[i].jump(to);
|
|
48
|
-
else
|
|
49
|
-
controls[i] = animate(values[i], to, spring);
|
|
47
|
+
controls[i] = animate(values[i], to, reduced.matches ? reducedSpring : spring);
|
|
50
48
|
});
|
|
51
49
|
};
|
|
52
50
|
const set = (key, on) => {
|
|
@@ -81,6 +79,15 @@ export function shapeMorph(el, corners) {
|
|
|
81
79
|
attributes: true,
|
|
82
80
|
attributeFilter: ['class', 'data-state', 'aria-pressed', 'aria-expanded']
|
|
83
81
|
});
|
|
82
|
+
const onReducedChange = () => {
|
|
83
|
+
if (!reduced.matches)
|
|
84
|
+
return;
|
|
85
|
+
controls.forEach((control, i) => {
|
|
86
|
+
control?.stop();
|
|
87
|
+
values[i].jump(targets[i]);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
reduced.addEventListener?.('change', onReducedChange);
|
|
84
91
|
el.addEventListener('pointerdown', onPointerDown);
|
|
85
92
|
el.addEventListener('pointerenter', onEnter);
|
|
86
93
|
el.addEventListener('pointerleave', onLeave);
|
|
@@ -92,6 +99,7 @@ export function shapeMorph(el, corners) {
|
|
|
92
99
|
window.addEventListener('pointercancel', release);
|
|
93
100
|
return () => {
|
|
94
101
|
observer.disconnect();
|
|
102
|
+
reduced.removeEventListener?.('change', onReducedChange);
|
|
95
103
|
controls.forEach((c) => c?.stop());
|
|
96
104
|
unsubscribe.forEach((u) => u());
|
|
97
105
|
el.removeEventListener('pointerdown', onPointerDown);
|
|
@@ -4,31 +4,31 @@ export declare const splitButton: import("tailwind-variants").TVReturnType<{
|
|
|
4
4
|
size: {
|
|
5
5
|
xs: {
|
|
6
6
|
leading: string[];
|
|
7
|
-
leadingIcon: "size-
|
|
7
|
+
leadingIcon: "size-(--text-icon-20) text-icon-20";
|
|
8
8
|
trailing: string[];
|
|
9
9
|
chevron: string;
|
|
10
10
|
};
|
|
11
11
|
sm: {
|
|
12
12
|
leading: string[];
|
|
13
|
-
leadingIcon: "size-
|
|
13
|
+
leadingIcon: "size-(--text-icon-20) text-icon-20";
|
|
14
14
|
trailing: string[];
|
|
15
15
|
chevron: string;
|
|
16
16
|
};
|
|
17
17
|
md: {
|
|
18
18
|
leading: string[];
|
|
19
|
-
leadingIcon: "size-
|
|
19
|
+
leadingIcon: "size-(--text-icon-24) text-icon-24";
|
|
20
20
|
trailing: string[];
|
|
21
21
|
chevron: string;
|
|
22
22
|
};
|
|
23
23
|
lg: {
|
|
24
24
|
leading: string[];
|
|
25
|
-
leadingIcon: "size-
|
|
25
|
+
leadingIcon: "size-(--text-icon-32) text-icon-32";
|
|
26
26
|
trailing: string[];
|
|
27
27
|
chevron: string;
|
|
28
28
|
};
|
|
29
29
|
xl: {
|
|
30
30
|
leading: string[];
|
|
31
|
-
leadingIcon: "size-
|
|
31
|
+
leadingIcon: "size-(--text-icon-40) text-icon-40";
|
|
32
32
|
trailing: string[];
|
|
33
33
|
chevron: string;
|
|
34
34
|
};
|
|
@@ -43,31 +43,31 @@ export declare const splitButton: import("tailwind-variants").TVReturnType<{
|
|
|
43
43
|
size: {
|
|
44
44
|
xs: {
|
|
45
45
|
leading: string[];
|
|
46
|
-
leadingIcon: "size-
|
|
46
|
+
leadingIcon: "size-(--text-icon-20) text-icon-20";
|
|
47
47
|
trailing: string[];
|
|
48
48
|
chevron: string;
|
|
49
49
|
};
|
|
50
50
|
sm: {
|
|
51
51
|
leading: string[];
|
|
52
|
-
leadingIcon: "size-
|
|
52
|
+
leadingIcon: "size-(--text-icon-20) text-icon-20";
|
|
53
53
|
trailing: string[];
|
|
54
54
|
chevron: string;
|
|
55
55
|
};
|
|
56
56
|
md: {
|
|
57
57
|
leading: string[];
|
|
58
|
-
leadingIcon: "size-
|
|
58
|
+
leadingIcon: "size-(--text-icon-24) text-icon-24";
|
|
59
59
|
trailing: string[];
|
|
60
60
|
chevron: string;
|
|
61
61
|
};
|
|
62
62
|
lg: {
|
|
63
63
|
leading: string[];
|
|
64
|
-
leadingIcon: "size-
|
|
64
|
+
leadingIcon: "size-(--text-icon-32) text-icon-32";
|
|
65
65
|
trailing: string[];
|
|
66
66
|
chevron: string;
|
|
67
67
|
};
|
|
68
68
|
xl: {
|
|
69
69
|
leading: string[];
|
|
70
|
-
leadingIcon: "size-
|
|
70
|
+
leadingIcon: "size-(--text-icon-40) text-icon-40";
|
|
71
71
|
trailing: string[];
|
|
72
72
|
chevron: string;
|
|
73
73
|
};
|
|
@@ -82,31 +82,31 @@ export declare const splitButton: import("tailwind-variants").TVReturnType<{
|
|
|
82
82
|
size: {
|
|
83
83
|
xs: {
|
|
84
84
|
leading: string[];
|
|
85
|
-
leadingIcon: "size-
|
|
85
|
+
leadingIcon: "size-(--text-icon-20) text-icon-20";
|
|
86
86
|
trailing: string[];
|
|
87
87
|
chevron: string;
|
|
88
88
|
};
|
|
89
89
|
sm: {
|
|
90
90
|
leading: string[];
|
|
91
|
-
leadingIcon: "size-
|
|
91
|
+
leadingIcon: "size-(--text-icon-20) text-icon-20";
|
|
92
92
|
trailing: string[];
|
|
93
93
|
chevron: string;
|
|
94
94
|
};
|
|
95
95
|
md: {
|
|
96
96
|
leading: string[];
|
|
97
|
-
leadingIcon: "size-
|
|
97
|
+
leadingIcon: "size-(--text-icon-24) text-icon-24";
|
|
98
98
|
trailing: string[];
|
|
99
99
|
chevron: string;
|
|
100
100
|
};
|
|
101
101
|
lg: {
|
|
102
102
|
leading: string[];
|
|
103
|
-
leadingIcon: "size-
|
|
103
|
+
leadingIcon: "size-(--text-icon-32) text-icon-32";
|
|
104
104
|
trailing: string[];
|
|
105
105
|
chevron: string;
|
|
106
106
|
};
|
|
107
107
|
xl: {
|
|
108
108
|
leading: string[];
|
|
109
|
-
leadingIcon: "size-
|
|
109
|
+
leadingIcon: "size-(--text-icon-40) text-icon-40";
|
|
110
110
|
trailing: string[];
|
|
111
111
|
chevron: string;
|
|
112
112
|
};
|
|
@@ -23,56 +23,62 @@ export const splitButton = tv({
|
|
|
23
23
|
xs: {
|
|
24
24
|
leading: [
|
|
25
25
|
buttonSizes.xs.base,
|
|
26
|
-
'ps-spacing-150 pe-spacing-125 [--split-active:
|
|
26
|
+
'ps-spacing-150 pe-spacing-125 [--split-active:var(--radius-sm)] [--split-inner:var(--radius-xs)]'
|
|
27
27
|
],
|
|
28
28
|
leadingIcon: buttonSizes.xs.icon,
|
|
29
29
|
trailing: [
|
|
30
30
|
buttonSizes.xs.base,
|
|
31
|
-
'w-spacing-600 [--split-active:
|
|
31
|
+
'w-spacing-600 [--split-active:var(--radius-sm)] [--split-inner:var(--radius-xs)]'
|
|
32
32
|
],
|
|
33
|
-
chevron: 'size-
|
|
33
|
+
chevron: 'size-(--text-icon-22) -translate-x-px text-icon-22'
|
|
34
34
|
},
|
|
35
35
|
sm: {
|
|
36
36
|
leading: [
|
|
37
37
|
buttonSizes.sm.base,
|
|
38
|
-
'ps-spacing-200 pe-spacing-150 [--split-active:
|
|
38
|
+
'ps-spacing-200 pe-spacing-150 [--split-active:var(--radius-md)] [--split-inner:var(--radius-xs)]'
|
|
39
39
|
],
|
|
40
40
|
leadingIcon: buttonSizes.sm.icon,
|
|
41
41
|
trailing: [
|
|
42
42
|
buttonSizes.sm.base,
|
|
43
|
-
'w-spacing-600 [--split-active:
|
|
43
|
+
'w-spacing-600 [--split-active:var(--radius-md)] [--split-inner:var(--radius-xs)]'
|
|
44
44
|
],
|
|
45
|
-
chevron: 'size-
|
|
45
|
+
chevron: 'size-(--text-icon-22) -translate-x-px text-icon-22'
|
|
46
46
|
},
|
|
47
47
|
md: {
|
|
48
48
|
leading: [
|
|
49
49
|
buttonSizes.md.base,
|
|
50
|
-
'px-spacing-300 [--split-active:
|
|
50
|
+
'px-spacing-300 [--split-active:var(--radius-md)] [--split-inner:var(--radius-xs)]'
|
|
51
51
|
],
|
|
52
52
|
leadingIcon: buttonSizes.md.icon,
|
|
53
53
|
trailing: [
|
|
54
54
|
buttonSizes.md.base,
|
|
55
|
-
'w-spacing-700 [--split-active:
|
|
55
|
+
'w-spacing-700 [--split-active:var(--radius-md)] [--split-inner:var(--radius-xs)]'
|
|
56
56
|
],
|
|
57
|
-
chevron: 'size-
|
|
57
|
+
chevron: 'size-(--text-icon-26) -translate-x-spacing-25 text-icon-26'
|
|
58
58
|
},
|
|
59
59
|
lg: {
|
|
60
60
|
leading: [
|
|
61
61
|
buttonSizes.lg.base,
|
|
62
|
-
'px-spacing-600 [--split-active:
|
|
62
|
+
'px-spacing-600 [--split-active:var(--radius-lg-increased)] [--split-inner:var(--radius-sm)]'
|
|
63
63
|
],
|
|
64
64
|
leadingIcon: buttonSizes.lg.icon,
|
|
65
|
-
trailing: [
|
|
66
|
-
|
|
65
|
+
trailing: [
|
|
66
|
+
buttonSizes.lg.base,
|
|
67
|
+
'w-24 [--split-active:var(--radius-lg-increased)] [--split-inner:var(--radius-sm)]'
|
|
68
|
+
],
|
|
69
|
+
chevron: 'size-(--text-icon-38) -translate-x-[3px] text-icon-38'
|
|
67
70
|
},
|
|
68
71
|
xl: {
|
|
69
72
|
leading: [
|
|
70
73
|
buttonSizes.xl.base,
|
|
71
|
-
'px-spacing-800 [--split-active:
|
|
74
|
+
'px-spacing-800 [--split-active:var(--radius-lg-increased)] [--split-inner:var(--radius-md)]'
|
|
72
75
|
],
|
|
73
76
|
leadingIcon: buttonSizes.xl.icon,
|
|
74
|
-
trailing: [
|
|
75
|
-
|
|
77
|
+
trailing: [
|
|
78
|
+
buttonSizes.xl.base,
|
|
79
|
+
'w-34 [--split-active:var(--radius-lg-increased)] [--split-inner:var(--radius-md)]'
|
|
80
|
+
],
|
|
81
|
+
chevron: 'size-(--text-icon-50) -translate-x-spacing-75 text-icon-50'
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
},
|