@bug-on/md3-react 3.0.1 → 3.0.3
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/.turbo/turbo-build.log +42 -42
- package/CHANGELOG.md +10 -0
- package/dist/index.css +107 -0
- package/dist/index.d.mts +1491 -1053
- package/dist/index.d.ts +1491 -1053
- package/dist/index.js +4457 -3156
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4394 -3109
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -6
- package/scripts/copy-assets.js +113 -8
- package/src/index.ts +66 -18
- package/src/test/button.test.tsx +1 -1
- package/src/ui/app-bar/app-bar.tokens.ts +5 -24
- package/src/ui/badge.tsx +2 -1
- package/src/ui/buttons/button/button-tokens.ts +118 -0
- package/src/ui/{button.test.tsx → buttons/button/button.test.tsx} +0 -21
- package/src/ui/buttons/button/button.tsx +381 -0
- package/src/ui/buttons/button/index.ts +3 -0
- package/src/ui/buttons/button/types.ts +90 -0
- package/src/ui/buttons/button-group/button-group-defaults.ts +95 -0
- package/src/ui/buttons/button-group/button-group-tokens.ts +20 -0
- package/src/ui/{button-group.test.tsx → buttons/button-group/button-group.test.tsx} +9 -10
- package/src/ui/buttons/button-group/button-group.tsx +699 -0
- package/src/ui/buttons/button-group/index.ts +8 -0
- package/src/ui/buttons/button-group/types.ts +77 -0
- package/src/ui/{fab.tsx → buttons/fabs/fab/fab.tsx} +6 -6
- package/src/ui/buttons/fabs/fab/index.ts +1 -0
- package/src/ui/{fab-menu.tsx → buttons/fabs/fab-menu/fab-menu.tsx} +7 -4
- package/src/ui/buttons/fabs/fab-menu/index.ts +1 -0
- package/src/ui/buttons/fabs/index.ts +2 -0
- package/src/ui/{icon-button.tsx → buttons/icon-button/icon-button.tsx} +6 -6
- package/src/ui/buttons/icon-button/index.ts +1 -0
- package/src/ui/buttons/index.ts +4 -0
- package/src/ui/code-block.tsx +1 -1
- package/src/ui/dialog.tsx +4 -7
- package/src/ui/drawer.tsx +4 -7
- package/src/ui/menu/menu-animations.ts +14 -20
- package/src/ui/menu/menu-tokens.ts +7 -5
- package/src/ui/menu/menu.test.tsx +9 -4
- package/src/ui/navigation-bar.test.tsx +111 -0
- package/src/ui/navigation-bar.tsx +464 -0
- package/src/ui/navigation-rail.test.tsx +5 -4
- package/src/ui/navigation-rail.tsx +32 -23
- package/src/ui/scroll-area.tsx +4 -0
- package/src/ui/search/search-view-fullscreen.tsx +1 -1
- package/src/ui/search/search.tokens.ts +9 -43
- package/src/ui/search/trailing-action.tsx +1 -1
- package/src/ui/shared/constants.ts +25 -27
- package/src/ui/shared/motion-tokens.ts +238 -0
- package/src/ui/snackbar/snackbar.tsx +4 -6
- package/src/ui/switch/switch.tsx +12 -18
- package/src/ui/text-field/text-field.tokens.ts +12 -12
- package/src/ui/text-field/text-field.tsx +31 -19
- package/src/ui/theme-provider/index.tsx +1 -5
- package/src/ui/toc.tsx +1 -1
- package/src/ui/toolbar/__snapshots__/bottom-docked-toolbar.test.tsx.snap +51 -0
- package/src/ui/toolbar/__snapshots__/floating-toolbar-with-fab.test.tsx.snap +113 -0
- package/src/ui/toolbar/__snapshots__/floating-toolbar.test.tsx.snap +169 -0
- package/src/ui/toolbar/bottom-docked-toolbar.test.tsx +114 -0
- package/src/ui/toolbar/docked-toolbar.tsx +186 -0
- package/src/ui/toolbar/floating-toolbar-with-fab.test.tsx +139 -0
- package/src/ui/toolbar/floating-toolbar-with-fab.tsx +199 -0
- package/src/ui/toolbar/floating-toolbar.test.tsx +230 -0
- package/src/ui/toolbar/floating-toolbar.tsx +344 -0
- package/src/ui/toolbar/index.ts +35 -0
- package/src/ui/toolbar/toolbar-colors.ts +37 -0
- package/src/ui/toolbar/toolbar-context.tsx +13 -0
- package/src/ui/toolbar/toolbar-divider.test.tsx +54 -0
- package/src/ui/toolbar/toolbar-divider.tsx +73 -0
- package/src/ui/toolbar/toolbar-icon-button.test.tsx +68 -0
- package/src/ui/toolbar/toolbar-icon-button.tsx +136 -0
- package/src/ui/toolbar/toolbar-scroll-behavior.ts +140 -0
- package/src/ui/toolbar/toolbar-tokens.ts +51 -0
- package/test-clip.html +31 -0
- package/test-shadow.html +5 -1
- package/test-width.html +34 -0
- package/src/ui/button-group.tsx +0 -350
- package/src/ui/button.tsx +0 -665
- package/test-render.tsx +0 -4
- package/test_output.txt +0 -164
- package/test_output_v2.txt +0 -5
- /package/src/ui/{fab.test.tsx → buttons/fabs/fab/fab.test.tsx} +0 -0
- /package/src/ui/{fab-menu.test.tsx → buttons/fabs/fab-menu/fab-menu.test.tsx} +0 -0
- /package/src/ui/{icon-button.test.tsx → buttons/icon-button/icon-button.test.tsx} +0 -0
- /package/src/ui/{Text.tsx → text.tsx} +0 -0
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
* @see docs/m3/search/SearchBarTokens.kt
|
|
10
10
|
* @see docs/m3/search/SearchViewTokens.kt
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_SPATIAL_SPRING,
|
|
14
|
+
FAST_SPATIAL_SPRING,
|
|
15
|
+
SLOW_SPATIAL_SPRING,
|
|
16
|
+
} from "../shared/motion-tokens";
|
|
13
17
|
// ─── Dimensional Tokens ───────────────────────────────────────────────────────
|
|
14
18
|
|
|
15
19
|
/**
|
|
@@ -87,48 +91,10 @@ export const SEARCH_TYPOGRAPHY = {
|
|
|
87
91
|
bodyLarge: "text-[16px] leading-6 font-normal tracking-[0.5px]",
|
|
88
92
|
} as const;
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
export const SEARCH_BAR_EXPAND_SPRING = FAST_SPATIAL_SPRING;
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
* Spring animation for SearchBar width expand (inactive → active).
|
|
94
|
-
* Matches MD3 FastSpatial motion scheme.
|
|
95
|
-
*/
|
|
96
|
-
export const SEARCH_BAR_EXPAND_SPRING = {
|
|
97
|
-
type: "spring" as const,
|
|
98
|
-
stiffness: 380,
|
|
99
|
-
damping: 38,
|
|
100
|
-
mass: 1,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Spring animation for Docked SearchView dropdown reveal (slide + fade).
|
|
105
|
-
* Offset Y: -8px on enter, opacity 0→1.
|
|
106
|
-
*/
|
|
107
|
-
export const SEARCH_DOCKED_REVEAL_SPRING = {
|
|
108
|
-
type: "spring" as const,
|
|
109
|
-
stiffness: 400,
|
|
110
|
-
damping: 35,
|
|
111
|
-
mass: 0.8,
|
|
112
|
-
};
|
|
96
|
+
export const SEARCH_DOCKED_REVEAL_SPRING = DEFAULT_SPATIAL_SPRING;
|
|
113
97
|
|
|
114
|
-
|
|
115
|
-
* Spring animation for FullScreen SearchView shape morphing.
|
|
116
|
-
* Lower stiffness + mass gives a smoother pill→fullscreen morph.
|
|
117
|
-
*/
|
|
118
|
-
export const SEARCH_FULLSCREEN_SPRING = {
|
|
119
|
-
type: "spring" as const,
|
|
120
|
-
stiffness: 300,
|
|
121
|
-
damping: 30,
|
|
122
|
-
mass: 0.9,
|
|
123
|
-
};
|
|
98
|
+
export const SEARCH_FULLSCREEN_SPRING = SLOW_SPATIAL_SPRING;
|
|
124
99
|
|
|
125
|
-
|
|
126
|
-
* Exit transition for SearchBar when mode="popLayout" is used.
|
|
127
|
-
* Fast fade-out so SearchView can claim the layoutId quickly.
|
|
128
|
-
*/
|
|
129
|
-
export const SEARCH_BAR_EXIT_SPRING = {
|
|
130
|
-
type: "spring" as const,
|
|
131
|
-
stiffness: 500,
|
|
132
|
-
damping: 40,
|
|
133
|
-
mass: 0.6,
|
|
134
|
-
};
|
|
100
|
+
export const SEARCH_BAR_EXIT_SPRING = FAST_SPATIAL_SPRING;
|
|
@@ -9,57 +9,55 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Target, TargetAndTransition, Transition } from "motion/react";
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_EFFECTS_SPRING,
|
|
14
|
+
DEFAULT_SPATIAL_SPRING,
|
|
15
|
+
FAST_EFFECTS_SPRING,
|
|
16
|
+
} from "./motion-tokens";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
DEFAULT_EFFECTS_SPRING,
|
|
20
|
+
DEFAULT_SPATIAL_SPRING,
|
|
21
|
+
FAST_EFFECTS_SPRING,
|
|
22
|
+
FAST_SPATIAL_SPRING,
|
|
23
|
+
getMotionSpring,
|
|
24
|
+
getMotionTransitionCSS,
|
|
25
|
+
SLOW_EFFECTS_SPRING,
|
|
26
|
+
SLOW_SPATIAL_SPRING,
|
|
27
|
+
} from "./motion-tokens";
|
|
12
28
|
|
|
13
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
30
|
// Spring Transitions
|
|
15
31
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
32
|
|
|
17
33
|
/**
|
|
18
|
-
* Fast
|
|
19
|
-
*
|
|
20
|
-
* - Duration: 200ms
|
|
21
|
-
* - Bounce: 0 (no overshoot → prevents negative radius jitter)
|
|
34
|
+
* Fast spring — used for border-radius morphing, icon swaps.
|
|
35
|
+
* Maps to MD3 `fast.effects` (critically damped, stiffness 3800).
|
|
22
36
|
*
|
|
23
37
|
* @example
|
|
24
38
|
* ```tsx
|
|
25
39
|
* <m.button transition={{ borderRadius: SPRING_TRANSITION_FAST }}>...</m.button>
|
|
26
40
|
* ```
|
|
27
41
|
*/
|
|
28
|
-
export const SPRING_TRANSITION_FAST: Transition =
|
|
29
|
-
type: "spring",
|
|
30
|
-
bounce: 0,
|
|
31
|
-
duration: 0.2,
|
|
32
|
-
} as const;
|
|
42
|
+
export const SPRING_TRANSITION_FAST: Transition = FAST_EFFECTS_SPRING;
|
|
33
43
|
|
|
34
44
|
/**
|
|
35
|
-
* Standard
|
|
36
|
-
*
|
|
37
|
-
* - Duration: 300ms
|
|
38
|
-
* - Bounce: 0 (no overshoot)
|
|
45
|
+
* Standard spring — used for icon/content scale animations.
|
|
46
|
+
* Maps to MD3 `default.effects` (critically damped, stiffness 1600).
|
|
39
47
|
*
|
|
40
48
|
* @example
|
|
41
49
|
* ```tsx
|
|
42
50
|
* <m.span transition={SPRING_TRANSITION}>...</m.span>
|
|
43
51
|
* ```
|
|
44
52
|
*/
|
|
45
|
-
export const SPRING_TRANSITION: Transition =
|
|
46
|
-
type: "spring",
|
|
47
|
-
bounce: 0,
|
|
48
|
-
duration: 0.3,
|
|
49
|
-
} as const;
|
|
53
|
+
export const SPRING_TRANSITION: Transition = DEFAULT_EFFECTS_SPRING;
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
56
|
* MD3 Expressive spring — active indicator expand/collapse.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* - Duration: 400ms
|
|
56
|
-
* - Bounce: 0.35 (spring overshoot → lò xo)
|
|
57
|
+
* Maps to MD3 `default.spatial` (dampingRatio: 0.8, stiffness: 380).
|
|
58
|
+
* Gentle bounce for the "pop" effect per MD3 Expressive spec.
|
|
57
59
|
*/
|
|
58
|
-
export const SPRING_TRANSITION_EXPRESSIVE: Transition =
|
|
59
|
-
type: "spring",
|
|
60
|
-
bounce: 0.35,
|
|
61
|
-
duration: 0.4,
|
|
62
|
-
} as const;
|
|
60
|
+
export const SPRING_TRANSITION_EXPRESSIVE: Transition = DEFAULT_SPATIAL_SPRING;
|
|
63
61
|
|
|
64
62
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
63
|
// Icon Span Motion Variants
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ui/shared/motion-tokens.ts
|
|
3
|
+
*
|
|
4
|
+
* Framer Motion spring presets derived from the official MD3 Expressive
|
|
5
|
+
* motion physics system (May 2025).
|
|
6
|
+
*
|
|
7
|
+
* Usage hierarchy:
|
|
8
|
+
* 1. Import a named preset (e.g. `FAST_SPATIAL_SPRING`) for convenience.
|
|
9
|
+
* 2. Call `getMotionSpring(speed, type)` when you need to pick dynamically.
|
|
10
|
+
* 3. Call `getMotionTransitionCSS(speed, type, scheme?)` for pure-CSS contexts.
|
|
11
|
+
*
|
|
12
|
+
* @see https://m3.material.io/styles/motion/overview/how-it-works
|
|
13
|
+
* @see https://m3.material.io/styles/motion/overview/specs
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Transition } from "motion/react";
|
|
17
|
+
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
// MD3 Spring Token Structure
|
|
20
|
+
//
|
|
21
|
+
// MD3 defines springs as dampingRatio (0–1) + stiffness.
|
|
22
|
+
// Framer Motion uses absolute `damping` coefficient.
|
|
23
|
+
//
|
|
24
|
+
// Conversion (with Framer's default mass = 1):
|
|
25
|
+
// damping = 2 × dampingRatio × √stiffness
|
|
26
|
+
//
|
|
27
|
+
// Computed values (rounded to 2 decimal places):
|
|
28
|
+
//
|
|
29
|
+
// fast spatial: 2 × 0.6 × √800 = 2 × 0.6 × 28.28 ≈ 33.94
|
|
30
|
+
// fast effects: 2 × 1.0 × √3800 = 2 × 61.64 ≈ 123.29
|
|
31
|
+
// default spatial: 2 × 0.8 × √380 = 2 × 0.8 × 19.49 ≈ 31.19
|
|
32
|
+
// default effects: 2 × 1.0 × √1600 = 2 × 40 = 80
|
|
33
|
+
// slow spatial: 2 × 0.8 × √200 = 2 × 0.8 × 14.14 ≈ 22.63
|
|
34
|
+
// slow effects: 2 × 1.0 × √800 = 2 × 28.28 ≈ 56.57
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
// ── Spatial springs — use for position, size, border-radius, rotation, scale ─
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* MD3 Expressive — Fast Spatial Spring
|
|
41
|
+
* `md.sys.motion.spring.fast.spatial` (dampingRatio: 0.6, stiffness: 800)
|
|
42
|
+
*
|
|
43
|
+
* Use for: small components (buttons, switches, chips).
|
|
44
|
+
* Has notable bounce/overshoot — feels snappy and expressive.
|
|
45
|
+
*/
|
|
46
|
+
export const FAST_SPATIAL_SPRING: Transition = {
|
|
47
|
+
type: "spring",
|
|
48
|
+
stiffness: 800,
|
|
49
|
+
damping: 33.94,
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* MD3 Expressive — Default Spatial Spring
|
|
54
|
+
* `md.sys.motion.spring.default.spatial` (dampingRatio: 0.8, stiffness: 380)
|
|
55
|
+
*
|
|
56
|
+
* Use for: medium elements (bottom sheets, menus, navigation rail).
|
|
57
|
+
* Gentle overshoot — the go-to spring for most UI animations.
|
|
58
|
+
*/
|
|
59
|
+
export const DEFAULT_SPATIAL_SPRING: Transition = {
|
|
60
|
+
type: "spring",
|
|
61
|
+
stiffness: 380,
|
|
62
|
+
damping: 31.19,
|
|
63
|
+
} as const;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* MD3 Expressive — Slow Spatial Spring
|
|
67
|
+
* `md.sys.motion.spring.slow.spatial` (dampingRatio: 0.8, stiffness: 200)
|
|
68
|
+
*
|
|
69
|
+
* Use for: full-screen transitions and large layout shifts.
|
|
70
|
+
* Subtle, slow overshoot.
|
|
71
|
+
*/
|
|
72
|
+
export const SLOW_SPATIAL_SPRING: Transition = {
|
|
73
|
+
type: "spring",
|
|
74
|
+
stiffness: 200,
|
|
75
|
+
damping: 22.63,
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
// ── Effects springs — use for color, opacity (critically damped, no bounce) ──
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* MD3 Expressive — Fast Effects Spring
|
|
82
|
+
* `md.sys.motion.spring.fast.effects` (dampingRatio: 1.0, stiffness: 3800)
|
|
83
|
+
*
|
|
84
|
+
* Use for: color changes on switches, icon swaps, badge counts.
|
|
85
|
+
* Critically damped — very fast, no overshoot.
|
|
86
|
+
*/
|
|
87
|
+
export const FAST_EFFECTS_SPRING: Transition = {
|
|
88
|
+
type: "spring",
|
|
89
|
+
stiffness: 3800,
|
|
90
|
+
damping: 123.29,
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* MD3 Expressive — Default Effects Spring
|
|
95
|
+
* `md.sys.motion.spring.default.effects` (dampingRatio: 1.0, stiffness: 1600)
|
|
96
|
+
*
|
|
97
|
+
* Use for: opacity fades, color transitions for medium-sized elements.
|
|
98
|
+
* Critically damped — smooth with no overshoot.
|
|
99
|
+
*/
|
|
100
|
+
export const DEFAULT_EFFECTS_SPRING: Transition = {
|
|
101
|
+
type: "spring",
|
|
102
|
+
stiffness: 1600,
|
|
103
|
+
damping: 80,
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* MD3 Expressive — Slow Effects Spring
|
|
108
|
+
* `md.sys.motion.spring.slow.effects` (dampingRatio: 1.0, stiffness: 800)
|
|
109
|
+
*
|
|
110
|
+
* Use for: full-screen opacity/color transitions.
|
|
111
|
+
* Critically damped — slow and smooth.
|
|
112
|
+
*/
|
|
113
|
+
export const SLOW_EFFECTS_SPRING: Transition = {
|
|
114
|
+
type: "spring",
|
|
115
|
+
stiffness: 800,
|
|
116
|
+
damping: 56.57,
|
|
117
|
+
} as const;
|
|
118
|
+
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
// Dynamic helper
|
|
121
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
export type SpringSpeed = "fast" | "default" | "slow";
|
|
124
|
+
export type SpringAnimationType = "spatial" | "effects";
|
|
125
|
+
|
|
126
|
+
const springMap = {
|
|
127
|
+
fast: { spatial: FAST_SPATIAL_SPRING, effects: FAST_EFFECTS_SPRING },
|
|
128
|
+
default: { spatial: DEFAULT_SPATIAL_SPRING, effects: DEFAULT_EFFECTS_SPRING },
|
|
129
|
+
slow: { spatial: SLOW_SPATIAL_SPRING, effects: SLOW_EFFECTS_SPRING },
|
|
130
|
+
} as const;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Pick a Framer Motion spring by speed and type.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```tsx
|
|
137
|
+
* transition={getMotionSpring("fast", "spatial")}
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export function getMotionSpring(
|
|
141
|
+
speed: SpringSpeed,
|
|
142
|
+
type: SpringAnimationType,
|
|
143
|
+
): Transition {
|
|
144
|
+
return springMap[speed][type];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
148
|
+
// CSS transition strings
|
|
149
|
+
// For contexts where Framer Motion is not used (plain CSS / Tailwind).
|
|
150
|
+
// Uses official Web spring → CSS cubic-bezier conversion table.
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
const CSS_CURVES = {
|
|
154
|
+
expressive: {
|
|
155
|
+
fast: {
|
|
156
|
+
spatial: {
|
|
157
|
+
curve: "cubic-bezier(0.42, 1.67, 0.21, 0.90)",
|
|
158
|
+
durationMs: 350,
|
|
159
|
+
},
|
|
160
|
+
effects: {
|
|
161
|
+
curve: "cubic-bezier(0.31, 0.94, 0.34, 1.00)",
|
|
162
|
+
durationMs: 150,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
default: {
|
|
166
|
+
spatial: {
|
|
167
|
+
curve: "cubic-bezier(0.38, 1.21, 0.22, 1.00)",
|
|
168
|
+
durationMs: 500,
|
|
169
|
+
},
|
|
170
|
+
effects: {
|
|
171
|
+
curve: "cubic-bezier(0.34, 0.80, 0.34, 1.00)",
|
|
172
|
+
durationMs: 200,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
slow: {
|
|
176
|
+
spatial: {
|
|
177
|
+
curve: "cubic-bezier(0.39, 1.29, 0.35, 0.98)",
|
|
178
|
+
durationMs: 650,
|
|
179
|
+
},
|
|
180
|
+
effects: {
|
|
181
|
+
curve: "cubic-bezier(0.34, 0.88, 0.34, 1.00)",
|
|
182
|
+
durationMs: 300,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
standard: {
|
|
187
|
+
fast: {
|
|
188
|
+
spatial: {
|
|
189
|
+
curve: "cubic-bezier(0.27, 1.06, 0.18, 1.00)",
|
|
190
|
+
durationMs: 350,
|
|
191
|
+
},
|
|
192
|
+
effects: {
|
|
193
|
+
curve: "cubic-bezier(0.31, 0.94, 0.34, 1.00)",
|
|
194
|
+
durationMs: 150,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
default: {
|
|
198
|
+
spatial: {
|
|
199
|
+
curve: "cubic-bezier(0.27, 1.06, 0.18, 1.00)",
|
|
200
|
+
durationMs: 500,
|
|
201
|
+
},
|
|
202
|
+
effects: {
|
|
203
|
+
curve: "cubic-bezier(0.34, 0.80, 0.34, 1.00)",
|
|
204
|
+
durationMs: 200,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
slow: {
|
|
208
|
+
spatial: {
|
|
209
|
+
curve: "cubic-bezier(0.27, 1.06, 0.18, 1.00)",
|
|
210
|
+
durationMs: 750,
|
|
211
|
+
},
|
|
212
|
+
effects: {
|
|
213
|
+
curve: "cubic-bezier(0.34, 0.88, 0.34, 1.00)",
|
|
214
|
+
durationMs: 300,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
} as const;
|
|
219
|
+
|
|
220
|
+
export type MotionScheme = "expressive" | "standard";
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Returns a CSS `transition` value string using the official MD3 web curve
|
|
224
|
+
* conversion, e.g. `"500ms cubic-bezier(0.38, 1.21, 0.22, 1.00)"`.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* element.style.transition = `border-radius ${getMotionTransitionCSS("default", "spatial")}`;
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function getMotionTransitionCSS(
|
|
232
|
+
speed: SpringSpeed,
|
|
233
|
+
type: SpringAnimationType,
|
|
234
|
+
scheme: MotionScheme = "expressive",
|
|
235
|
+
): string {
|
|
236
|
+
const { curve, durationMs } = CSS_CURVES[scheme][speed][type];
|
|
237
|
+
return `${durationMs}ms ${curve}`;
|
|
238
|
+
}
|
|
@@ -27,8 +27,9 @@ import {
|
|
|
27
27
|
} from "motion/react";
|
|
28
28
|
import * as React from "react";
|
|
29
29
|
import { cn } from "../../lib/utils";
|
|
30
|
+
import { IconButton } from "../buttons/icon-button";
|
|
30
31
|
import { Icon } from "../icon";
|
|
31
|
-
import {
|
|
32
|
+
import { DEFAULT_SPATIAL_SPRING } from "../shared/motion-tokens";
|
|
32
33
|
|
|
33
34
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
34
35
|
|
|
@@ -44,11 +45,8 @@ const RESULT = {
|
|
|
44
45
|
|
|
45
46
|
// ─── Animation Config (Framer Motion — NOT CSS transitions) ──────────────────
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
bounce: 0.15,
|
|
50
|
-
duration: 0.4,
|
|
51
|
-
};
|
|
48
|
+
// MD3 default.spatial: stiffness=380, dampingRatio=0.8 → Framer damping ≈ 31.19
|
|
49
|
+
const SNACKBAR_SPRING = DEFAULT_SPATIAL_SPRING;
|
|
52
50
|
|
|
53
51
|
const SNACKBAR_ANIM = {
|
|
54
52
|
initial: { opacity: 0, y: 56, scale: 0.9 },
|
package/src/ui/switch/switch.tsx
CHANGED
|
@@ -20,32 +20,26 @@ import {
|
|
|
20
20
|
import * as React from "react";
|
|
21
21
|
import { cn } from "../../lib/utils";
|
|
22
22
|
import { Ripple, type RippleOrigin } from "../ripple";
|
|
23
|
+
import {
|
|
24
|
+
FAST_EFFECTS_SPRING,
|
|
25
|
+
FAST_SPATIAL_SPRING,
|
|
26
|
+
} from "../shared/motion-tokens";
|
|
23
27
|
import { SwitchColors, SwitchTokens } from "./switch.tokens";
|
|
24
28
|
import type { SwitchProps } from "./switch.types";
|
|
25
29
|
|
|
26
30
|
// ─── Animation constants ───────────────────────────────────────────────────────
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
const FAST_SPATIAL_SPRING = {
|
|
33
|
-
type: "spring",
|
|
34
|
-
stiffness: 500,
|
|
35
|
-
damping: 40,
|
|
36
|
-
} as const;
|
|
32
|
+
// FAST_SPATIAL_SPRING and FAST_EFFECTS_SPRING imported from motion-tokens:
|
|
33
|
+
// fast.spatial: stiffness=800, dampingRatio=0.6 → Framer damping≈33.94 (thumb movement/size)
|
|
34
|
+
// fast.effects: stiffness=3800, dampingRatio=1.0 → Framer damping≈123.29 (state layer)
|
|
37
35
|
|
|
38
36
|
/** Instant transition (SnapSpec equivalent) — used when thumb is pressed. */
|
|
39
37
|
const SNAP_TRANSITION = { duration: 0 } as const;
|
|
40
38
|
|
|
41
|
-
/** Color transition for track/thumb color changes. */
|
|
42
|
-
const COLOR_TRANSITION = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const STATE_LAYER_SPRING = {
|
|
46
|
-
type: "spring",
|
|
47
|
-
stiffness: 400,
|
|
48
|
-
damping: 30,
|
|
39
|
+
/** Color transition for track/thumb color changes. Uses fast.effects CSS curve. */
|
|
40
|
+
const COLOR_TRANSITION = {
|
|
41
|
+
duration: 0.15,
|
|
42
|
+
ease: [0.31, 0.94, 0.34, 1.0] as [number, number, number, number],
|
|
49
43
|
} as const;
|
|
50
44
|
|
|
51
45
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -223,7 +217,7 @@ const SwitchVisual = React.memo(function SwitchVisual({
|
|
|
223
217
|
|
|
224
218
|
const stateLayerTransition = prefersReduced
|
|
225
219
|
? { duration: 0 }
|
|
226
|
-
:
|
|
220
|
+
: FAST_EFFECTS_SPRING;
|
|
227
221
|
|
|
228
222
|
return (
|
|
229
223
|
<div
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
|
|
13
13
|
export const TF_COLORS = {
|
|
14
14
|
/** Filled container background */
|
|
15
|
-
filledBg: "var(--color-
|
|
15
|
+
filledBg: "var(--md-sys-color-surface-container-highest)",
|
|
16
16
|
/** Input text color */
|
|
17
|
-
inputText: "var(--color-
|
|
17
|
+
inputText: "var(--md-sys-color-on-surface)",
|
|
18
18
|
/** Label (unfloated) + icons + prefix/suffix + supporting text */
|
|
19
|
-
onSurfaceVariant: "var(--color-
|
|
19
|
+
onSurfaceVariant: "var(--md-sys-color-on-surface-variant)",
|
|
20
20
|
/** Focused active indicator / outline, floated label */
|
|
21
|
-
primary: "var(--color-
|
|
21
|
+
primary: "var(--md-sys-color-primary)",
|
|
22
22
|
/** Error indicator / outline / label / icon / supporting text */
|
|
23
|
-
error: "var(--color-
|
|
23
|
+
error: "var(--md-sys-color-error)",
|
|
24
24
|
/** Outlined border (enabled) */
|
|
25
|
-
outline: "var(--color-
|
|
25
|
+
outline: "var(--md-sys-color-outline)",
|
|
26
26
|
/** Transparent */
|
|
27
27
|
transparent: "transparent",
|
|
28
28
|
} as const;
|
|
@@ -78,12 +78,12 @@ export const TF_TYPOGRAPHY = {
|
|
|
78
78
|
export const TF_CLASSES = {
|
|
79
79
|
// Container
|
|
80
80
|
filledContainer:
|
|
81
|
-
"bg-[var(--color-
|
|
81
|
+
"bg-[var(--md-sys-color-surface-container-highest)] rounded-tl-[4px] rounded-tr-[4px] rounded-bl-none rounded-br-none",
|
|
82
82
|
outlinedContainer: "bg-transparent rounded-[4px]",
|
|
83
83
|
|
|
84
84
|
// Input
|
|
85
85
|
input:
|
|
86
|
-
"bg-transparent outline-none w-full text-base text-[var(--color-
|
|
86
|
+
"bg-transparent outline-none w-full text-base text-[var(--md-sys-color-on-surface)] caret-[var(--md-sys-color-primary)] placeholder:text-[var(--md-sys-color-on-surface-variant)]",
|
|
87
87
|
inputNoSpinner:
|
|
88
88
|
"[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
|
|
89
89
|
|
|
@@ -91,14 +91,14 @@ export const TF_CLASSES = {
|
|
|
91
91
|
disabled: "opacity-[0.38] pointer-events-none cursor-not-allowed",
|
|
92
92
|
|
|
93
93
|
// Supporting text
|
|
94
|
-
supportingText: "text-xs text-[var(--color-
|
|
95
|
-
errorText: "text-xs text-[var(--color-
|
|
94
|
+
supportingText: "text-xs text-[var(--md-sys-color-on-surface-variant)] px-4",
|
|
95
|
+
errorText: "text-xs text-[var(--md-sys-color-error)] px-4",
|
|
96
96
|
|
|
97
97
|
// Prefix / Suffix
|
|
98
98
|
prefixSuffix:
|
|
99
|
-
"text-base text-[var(--color-
|
|
99
|
+
"text-base text-[var(--md-sys-color-on-surface-variant)] select-none shrink-0",
|
|
100
100
|
|
|
101
101
|
// State layer (hover)
|
|
102
102
|
stateLayer:
|
|
103
|
-
"absolute inset-0 bg-[var(--color-
|
|
103
|
+
"absolute inset-0 bg-[var(--md-sys-color-on-surface)] opacity-0 transition-opacity duration-150 pointer-events-none rounded-[inherit]",
|
|
104
104
|
} as const;
|
|
@@ -18,12 +18,11 @@
|
|
|
18
18
|
* @see https://m3.material.io/components/text-fields/overview
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
"use client";
|
|
22
|
-
|
|
23
21
|
import { domMax, LazyMotion, useReducedMotion } from "motion/react";
|
|
24
22
|
import * as React from "react";
|
|
25
23
|
import { cn } from "../../lib/utils";
|
|
26
24
|
import { ScrollArea } from "../scroll-area";
|
|
25
|
+
import { useToolbarContext } from "../toolbar/toolbar-context";
|
|
27
26
|
import { ActiveIndicator } from "./subcomponents/active-indicator";
|
|
28
27
|
import { FloatingLabel } from "./subcomponents/floating-label";
|
|
29
28
|
import { LeadingIcon } from "./subcomponents/leading-icon";
|
|
@@ -107,6 +106,12 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
107
106
|
ref,
|
|
108
107
|
) => {
|
|
109
108
|
const prefersReduced = useReducedMotion() ?? false;
|
|
109
|
+
const toolbarContext = useToolbarContext();
|
|
110
|
+
const isInToolbar = !!toolbarContext;
|
|
111
|
+
|
|
112
|
+
// ── Props Override (Toolbar) ──────────────────────────────────────────
|
|
113
|
+
const resolvedDense = isInToolbar ? true : dense;
|
|
114
|
+
const resolvedLabel = isInToolbar ? undefined : label;
|
|
110
115
|
|
|
111
116
|
// ── IDs ───────────────────────────────────────────────────────────────
|
|
112
117
|
const generatedId = React.useId();
|
|
@@ -141,7 +146,9 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
141
146
|
errorProp ||
|
|
142
147
|
!!nativeError ||
|
|
143
148
|
(maxLength !== undefined && currentValue.length > maxLength);
|
|
144
|
-
const containerHeight =
|
|
149
|
+
const containerHeight = resolvedDense
|
|
150
|
+
? TF_SIZE.denseHeight
|
|
151
|
+
: TF_SIZE.height;
|
|
145
152
|
const showAsterisk = required && !noAsterisk;
|
|
146
153
|
|
|
147
154
|
// ── Refs ──────────────────────────────────────────────────────────────
|
|
@@ -270,7 +277,8 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
270
277
|
|
|
271
278
|
// ── Input classes ─────────────────────────────────────────────────────
|
|
272
279
|
const inputClass = cn(
|
|
273
|
-
"bg-transparent outline-none w-full
|
|
280
|
+
"bg-transparent outline-none w-full",
|
|
281
|
+
isInToolbar ? "self-center" : "self-end",
|
|
274
282
|
"text-base leading-6 text-m3-on-surface",
|
|
275
283
|
"caret-[var(--color-m3-primary)]",
|
|
276
284
|
"placeholder:text-m3-on-surface-variant/60",
|
|
@@ -315,22 +323,26 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
315
323
|
const containerClass = cn(
|
|
316
324
|
"relative flex items-center",
|
|
317
325
|
variant === "filled"
|
|
318
|
-
?
|
|
326
|
+
? isInToolbar
|
|
327
|
+
? "bg-transparent"
|
|
328
|
+
: "bg-[var(--color-m3-surface-container-highest)] rounded-tl-sm rounded-tr-sm"
|
|
319
329
|
: "bg-transparent rounded-sm",
|
|
320
330
|
fullWidth ? "w-full" : "w-fit",
|
|
321
331
|
);
|
|
322
332
|
|
|
323
333
|
const innerClass = cn(
|
|
324
|
-
"relative flex flex-col flex-1 min-w-0",
|
|
334
|
+
"relative flex flex-col flex-1 min-w-0 h-full",
|
|
325
335
|
paddingStart,
|
|
326
336
|
paddingEnd,
|
|
327
|
-
|
|
328
|
-
?
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
:
|
|
337
|
+
isInToolbar
|
|
338
|
+
? "justify-center"
|
|
339
|
+
: variant === "filled"
|
|
340
|
+
? resolvedDense
|
|
341
|
+
? "pt-5 pb-2"
|
|
342
|
+
: "pt-6 pb-2"
|
|
343
|
+
: resolvedDense
|
|
344
|
+
? "py-3"
|
|
345
|
+
: "py-4",
|
|
334
346
|
);
|
|
335
347
|
|
|
336
348
|
const wrapperClass = cn(
|
|
@@ -340,7 +352,7 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
340
352
|
className,
|
|
341
353
|
);
|
|
342
354
|
|
|
343
|
-
const containerHeightClass =
|
|
355
|
+
const containerHeightClass = resolvedDense ? "h-12" : "h-14";
|
|
344
356
|
|
|
345
357
|
// ── Render ────────────────────────────────────────────────────────────
|
|
346
358
|
return (
|
|
@@ -354,7 +366,7 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
354
366
|
)}
|
|
355
367
|
>
|
|
356
368
|
{/* Outlined border with animated notch */}
|
|
357
|
-
{variant === "outlined" && (
|
|
369
|
+
{variant === "outlined" && !isInToolbar && (
|
|
358
370
|
<OutlineContainer
|
|
359
371
|
isFloated={isFloated}
|
|
360
372
|
isFocused={isFocused}
|
|
@@ -367,7 +379,7 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
367
379
|
)}
|
|
368
380
|
|
|
369
381
|
{/* Hover state layer (filled only) */}
|
|
370
|
-
{variant === "filled" && (
|
|
382
|
+
{variant === "filled" && !isInToolbar && (
|
|
371
383
|
<div
|
|
372
384
|
aria-hidden="true"
|
|
373
385
|
className={cn(
|
|
@@ -388,9 +400,9 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
388
400
|
{/* Inner column: floating label + input row */}
|
|
389
401
|
<div className={innerClass}>
|
|
390
402
|
{/* Floating label — absolutely positioned over the inner region */}
|
|
391
|
-
{
|
|
403
|
+
{resolvedLabel && (
|
|
392
404
|
<FloatingLabel
|
|
393
|
-
text={
|
|
405
|
+
text={resolvedLabel}
|
|
394
406
|
isFloated={isFloated}
|
|
395
407
|
isFocused={isFocused}
|
|
396
408
|
isError={isError}
|
|
@@ -485,7 +497,7 @@ const TextFieldComponent = React.forwardRef<TextFieldHandle, TextFieldProps>(
|
|
|
485
497
|
)}
|
|
486
498
|
|
|
487
499
|
{/* Active indicator: bottom border line (filled variant only) */}
|
|
488
|
-
{variant === "filled" && (
|
|
500
|
+
{variant === "filled" && !isInToolbar && (
|
|
489
501
|
<ActiveIndicator
|
|
490
502
|
isFocused={isFocused}
|
|
491
503
|
isError={isError}
|
|
@@ -106,11 +106,7 @@ export function MD3ThemeProvider({
|
|
|
106
106
|
) as ThemeMode | null;
|
|
107
107
|
|
|
108
108
|
if (savedColor) setSourceColor(savedColor);
|
|
109
|
-
if (
|
|
110
|
-
savedMode === "light" ||
|
|
111
|
-
savedMode === "dark" ||
|
|
112
|
-
savedMode === "system"
|
|
113
|
-
)
|
|
109
|
+
if (savedMode === "light" || savedMode === "dark" || savedMode === "system")
|
|
114
110
|
setMode(savedMode);
|
|
115
111
|
|
|
116
112
|
setIsHydrated(true);
|