@bug-on/md3-react 3.0.2 → 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.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +12 -11
  2. package/dist/index.css +107 -0
  3. package/dist/index.d.mts +1426 -1039
  4. package/dist/index.d.ts +1426 -1039
  5. package/dist/index.js +3830 -2820
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +3818 -2822
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +11 -6
  10. package/scripts/copy-assets.js +113 -8
  11. package/src/index.ts +59 -19
  12. package/src/test/button.test.tsx +1 -1
  13. package/src/ui/app-bar/app-bar.tokens.ts +5 -24
  14. package/src/ui/badge.tsx +2 -1
  15. package/src/ui/buttons/button/button-tokens.ts +118 -0
  16. package/src/ui/{button.test.tsx → buttons/button/button.test.tsx} +0 -21
  17. package/src/ui/buttons/button/button.tsx +381 -0
  18. package/src/ui/buttons/button/index.ts +3 -0
  19. package/src/ui/buttons/button/types.ts +90 -0
  20. package/src/ui/buttons/button-group/button-group-defaults.ts +95 -0
  21. package/src/ui/buttons/button-group/button-group-tokens.ts +20 -0
  22. package/src/ui/{button-group.test.tsx → buttons/button-group/button-group.test.tsx} +9 -10
  23. package/src/ui/buttons/button-group/button-group.tsx +699 -0
  24. package/src/ui/buttons/button-group/index.ts +8 -0
  25. package/src/ui/buttons/button-group/types.ts +77 -0
  26. package/src/ui/{fab.tsx → buttons/fabs/fab/fab.tsx} +6 -6
  27. package/src/ui/buttons/fabs/fab/index.ts +1 -0
  28. package/src/ui/{fab-menu.tsx → buttons/fabs/fab-menu/fab-menu.tsx} +7 -4
  29. package/src/ui/buttons/fabs/fab-menu/index.ts +1 -0
  30. package/src/ui/buttons/fabs/index.ts +2 -0
  31. package/src/ui/{icon-button.tsx → buttons/icon-button/icon-button.tsx} +6 -6
  32. package/src/ui/buttons/icon-button/index.ts +1 -0
  33. package/src/ui/buttons/index.ts +4 -0
  34. package/src/ui/code-block.tsx +1 -1
  35. package/src/ui/dialog.tsx +4 -7
  36. package/src/ui/drawer.tsx +4 -7
  37. package/src/ui/menu/menu-animations.ts +14 -20
  38. package/src/ui/menu/menu-tokens.ts +7 -5
  39. package/src/ui/menu/menu.test.tsx +9 -4
  40. package/src/ui/navigation-bar.tsx +20 -4
  41. package/src/ui/navigation-rail.tsx +17 -7
  42. package/src/ui/search/search-view-fullscreen.tsx +1 -1
  43. package/src/ui/search/search.tokens.ts +9 -43
  44. package/src/ui/search/trailing-action.tsx +1 -1
  45. package/src/ui/shared/constants.ts +25 -27
  46. package/src/ui/shared/motion-tokens.ts +238 -0
  47. package/src/ui/snackbar/snackbar.tsx +4 -6
  48. package/src/ui/switch/switch.tsx +12 -18
  49. package/src/ui/text-field/text-field.tokens.ts +12 -12
  50. package/src/ui/text-field/text-field.tsx +31 -19
  51. package/src/ui/theme-provider/index.tsx +1 -5
  52. package/src/ui/toc.tsx +1 -1
  53. package/src/ui/toolbar/__snapshots__/bottom-docked-toolbar.test.tsx.snap +51 -0
  54. package/src/ui/toolbar/__snapshots__/floating-toolbar-with-fab.test.tsx.snap +113 -0
  55. package/src/ui/toolbar/__snapshots__/floating-toolbar.test.tsx.snap +169 -0
  56. package/src/ui/toolbar/bottom-docked-toolbar.test.tsx +114 -0
  57. package/src/ui/toolbar/docked-toolbar.tsx +186 -0
  58. package/src/ui/toolbar/floating-toolbar-with-fab.test.tsx +139 -0
  59. package/src/ui/toolbar/floating-toolbar-with-fab.tsx +199 -0
  60. package/src/ui/toolbar/floating-toolbar.test.tsx +230 -0
  61. package/src/ui/toolbar/floating-toolbar.tsx +344 -0
  62. package/src/ui/toolbar/index.ts +35 -0
  63. package/src/ui/toolbar/toolbar-colors.ts +37 -0
  64. package/src/ui/toolbar/toolbar-context.tsx +13 -0
  65. package/src/ui/toolbar/toolbar-divider.test.tsx +54 -0
  66. package/src/ui/toolbar/toolbar-divider.tsx +73 -0
  67. package/src/ui/toolbar/toolbar-icon-button.test.tsx +68 -0
  68. package/src/ui/toolbar/toolbar-icon-button.tsx +136 -0
  69. package/src/ui/toolbar/toolbar-scroll-behavior.ts +140 -0
  70. package/src/ui/toolbar/toolbar-tokens.ts +51 -0
  71. package/test-clip.html +31 -0
  72. package/test-shadow.html +5 -1
  73. package/test-width.html +34 -0
  74. package/src/ui/button-group.tsx +0 -350
  75. package/src/ui/button.tsx +0 -665
  76. package/test-render.tsx +0 -4
  77. /package/src/ui/{fab.test.tsx → buttons/fabs/fab/fab.test.tsx} +0 -0
  78. /package/src/ui/{fab-menu.test.tsx → buttons/fabs/fab-menu/fab-menu.test.tsx} +0 -0
  79. /package/src/ui/{icon-button.test.tsx → buttons/icon-button/icon-button.test.tsx} +0 -0
  80. /package/src/ui/{Text.tsx → text.tsx} +0 -0
