@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.
- package/.turbo/turbo-build.log +12 -11
- package/dist/index.css +107 -0
- package/dist/index.d.mts +1426 -1039
- package/dist/index.d.ts +1426 -1039
- package/dist/index.js +3830 -2820
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3818 -2822
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -6
- package/scripts/copy-assets.js +113 -8
- package/src/index.ts +59 -19
- 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.tsx +20 -4
- package/src/ui/navigation-rail.tsx +17 -7
- 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/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
|
@@ -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 "
|
|
17
|
-
import { LoadingIndicator } from "
|
|
18
|
-
import { ProgressIndicator } from "
|
|
19
|
-
import { Ripple, useRippleState } from "
|
|
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 "
|
|
25
|
-
import { TouchTarget } from "
|
|
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 "
|
|
24
|
-
import { Ripple, useRippleState } from "
|
|
25
|
-
import {
|
|
26
|
-
|
|
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";
|
|
@@ -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 "
|
|
17
|
-
import { LoadingIndicator } from "
|
|
18
|
-
import { ProgressIndicator } from "
|
|
19
|
-
import { Ripple, useRippleState } from "
|
|
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 "
|
|
25
|
-
import { TouchTarget } from "
|
|
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";
|
package/src/ui/code-block.tsx
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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 {
|
|
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
|
-
|
|
13
|
+
export { FAST_EFFECTS_SPRING, FAST_SPATIAL_SPRING };
|
|
12
14
|
|
|
13
|
-
/**
|
|
14
|
-
export const
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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 {
|
|
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
|
|
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={{
|
|
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"
|
|
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 {
|
|
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:
|
|
70
|
-
|
|
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 = {
|
|
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
|
-
|
|
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.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
|