@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,77 @@
1
+ import type * as React from "react";
2
+ import type { MD3Size } from "../../../types/md3";
3
+
4
+ export type ButtonGroupVariant = "standard" | "connected" | "navbar";
5
+ export type ButtonGroupOrientation = "horizontal" | "vertical";
6
+
7
+ /**
8
+ * Thuộc tính truyền vào cho thành phần nhóm nút (Button Group).
9
+ */
10
+ export interface ButtonGroupProps
11
+ extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {
12
+ /**
13
+ * Cấu trúc hiển thị của nhóm nút:
14
+ * - `standard`: Các nút cách xa và có khoảng cách độc lập với nhau (gap).
15
+ * - `connected`: Các nút nối liền khung viền với nhau để tạo thành dạng Segmented Button.
16
+ * - `navbar`: Nút hiển thị dưới dạng thanh điều hướng với active pill chuyển động (Sliding Indicator).
17
+ * @default "standard"
18
+ */
19
+ variant?: ButtonGroupVariant;
20
+ /**
21
+ * Hướng sắp xếp của các nút trong nhóm.
22
+ * @default "horizontal"
23
+ */
24
+ orientation?: ButtonGroupOrientation;
25
+ /**
26
+ * Đặt thành `true` nếu bạn muốn nhóm hiển thị dạng `standard` giãn đều lấp đầy toàn bộ khu vực chứa (container).
27
+ * @default false
28
+ */
29
+ fullWidth?: boolean;
30
+ /**
31
+ * Áp dụng thống nhất chung một kích thước (`size`) cho tất cả các con trong nhóm (ghi đè kích thước lẻ từng nút).
32
+ */
33
+ size?: MD3Size;
34
+ /**
35
+ * Bật/tắt hiệu ứng thu phóng độ rộng / khoảng đệm (Morphing Width) khi nhấn vào các nút (áp dụng cho nhóm `standard`).
36
+ * @default true
37
+ */
38
+ morphingWidth?: boolean;
39
+ /**
40
+ * Tỷ lệ mở rộng chiều rộng của nút khi được nhấn (áp dụng cho nhóm `standard` ngang).
41
+ * Theo MD3 Spec, mặc định là 0.15 (15%).
42
+ * @default 0.15
43
+ */
44
+ expandedRatio?: number;
45
+ /**
46
+ * Tự động hiển thị biểu tượng (icon) Check khi một nút trạng thái nằm trong nhóm được chỉ định là `selected={true}`.
47
+ * @default false
48
+ */
49
+ showCheck?: boolean;
50
+ /**
51
+ * Điều khiển hành vi hiển thị Icon (áp dụng chính cho `navbar`).
52
+ * - `selected`: Chỉ hiển thị icon trên mục được chọn.
53
+ * - `all`: Hiển thị trên tất cả.
54
+ * - `none`: Ẩn toàn bộ icon.
55
+ */
56
+ iconBehavior?: "selected" | "all" | "none";
57
+ /**
58
+ * Điều khiển hành vi hiển thị Label (áp dụng chính cho `navbar`).
59
+ * - `selected`: Chỉ hiển thị label trên mục được chọn.
60
+ * - `all`: Hiển thị trên tất cả.
61
+ * - `none`: Ẩn toàn bộ label.
62
+ */
63
+ labelBehavior?: "selected" | "all" | "none";
64
+ /**
65
+ * Tùy chọn: Bật chế độ Active Morphing (dựa trên Selection).
66
+ * Khi `true`, các nút sẽ tự động co dãn (tăng/giảm width) dựa trên việc nút nào đang được chọn,
67
+ * tạo hiệu ứng thay đổi kích thước mượt mà theo trạng thái.
68
+ * Khi `false` hoặc `undefined`, Group sẽ hoạt động dựa trên `isPressed` (nhấn chuột) như mặc định.
69
+ */
70
+ activeMorphing?: boolean;
71
+ /**
72
+ * Class CSS tùy chỉnh áp dụng cho từng button con trong nhóm.
73
+ * Hữu ích để thay đổi padding, min-width hoặc các thuộc tính khác một cách linh hoạt.
74
+ * Đối với variant `navbar`, class này sẽ được ưu tiên hơn so với các padding mặc định.
75
+ */
76
+ itemClassName?: string;
77
+ }
@@ -13,16 +13,16 @@
13
13
  import type { HTMLMotionProps } from "motion/react";
14
14
  import { AnimatePresence, domMax, LazyMotion, m } from "motion/react";