@@ -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 { IconButton } from "../icon-button";
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
- const SNACKBAR_SPRING = {
48
- type: "spring" as const,
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 },
@@ -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
- * FastSpatial spring equivalent to MotionSchemeKeyTokens.FastSpatial.
30
- * Used for thumb translation and size morph on checked state change.
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 = { duration: 0.2, ease: "easeInOut" } as const;
43
-
44
- /** State layer spring for hover/focus overlay. */
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
- : STATE_LAYER_SPRING;
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-m3-surface-container-highest)",
15
+ filledBg: "var(--md-sys-color-surface-container-highest)",
16
16
  /** Input text color */
17
- inputText: "var(--color-m3-on-surface)",
17
+ inputText: "var(--md-sys-color-on-surface)",
18
18
  /** Label (unfloated) + icons + prefix/suffix + supporting text */
19
- onSurfaceVariant: "var(--color-m3-on-surface-variant)",
19
+ onSurfaceVariant: "var(--md-sys-color-on-surface-variant)",
20
20
  /** Focused active indicator / outline, floated label */
21
- primary: "var(--color-m3-primary)",
21
+ primary: "var(--md-sys-color-primary)",
22
22
  /** Error indicator / outline / label / icon / supporting text */
23
- error: "var(--color-m3-error)",
23
+ error: "var(--md-sys-color-error)",
24
24
  /** Outlined border (enabled) */
25
- outline: "var(--color-m3-outline)",
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-m3-surface-container-highest)] rounded-tl-[4px] rounded-tr-[4px] rounded-bl-none rounded-br-none",
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-m3-on-surface)] caret-[var(--color-m3-primary)] placeholder:text-[var(--color-m3-on-surface-variant)]",
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-m3-on-surface-variant)] px-4",
95
- errorText: "text-xs text-[var(--color-m3-error)] px-4",
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-m3-on-surface-variant)] select-none shrink-0",
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-m3-on-surface)] opacity-0 transition-opacity duration-150 pointer-events-none rounded-[inherit]",
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 = dense ? TF_SIZE.denseHeight : TF_SIZE.height;
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 self-end",
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
- ? "bg-[var(--color-m3-surface-container-highest)] rounded-tl-sm rounded-tr-sm"
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
- variant === "filled"
328
- ? dense
329
- ? "pt-5 pb-2"
330
- : "pt-6 pb-2"
331
- : dense
332
- ? "py-3"
333
- : "py-4",
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 = dense ? "h-12" : "h-14";
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
- {label && (
403
+ {resolvedLabel && (
392
404
  <FloatingLabel
393
- text={label}
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);
package/src/ui/toc.tsx CHANGED
@@ -139,7 +139,7 @@ export function TableOfContents({
139
139
  aria-label="On this page"
140
140
  className={cn("pl-6 flex flex-col h-full", className)}
141
141
  >
142
- <h4 className="text-xs font-bold text-m3-on-surface-variant uppercase tracking-widest mb-4 sm:hidden lg:block">
142
+ <h4 className="text-xs font-bold text-m3-on-surface-variant uppercase tracking-widest mb-4 hidden lg:block">
143
143
  On this page
144
144
  </h4>
145
145
  <ScrollArea
@@ -0,0 +1,51 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`BottomDockedToolbar > snapshots: with leading and trailing content 1`] = `
4
+ <div
5
+ aria-label="Bottom Docked Toolbar"
6
+ class="fixed bottom-0 left-0 right-0 w-full z-40 flex items-center h-16 justify-between shadow-[0_-1px_3px_rgba(0,0,0,0.1)] pointer-events-auto"
7
+ role="toolbar"
8
+ style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); background-color: var(--toolbar-bg); color: var(--toolbar-color); padding-left: 16px; padding-right: 16px; transform: none;"
9
+ >
10
+ <div
11
+ class="flex items-center justify-start flex-1 shrink-0"
12
+ >
13
+ <span>
14
+ Lead
15
+ </span>
16
+ </div>
17
+ <div
18
+ class="flex items-center justify-center flex-1 shrink-0"
19
+ >
20
+ Center
21
+ </div>
22
+ <div
23
+ class="flex items-center justify-end flex-1 shrink-0"
24
+ >
25
+ <span>
26
+ Trail
27
+ </span>
28
+ </div>
29
+ </div>
30
+ `;
31
+
32
+ exports[`BottomDockedToolbar > snapshots: without optional content 1`] = `
33
+ <div
34
+ aria-label="Bottom Docked Toolbar"
35
+ class="fixed bottom-0 left-0 right-0 w-full z-40 flex items-center h-16 justify-between shadow-[0_-1px_3px_rgba(0,0,0,0.1)] pointer-events-auto"
36
+ role="toolbar"
37
+ style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); background-color: var(--toolbar-bg); color: var(--toolbar-color); padding-left: 16px; padding-right: 16px; transform: none;"
38
+ >
39
+ <div
40
+ class="flex items-center justify-start flex-1 shrink-0"
41
+ />
42
+ <div
43
+ class="flex items-center justify-center flex-1 shrink-0"
44
+ >
45
+ Center
46
+ </div>
47
+ <div
48
+ class="flex items-center justify-end flex-1 shrink-0"
49
+ />
50
+ </div>
51
+ `;