15
15
  import * as React from "react";
16
- import { cn } from "../lib/utils";
17
- import { LoadingIndicator } from "./loading-indicator";
18
- import { ProgressIndicator } from "./progress-indicator";
19
- import { Ripple, useRippleState } from "./ripple";
16
+ import { cn } from "../../../../lib/utils";
17
+ import { LoadingIndicator } from "../../../loading-indicator";
18
+ import { ProgressIndicator } from "../../../progress-indicator";
19
+ import { Ripple, useRippleState } from "../../../ripple";
20
20
  import {
21
21
  ICON_SPAN_VARIANTS,
22
22
  SPRING_TRANSITION,
23
23
  SPRING_TRANSITION_FAST,
24
- } from "./shared/constants";
25
- import { TouchTarget } from "./shared/touch-target";
24
+ } from "../../../shared/constants";
25
+ import { TouchTarget } from "../../../shared/touch-target";
26
26
 
27
27
  // ─────────────────────────────────────────────────────────────────────────────
28
28
  // Design Tokens
@@ -0,0 +1 @@
1
+ export * from "./fab";
@@ -20,10 +20,13 @@ import {
20
20
  useTransform,
21
21
  } from "motion/react";
22
22
  import * as React from "react";
23
- import { cn } from "../lib/utils";
24
- import { Ripple, useRippleState } from "./ripple";
25
- import { SPRING_TRANSITION, SPRING_TRANSITION_FAST } from "./shared/constants";
26
- import { TouchTarget } from "./shared/touch-target";
23
+ import { cn } from "../../../../lib/utils";
24
+ import { Ripple, useRippleState } from "../../../ripple";
25
+ import {
26
+ SPRING_TRANSITION,
27
+ SPRING_TRANSITION_FAST,
28
+ } from "../../../shared/constants";
29
+ import { TouchTarget } from "../../../shared/touch-target";
27
30
 
28
31
  // ─────────────────────────────────────────────────────────────────────────────
29
32
  // Design Tokens — MD3 FAB Menu Spec
@@ -0,0 +1 @@
1
+ export * from "./fab-menu";
@@ -0,0 +1,2 @@
1
+ export * from "./fab";
2
+ export * from "./fab-menu";
@@ -13,16 +13,16 @@
13
13
  import type { HTMLMotionProps } from "motion/react";
14
14
  import { AnimatePresence, domMax, LazyMotion, m } from "motion/react";
15
15
  import * as React from "react";
16
- import { cn } from "../lib/utils";
17
- import { LoadingIndicator } from "./loading-indicator";
18
- import { ProgressIndicator } from "./progress-indicator";
19
- import { Ripple, useRippleState } from "./ripple";
16
+ import { cn } from "../../../lib/utils";
17
+ import { LoadingIndicator } from "../../loading-indicator";
18
+ import { ProgressIndicator } from "../../progress-indicator";
19
+ import { Ripple, useRippleState } from "../../ripple";
20
20
  import {
21
21
  ICON_SPAN_VARIANTS,
22
22
  SPRING_TRANSITION,
23
23
  SPRING_TRANSITION_FAST,
24
- } from "./shared/constants";
25
- import { TouchTarget } from "./shared/touch-target";
24
+ } from "../../shared/constants";
25
+ import { TouchTarget } from "../../shared/touch-target";
26
26
 
27
27
  // ─────────────────────────────────────────────────────────────────────────────
28
28
  // Design Tokens
@@ -0,0 +1 @@
1
+ export * from "./icon-button";
@@ -0,0 +1,4 @@
1
+ export * from "./button";
2
+ export * from "./button-group";
3
+ export * from "./fabs";
4
+ export * from "./icon-button";
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { createElement, useCallback, useEffect, useState } from "react";
4
4
  import { cn } from "../lib/utils";
5
- import { Button } from "./button";
5
+ import { Button } from "./buttons/button";
6
6
  import { Icon } from "./icon";
7
7
  import { ScrollArea } from "./scroll-area";
8
8
 
package/src/ui/dialog.tsx CHANGED
@@ -14,17 +14,14 @@ import * as RadixDialog from "@radix-ui/react-dialog";
14
14
  import { AnimatePresence, m } from "motion/react";
15
15
  import * as React from "react";
16
16
  import { cn } from "../lib/utils";
17
+ import { IconButton } from "./buttons/icon-button";
17
18
  import { Icon } from "./icon";
18
- import { IconButton } from "./icon-button";
19
19
  import { ScrollArea } from "./scroll-area";
20
+ import { DEFAULT_SPATIAL_SPRING } from "./shared/motion-tokens";
20
21
 
21
22
  // ─── MD3 Spring Config (Expressive) ──────────────────────────────────────────
22
- const MD3_SPRING = {
23
- type: "spring" as const,
24
- stiffness: 400,
25
- damping: 30,
26
- mass: 1,
27
- };
23
+ // MD3 default.spatial: stiffness=380, dampingRatio=0.8 → Framer damping ≈ 31.19
24
+ const MD3_SPRING = DEFAULT_SPATIAL_SPRING;
28
25
 
29
26
  const MD3_OVERLAY_ANIM = {
30
27
  initial: { opacity: 0 },
package/src/ui/drawer.tsx CHANGED
@@ -3,15 +3,12 @@ import { AnimatePresence, m } from "motion/react";
3
3
  import * as React from "react";
4
4
  import { cn } from "../lib/utils";
5
5
  import { Icon } from "./icon";
6
+ import { DEFAULT_SPATIAL_SPRING } from "./shared/motion-tokens";
6
7
 
7
8
  // ─── MD3 Expressive Drawer Animation ─────────────────────────────────────────
8
- // Slide từ dưới lên, spring physics giống Google Material's "Emphasized" easing
9
- const MD3_DRAWER_SPRING = {
10
- type: "spring" as const,
11
- stiffness: 350,
12
- damping: 35,
13
- mass: 0.9,
14
- };
9
+ // Slide from bottom, MD3 default.spatial spring
10
+ // (stiffness=380, dampingRatio=0.8 → Framer damping ≈ 31.19)
11
+ const MD3_DRAWER_SPRING = DEFAULT_SPATIAL_SPRING;
15
12
 
16
13
  const MD3_DRAWER_ANIM = {
17
14
  initial: { y: "100%", opacity: 0.6 },
@@ -1,28 +1,22 @@
1
1
  // ─── MD3 Expressive Menu — Framer Motion Animation Variants ──────────────────
2
- // FastSpatial: mirrors MotionSchemeKeyTokens.FastSpatial
3
- // Android: spring(stiffness=380, dampingRatio=0.7)
4
- // Framer: damping = 2 × 0.7 × √(380 × 1) 27.3 → use 28
5
- // FastEffects: mirrors MotionSchemeKeyTokens.FastEffects
6
- // Android: duration=150ms, FastOutLinearIn
7
- // Framer: duration=0.15, ease=[0.4, 0, 1, 1]
2
+ // FastSpatial: md.sys.motion.spring.fast.spatial
3
+ // MD3: stiffness=800, dampingRatio=0.6 → Framer damping ≈ 33.94
4
+ // FastEffects: md.sys.motion.spring.fast.effects (CSS fallback used for exit)
5
+ // MD3: duration=150ms, cubic-bezier(0.31, 0.94, 0.34, 1.00)
8
6
 
9
- import type { Transition, Variants } from "motion/react";
7
+ import type { Variants } from "motion/react";
8
+ import {
9
+ FAST_EFFECTS_SPRING,
10
+ FAST_SPATIAL_SPRING,
11
+ } from "../shared/motion-tokens";
10
12
 
11
- // ─── Shared spring/easing specs ───────────────────────────────────────────────
13
+ export { FAST_EFFECTS_SPRING, FAST_SPATIAL_SPRING };
12
14
 
13
- /** FastSpatial spring — used for shape morphing and spatial enter animations */
14
- export const FAST_SPATIAL_SPRING: Transition = {
15
- type: "spring",
16
- stiffness: 380,
17
- damping: 28,
18
- mass: 1,
19
- };
20
-
21
- /** FastEffects transition — used for opacity and exit animations */
22
- export const FAST_EFFECTS_TRANSITION: Transition = {
15
+ /** FastEffects CSS-fallback transition — used for opacity/exit animations */
16
+ export const FAST_EFFECTS_TRANSITION = {
23
17
  duration: 0.15,
24
- ease: [0.4, 0, 1, 1], // FastOutLinearIn
25
- };
18
+ ease: [0.31, 0.94, 0.34, 1.0] as [number, number, number, number],
19
+ } as const;
26
20
 
27
21
  // ─── Menu popup container ─────────────────────────────────────────────────────
28
22
 
@@ -2,6 +2,8 @@
2
2
  // Sourced from: SegmentedMenuTokens.kt, StandardMenuTokens.kt, VibrantMenuTokens.kt,
3
3
  // MenuTokens.kt, MenuDefaults.kt, ListTokens.kt
4
4
 
5
+ import { cornerRadius } from "@bug-on/md3-tokens";
6
+
5
7
  // ─── Spacing (px → dp) ────────────────────────────────────────────────────────
6
8
 
7
9
  /** Horizontal padding for menu items: 16dp (ItemLeadingSpace / ItemTrailingSpace) */
@@ -64,23 +66,23 @@ export const BASELINE_ITEM_SHAPE = "rounded-none";
64
66
  */
65
67
  export const GROUP_SHAPES = {
66
68
  /** Active standalone group shape: CornerLarge all corners (16px) */
67
- standaloneActive: "16px",
69
+ standaloneActive: `${cornerRadius.large}px`,
68
70
  /**
69
71
  * Active leading group shape: top=CornerLarge(16px), bottom=CornerSmall(8px)
70
72
  * Source: SegmentedMenuTokens — LeadingContainerShape:
71
73
  * topStart=CornerLarge, topEnd=CornerLarge, bottomStart=CornerSmall, bottomEnd=CornerSmall
72
74
  */
73
- leadingActive: "16px 16px 8px 8px",
75
+ leadingActive: `${cornerRadius.large}px ${cornerRadius.large}px ${cornerRadius.small}px ${cornerRadius.small}px`,
74
76
  /** Active middle group shape: CornerExtraSmall all corners (4px) */
75
- middleActive: "4px",
77
+ middleActive: `${cornerRadius.extraSmall}px`,
76
78
  /**
77
79
  * Active trailing group shape: top=CornerSmall(8px), bottom=CornerLarge(16px)
78
80
  * Source: SegmentedMenuTokens — TrailingContainerShape:
79
81
  * topStart=CornerSmall, topEnd=CornerSmall, bottomStart=CornerLarge, bottomEnd=CornerLarge
80
82
  */
81
- trailingActive: "8px 8px 16px 16px",
83
+ trailingActive: `${cornerRadius.small}px ${cornerRadius.small}px ${cornerRadius.large}px ${cornerRadius.large}px`,
82
84
  /** Inactive (default, pre-hover) shape for all groups: CornerExtraSmall (4px) */
83
- inactive: "4px",
85
+ inactive: `${cornerRadius.extraSmall}px`,
84
86
  } as const;
85
87
 
86
88
  /**
@@ -616,9 +616,14 @@ describe("Menu Internals", () => {
616
616
  expect(screen.getByText("A")).toBeInTheDocument();
617
617
  });
618
618
 
619
- // 3. Animation variants check
620
- it("FAST_SPATIAL_SPRING has correct spring parameters", () => {
621
- expect(FAST_SPATIAL_SPRING.stiffness).toBe(380);
622
- expect(FAST_SPATIAL_SPRING.damping).toBe(28);
619
+ // 3. Animation variants check — values must match MD3 fast.spatial token
620
+ // md.sys.motion.spring.fast.spatial: stiffness=800, dampingRatio=0.6
621
+ // Framer damping = 2 × 0.6 × √800 ≈ 33.94
622
+ it("FAST_SPATIAL_SPRING has correct MD3 spring parameters", () => {
623
+ expect(FAST_SPATIAL_SPRING.stiffness).toBe(800);
624
+ expect((FAST_SPATIAL_SPRING as { damping: number }).damping).toBeCloseTo(
625
+ 33.94,
626
+ 1,
627
+ );
623
628
  });
624
629
  });
@@ -1,7 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { cva } from "class-variance-authority";
4
- import { AnimatePresence, domMax, LazyMotion, m, type Transition } from "motion/react";
4
+ import {
5
+ AnimatePresence,
6
+ domMax,
7
+ LazyMotion,
8
+ m,
9
+ type Transition,
10
+ } from "motion/react";
5
11
  import * as React from "react";
6
12
  import { cn } from "../lib/utils";
7
13
  import { Icon } from "./icon";
@@ -416,7 +422,9 @@ export const NavigationBarComponent = React.forwardRef<
416
422
 
417
423
  return (
418
424
  <LazyMotion features={domMax} strict>
419
- <NavigationBarContext.Provider value={{ variant, itemLayout, activeIndicatorTransition }}>
425
+ <NavigationBarContext.Provider
426
+ value={{ variant, itemLayout, activeIndicatorTransition }}
427
+ >
420
428
  <m.nav
421
429
  ref={ref}
422
430
  role="navigation"
@@ -424,7 +432,13 @@ export const NavigationBarComponent = React.forwardRef<
424
432
  className={navBaseClasses}
425
433
  style={style}
426
434
  initial={false}
427
- animate={{ y: isVisible ? "0%" : "100%" }}
435
+ animate={{
436
+ y: isVisible
437
+ ? "0%"
438
+ : variant === "xr"
439
+ ? "calc(100% + 40px)"
440
+ : "100%",
441
+ }}
428
442
  transition={{ type: "tween", duration: 0.3, ease: "easeInOut" }}
429
443
  >
430
444
  <div
@@ -432,7 +446,9 @@ export const NavigationBarComponent = React.forwardRef<
432
446
  aria-orientation="horizontal"
433
447
  className={cn(
434
448
  "flex w-full h-full mx-auto",
435
- variant === "xr" ? "gap-0 min-[600px]:gap-1.5" : "max-w-7xl gap-1.5",
449
+ variant === "xr"
450
+ ? "gap-0 min-[600px]:gap-1.5"
451
+ : "max-w-7xl gap-1.5",
436
452
  )}
437
453
  >
438
454
  {children}
@@ -1,5 +1,11 @@
1
1
  import { cva } from "class-variance-authority";
2
- import { AnimatePresence, domMax, LazyMotion, m, type Transition } from "motion/react";
2
+ import {
3
+ AnimatePresence,
4
+ domMax,
5
+ LazyMotion,
6
+ m,
7
+ type Transition,
8
+ } from "motion/react";
3
9
  import * as React from "react";
4
10
  import { createPortal } from "react-dom";
5
11
  import { cn } from "../lib/utils";
@@ -66,8 +72,10 @@ const railContainerVariants = cva(
66
72
  {
67
73
  variants: {
68
74
  variant: {
69
- collapsed: "items-center h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
70
- expanded: "items-start h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
75
+ collapsed:
76
+ "items-center h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
77
+ expanded:
78
+ "items-start h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
71
79
  modal:
72
80
  "bg-m3-surface shadow-lg rounded-r-[var(--m3-shape-corner-large)] h-full pt-11 pb-4",
73
81
  xr: "h-fit py-5 rounded-[48px] shadow-xl bg-m3-surface border border-white/5",
@@ -459,9 +467,7 @@ const NavigationRailComponent = React.forwardRef<
459
467
  [ref],
460
468
  );
461
469
 
462
- const navBaseClasses = cn(
463
- railContainerVariants({ variant, narrow }),
464
- );
470
+ const navBaseClasses = cn(railContainerVariants({ variant, narrow }));
465
471
  const modalPositioning = isModal ? "fixed left-0 top-0 z-[100]" : "";
466
472
 
467
473
  const navHeaderSpacing = (() => {
@@ -563,7 +569,11 @@ const NavigationRailComponent = React.forwardRef<
563
569
 
564
570
  const finalNavElement = isSpatial ? spatialWrapper : navElement;
565
571
 
566
- const contextValue = { variant, labelVisibility, activeIndicatorTransition };
572
+ const contextValue = {
573
+ variant,
574
+ labelVisibility,
575
+ activeIndicatorTransition,
576
+ };
567
577
 
568
578
  if (isModal) {
569
579
  if (typeof document === "undefined") return null;
@@ -22,7 +22,7 @@ import { AnimatePresence, m, useReducedMotion } from "motion/react";
22
22
  import * as React from "react";
23
23
  import { createPortal } from "react-dom";
24
24
  import { cn } from "../../lib/utils";
25
- import { IconButton } from "../icon-button";
25
+ import { IconButton } from "../buttons/icon-button";
26
26
  import { AnimatedPlaceholder } from "./animated-placeholder";
27
27
  import { useSearchViewFocus } from "./hooks/use-search-view-focus";
28
28
  import {
@@ -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
- // ─── Animation Constants ──────────────────────────────────────────────────────
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;
@@ -1,5 +1,5 @@
1
1
  import type * as React from "react";
2
- import { IconButton } from "../icon-button";
2
+ import { IconButton } from "../buttons/icon-button";
3
3
  import { SEARCH_COLORS } from "./search.tokens";
4
4
 
5
5
  interface TrailingActionProps {
@@ -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 critically-damped spring — used for border-radius morphing.
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 critically-damped spring — used for icon/content scale animations.
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
- * Higher bounce for the "pop" effect per MD3 Expressive spec.
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.45,
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