@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bug-on/md3-react",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Material Design 3 Expressive React components",
|
|
5
5
|
"author": "Bug Ổn",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,14 @@
|
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/index.d.mts",
|
|
28
|
+
"default": "./dist/index.mjs"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
29
34
|
},
|
|
30
35
|
"./typography.css": {
|
|
31
36
|
"types": "./dist/typography.css.d.ts",
|
|
@@ -69,8 +74,8 @@
|
|
|
69
74
|
"class-variance-authority": "^0.7.1",
|
|
70
75
|
"clsx": "^2.1.1",
|
|
71
76
|
"tailwind-merge": "^3.3.1",
|
|
72
|
-
"@bug-on/md3-tokens": "3.0.
|
|
73
|
-
"@bug-on/md3-tailwind": "3.0.
|
|
77
|
+
"@bug-on/md3-tokens": "3.0.3",
|
|
78
|
+
"@bug-on/md3-tailwind": "3.0.3"
|
|
74
79
|
},
|
|
75
80
|
"devDependencies": {
|
|
76
81
|
"@testing-library/jest-dom": "^6.9.1",
|
package/scripts/copy-assets.js
CHANGED
|
@@ -26,16 +26,13 @@ if (fs.existsSync(srcCss)) {
|
|
|
26
26
|
console.log("✅ Copied typography.css to dist/typography.css");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Bundle index.css = md3-tokens CSS + react component base styles.
|
|
30
|
-
//
|
|
31
|
-
// and get all required design tokens automatically.
|
|
29
|
+
// Bundle index.css = md3-tokens CSS + react component base styles + @theme block.
|
|
30
|
+
// Users only need: @import "@bug-on/md3-react/index.css" — zero manual config.
|
|
32
31
|
const tokensCssDir = path.join(__dirname, "../../tokens/dist");
|
|
33
32
|
const tokensColorsCss = path.join(tokensCssDir, "colors.css");
|
|
34
33
|
const tokensShapeCss = path.join(tokensCssDir, "shape.css");
|
|
35
34
|
|
|
36
|
-
const bundleParts = [
|
|
37
|
-
"/* @bug-on/md3-tokens — MD3 System Color Tokens */",
|
|
38
|
-
];
|
|
35
|
+
const bundleParts = ["/* @bug-on/md3-tokens — MD3 System Color Tokens */"];
|
|
39
36
|
|
|
40
37
|
if (fs.existsSync(tokensColorsCss)) {
|
|
41
38
|
bundleParts.push(fs.readFileSync(tokensColorsCss, "utf-8"));
|
|
@@ -58,9 +55,117 @@ if (fs.existsSync(srcIndexCss)) {
|
|
|
58
55
|
);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
// ── Tailwind v4 @theme block ─────────────────────────────────────────────────
|
|
59
|
+
// Registers MD3 system tokens as Tailwind design tokens so utility classes
|
|
60
|
+
// like bg-m3-primary, text-m3-on-surface, rounded-m3-xl work automatically.
|
|
61
|
+
// Tailwind v4 processes @theme blocks inside @imported CSS files.
|
|
62
|
+
const tailwindThemeBlock = `
|
|
63
|
+
/* @bug-on/md3-react — Tailwind v4 Theme Registration */
|
|
64
|
+
/* Registers MD3 tokens as Tailwind utilities. No manual @theme mapping needed. */
|
|
65
|
+
@theme {
|
|
66
|
+
/* ── Colors: Primary ── */
|
|
67
|
+
--color-m3-primary: var(--md-sys-color-primary);
|
|
68
|
+
--color-m3-on-primary: var(--md-sys-color-on-primary);
|
|
69
|
+
--color-m3-primary-container: var(--md-sys-color-primary-container);
|
|
70
|
+
--color-m3-on-primary-container: var(--md-sys-color-on-primary-container);
|
|
71
|
+
--color-m3-primary-fixed: var(--md-sys-color-primary-fixed);
|
|
72
|
+
--color-m3-primary-fixed-dim: var(--md-sys-color-primary-fixed-dim);
|
|
73
|
+
--color-m3-on-primary-fixed: var(--md-sys-color-on-primary-fixed);
|
|
74
|
+
--color-m3-on-primary-fixed-variant: var(--md-sys-color-on-primary-fixed-variant);
|
|
75
|
+
|
|
76
|
+
/* ── Colors: Secondary ── */
|
|
77
|
+
--color-m3-secondary: var(--md-sys-color-secondary);
|
|
78
|
+
--color-m3-on-secondary: var(--md-sys-color-on-secondary);
|
|
79
|
+
--color-m3-secondary-container: var(--md-sys-color-secondary-container);
|
|
80
|
+
--color-m3-on-secondary-container: var(--md-sys-color-on-secondary-container);
|
|
81
|
+
--color-m3-secondary-fixed: var(--md-sys-color-secondary-fixed);
|
|
82
|
+
--color-m3-secondary-fixed-dim: var(--md-sys-color-secondary-fixed-dim);
|
|
83
|
+
--color-m3-on-secondary-fixed: var(--md-sys-color-on-secondary-fixed);
|
|
84
|
+
--color-m3-on-secondary-fixed-variant: var(--md-sys-color-on-secondary-fixed-variant);
|
|
85
|
+
|
|
86
|
+
/* ── Colors: Tertiary ── */
|
|
87
|
+
--color-m3-tertiary: var(--md-sys-color-tertiary);
|
|
88
|
+
--color-m3-on-tertiary: var(--md-sys-color-on-tertiary);
|
|
89
|
+
--color-m3-tertiary-container: var(--md-sys-color-tertiary-container);
|
|
90
|
+
--color-m3-on-tertiary-container: var(--md-sys-color-on-tertiary-container);
|
|
91
|
+
--color-m3-tertiary-fixed: var(--md-sys-color-tertiary-fixed);
|
|
92
|
+
--color-m3-tertiary-fixed-dim: var(--md-sys-color-tertiary-fixed-dim);
|
|
93
|
+
--color-m3-on-tertiary-fixed: var(--md-sys-color-on-tertiary-fixed);
|
|
94
|
+
--color-m3-on-tertiary-fixed-variant: var(--md-sys-color-on-tertiary-fixed-variant);
|
|
95
|
+
|
|
96
|
+
/* ── Colors: Error ── */
|
|
97
|
+
--color-m3-error: var(--md-sys-color-error);
|
|
98
|
+
--color-m3-on-error: var(--md-sys-color-on-error);
|
|
99
|
+
--color-m3-error-container: var(--md-sys-color-error-container);
|
|
100
|
+
--color-m3-on-error-container: var(--md-sys-color-on-error-container);
|
|
101
|
+
|
|
102
|
+
/* ── Colors: Surface ── */
|
|
103
|
+
--color-m3-surface: var(--md-sys-color-surface);
|
|
104
|
+
--color-m3-surface-dim: var(--md-sys-color-surface-dim);
|
|
105
|
+
--color-m3-surface-bright: var(--md-sys-color-surface-bright);
|
|
106
|
+
--color-m3-on-surface: var(--md-sys-color-on-surface);
|
|
107
|
+
--color-m3-surface-variant: var(--md-sys-color-surface-variant);
|
|
108
|
+
--color-m3-on-surface-variant: var(--md-sys-color-on-surface-variant);
|
|
109
|
+
--color-m3-surface-container-lowest: var(--md-sys-color-surface-container-lowest);
|
|
110
|
+
--color-m3-surface-container-low: var(--md-sys-color-surface-container-low);
|
|
111
|
+
--color-m3-surface-container: var(--md-sys-color-surface-container);
|
|
112
|
+
--color-m3-surface-container-high: var(--md-sys-color-surface-container-high);
|
|
113
|
+
--color-m3-surface-container-highest: var(--md-sys-color-surface-container-highest);
|
|
114
|
+
|
|
115
|
+
/* ── Colors: Inverse ── */
|
|
116
|
+
--color-m3-inverse-surface: var(--md-sys-color-inverse-surface);
|
|
117
|
+
--color-m3-inverse-on-surface: var(--md-sys-color-inverse-on-surface);
|
|
118
|
+
--color-m3-inverse-primary: var(--md-sys-color-inverse-primary);
|
|
119
|
+
|
|
120
|
+
/* ── Colors: Outline / Shadow / Scrim ── */
|
|
121
|
+
--color-m3-outline: var(--md-sys-color-outline);
|
|
122
|
+
--color-m3-outline-variant: var(--md-sys-color-outline-variant);
|
|
123
|
+
--color-m3-shadow: var(--md-sys-color-shadow);
|
|
124
|
+
--color-m3-scrim: var(--md-sys-color-scrim);
|
|
125
|
+
--color-m3-surface-tint: var(--md-sys-color-surface-tint);
|
|
126
|
+
|
|
127
|
+
/* ── Colors: Background ── */
|
|
128
|
+
--color-m3-background: var(--md-sys-color-background);
|
|
129
|
+
--color-m3-on-background: var(--md-sys-color-on-background);
|
|
130
|
+
|
|
131
|
+
/* ── Colors: Navigation Indicator ── */
|
|
132
|
+
--color-m3-indicator-active: var(--md-sys-color-indicator-active);
|
|
133
|
+
--color-m3-indicator-container: var(--md-sys-color-indicator-container);
|
|
134
|
+
--color-m3-indicator-contained-active: var(--md-sys-color-indicator-contained-active);
|
|
135
|
+
--color-m3-indicator-contained-container: var(--md-sys-color-indicator-contained-container);
|
|
136
|
+
--color-m3-indicator-track: var(--md-sys-color-indicator-track);
|
|
137
|
+
--color-m3-indicator-stop: var(--md-sys-color-indicator-stop);
|
|
138
|
+
|
|
139
|
+
/* ── Shape: Corner Radius Scale (10 levels, M3 Expressive) ── */
|
|
140
|
+
--radius-m3-none: var(--md-sys-shape-corner-none, 0px);
|
|
141
|
+
--radius-m3-xs: var(--md-sys-shape-corner-extra-small, 4px);
|
|
142
|
+
--radius-m3-sm: var(--md-sys-shape-corner-small, 8px);
|
|
143
|
+
--radius-m3-md: var(--md-sys-shape-corner-medium, 12px);
|
|
144
|
+
--radius-m3-lg: var(--md-sys-shape-corner-large, 16px);
|
|
145
|
+
--radius-m3-lg-inc: var(--md-sys-shape-corner-large-increased, 20px);
|
|
146
|
+
--radius-m3-xl: var(--md-sys-shape-corner-extra-large, 28px);
|
|
147
|
+
--radius-m3-xl-inc: var(--md-sys-shape-corner-extra-large-increased, 32px);
|
|
148
|
+
--radius-m3-xxl: var(--md-sys-shape-corner-extra-extra-large, 48px);
|
|
149
|
+
--radius-m3-full: var(--md-sys-shape-corner-full, 9999px);
|
|
150
|
+
|
|
151
|
+
/* ── Motion Easing ── */
|
|
152
|
+
--ease-m3-emphasized: cubic-bezier(0.2, 0, 0, 1);
|
|
153
|
+
--ease-m3-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);
|
|
154
|
+
--ease-m3-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
|
|
155
|
+
--ease-m3-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
156
|
+
--ease-m3-standard-accelerate: cubic-bezier(0.3, 0, 1, 1);
|
|
157
|
+
--ease-m3-standard-decelerate: cubic-bezier(0, 0, 0, 1);
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
bundleParts.push(tailwindThemeBlock);
|
|
63
161
|
|
|
162
|
+
fs.writeFileSync(distIndexCss, bundleParts.join("\n"));
|
|
163
|
+
console.log(
|
|
164
|
+
"✅ Built bundled index.css (tokens + @theme + component base) to dist/index.css",
|
|
165
|
+
);
|
|
166
|
+
console.log(
|
|
167
|
+
"✅ Tailwind v4 @theme block injected — bg-m3-primary etc. work without manual mapping",
|
|
168
|
+
);
|
|
64
169
|
|
|
65
170
|
// Copy Material Symbols CSS variants
|
|
66
171
|
const cssVariants = [
|
package/src/index.ts
CHANGED
|
@@ -62,10 +62,34 @@ export {
|
|
|
62
62
|
export type { BadgedBoxProps, BadgeProps } from "./ui/badge";
|
|
63
63
|
// Badge — MD3 Expressive status indicator
|
|
64
64
|
export { Badge, BadgedBox } from "./ui/badge";
|
|
65
|
-
export type { BaseButtonProps, ButtonProps } from "./ui/button";
|
|
66
|
-
export {
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
export type { BaseButtonProps, ButtonProps } from "./ui/buttons/button";
|
|
66
|
+
export {
|
|
67
|
+
BUTTON_COLOR_TOKENS,
|
|
68
|
+
BUTTON_SIZE_TOKENS,
|
|
69
|
+
Button,
|
|
70
|
+
} from "./ui/buttons/button";
|
|
71
|
+
export type {
|
|
72
|
+
ButtonGroupOrientation,
|
|
73
|
+
ButtonGroupProps,
|
|
74
|
+
ButtonGroupVariant,
|
|
75
|
+
} from "./ui/buttons/button-group";
|
|
76
|
+
export { ButtonGroup } from "./ui/buttons/button-group";
|
|
77
|
+
export type { FABPositionProps, FABProps } from "./ui/buttons/fabs/fab";
|
|
78
|
+
// Floating Action Button
|
|
79
|
+
export { FAB, FABPosition } from "./ui/buttons/fabs/fab";
|
|
80
|
+
export type {
|
|
81
|
+
FABMenuItemData,
|
|
82
|
+
FABMenuItemProps,
|
|
83
|
+
FABMenuProps,
|
|
84
|
+
ToggleFABProps,
|
|
85
|
+
} from "./ui/buttons/fabs/fab-menu";
|
|
86
|
+
// FAB Menu
|
|
87
|
+
export { FABMenu, FABMenuItem, ToggleFAB } from "./ui/buttons/fabs/fab-menu";
|
|
88
|
+
export type {
|
|
89
|
+
BaseIconButtonProps,
|
|
90
|
+
IconButtonProps,
|
|
91
|
+
} from "./ui/buttons/icon-button";
|
|
92
|
+
export { IconButton } from "./ui/buttons/icon-button";
|
|
69
93
|
export type { CardProps } from "./ui/card";
|
|
70
94
|
export { Card } from "./ui/card";
|
|
71
95
|
// Checkbox — MD3 Expressive tri-state checkbox
|
|
@@ -117,22 +141,9 @@ export {
|
|
|
117
141
|
DrawerTitle,
|
|
118
142
|
DrawerTrigger,
|
|
119
143
|
} from "./ui/drawer";
|
|
120
|
-
export type { FABPositionProps, FABProps } from "./ui/fab";
|
|
121
|
-
// Floating Action Button
|
|
122
|
-
export { FAB, FABPosition } from "./ui/fab";
|
|
123
|
-
export type {
|
|
124
|
-
FABMenuItemData,
|
|
125
|
-
FABMenuItemProps,
|
|
126
|
-
FABMenuProps,
|
|
127
|
-
ToggleFABProps,
|
|
128
|
-
} from "./ui/fab-menu";
|
|
129
|
-
// FAB Menu
|
|
130
|
-
export { FABMenu, FABMenuItem, ToggleFAB } from "./ui/fab-menu";
|
|
131
144
|
// Icon — Material Symbols variable font
|
|
132
145
|
export type { IconProps } from "./ui/icon";
|
|
133
146
|
export { Icon } from "./ui/icon";
|
|
134
|
-
export type { BaseIconButtonProps, IconButtonProps } from "./ui/icon-button";
|
|
135
|
-
export { IconButton } from "./ui/icon-button";
|
|
136
147
|
export type { LoadingIndicatorProps } from "./ui/loading-indicator";
|
|
137
148
|
export { LoadingIndicator } from "./ui/loading-indicator";
|
|
138
149
|
// MD3 Expressive Menu — Standard + Vibrant + shape morphing
|
|
@@ -198,8 +209,8 @@ export {
|
|
|
198
209
|
} from "./ui/menu";
|
|
199
210
|
// Navigation Bar
|
|
200
211
|
export type {
|
|
201
|
-
NavigationBarItemProps,
|
|
202
212
|
NavigationBarItemLayout,
|
|
213
|
+
NavigationBarItemProps,
|
|
203
214
|
NavigationBarProps,
|
|
204
215
|
NavigationBarVariant,
|
|
205
216
|
} from "./ui/navigation-bar";
|
|
@@ -289,7 +300,6 @@ export {
|
|
|
289
300
|
// Switch — MD3 Expressive toggle
|
|
290
301
|
export type { SwitchProps } from "./ui/switch";
|
|
291
302
|
export { Switch, SwitchColors, SwitchTokens } from "./ui/switch";
|
|
292
|
-
export { Text, type TextProps } from "./ui/Text";
|
|
293
303
|
// Tabs — MD3 Expressive navigation tabs
|
|
294
304
|
export type {
|
|
295
305
|
TabProps,
|
|
@@ -306,6 +316,9 @@ export {
|
|
|
306
316
|
TabsList,
|
|
307
317
|
TabsTokens,
|
|
308
318
|
} from "./ui/tabs";
|
|
319
|
+
// Text — MD3 Expressive Typography component
|
|
320
|
+
export type { TextProps } from "./ui/text";
|
|
321
|
+
export { Text } from "./ui/text";
|
|
309
322
|
// TextField — MD3 Expressive
|
|
310
323
|
export type {
|
|
311
324
|
TextFieldHandle,
|
|
@@ -319,6 +332,33 @@ export type { MD3ThemeProviderProps } from "./ui/theme-provider";
|
|
|
319
332
|
export { MD3ThemeProvider, useTheme, useThemeMode } from "./ui/theme-provider";
|
|
320
333
|
export type { TableOfContentsProps, ToCItem } from "./ui/toc";
|
|
321
334
|
export { TableOfContents } from "./ui/toc";
|
|
335
|
+
// Toolbar — MD3 Expressive
|
|
336
|
+
export type {
|
|
337
|
+
BottomDockedToolbarProps,
|
|
338
|
+
FloatingToolbarColors,
|
|
339
|
+
FloatingToolbarProps,
|
|
340
|
+
FloatingToolbarScrollBehavior,
|
|
341
|
+
FloatingToolbarWithFabProps,
|
|
342
|
+
ToolbarDividerProps,
|
|
343
|
+
ToolbarIconButtonProps,
|
|
344
|
+
ToolbarIconButtonSize,
|
|
345
|
+
ToolbarIconButtonVariant,
|
|
346
|
+
UseFloatingToolbarScrollBehaviorOptions,
|
|
347
|
+
} from "./ui/toolbar";
|
|
348
|
+
export {
|
|
349
|
+
BottomDockedToolbar,
|
|
350
|
+
HorizontalFloatingToolbar,
|
|
351
|
+
HorizontalFloatingToolbarWithFab,
|
|
352
|
+
standardFloatingToolbarColors,
|
|
353
|
+
ToolbarDivider,
|
|
354
|
+
ToolbarDividerTokens,
|
|
355
|
+
ToolbarIconButton,
|
|
356
|
+
ToolbarIconButtonTokens,
|
|
357
|
+
useFloatingToolbarScrollBehavior,
|
|
358
|
+
VerticalFloatingToolbar,
|
|
359
|
+
VerticalFloatingToolbarWithFab,
|
|
360
|
+
vibrantFloatingToolbarColors,
|
|
361
|
+
} from "./ui/toolbar";
|
|
322
362
|
// Tooltip — MD3 Expressive
|
|
323
363
|
export type {
|
|
324
364
|
CaretConfig,
|
package/src/test/button.test.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
|
-
import { Button } from "../ui/button";
|
|
4
|
+
import { Button } from "../ui/buttons/button";
|
|
5
5
|
|
|
6
6
|
describe("Button Loading State", () => {
|
|
7
7
|
it("does not render LoadingIndicator by default", () => {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* @see docs/m3/app-bars/
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { DEFAULT_SPATIAL_SPRING } from "../shared/motion-tokens";
|
|
18
|
+
|
|
17
19
|
// ─── Dimensional Tokens ───────────────────────────────────────────────────────
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -186,32 +188,11 @@ export const APP_BAR_COLOR_TRANSITION = {
|
|
|
186
188
|
* Spring animation for enterAlways behavior (hide/show on scroll direction).
|
|
187
189
|
* Equivalent to MD3 FastSpatial motion scheme.
|
|
188
190
|
*/
|
|
189
|
-
export const APP_BAR_ENTER_ALWAYS_SPRING =
|
|
190
|
-
type: "spring",
|
|
191
|
-
stiffness: 380,
|
|
192
|
-
damping: 40,
|
|
193
|
-
mass: 1,
|
|
194
|
-
} as const;
|
|
191
|
+
export const APP_BAR_ENTER_ALWAYS_SPRING = DEFAULT_SPATIAL_SPRING;
|
|
195
192
|
|
|
196
|
-
|
|
197
|
-
* Spring animation for Bottom App Bar hide/show.
|
|
198
|
-
* Slightly looser feel for bottom navigation.
|
|
199
|
-
*/
|
|
200
|
-
export const APP_BAR_BOTTOM_SPRING = {
|
|
201
|
-
type: "spring",
|
|
202
|
-
stiffness: 300,
|
|
203
|
-
damping: 30,
|
|
204
|
-
} as const;
|
|
193
|
+
export const APP_BAR_BOTTOM_SPRING = DEFAULT_SPATIAL_SPRING;
|
|
205
194
|
|
|
206
|
-
|
|
207
|
-
* SearchView appearance/disappearance transition.
|
|
208
|
-
* Uses spring for natural feel of expanding overlay.
|
|
209
|
-
*/
|
|
210
|
-
export const SEARCH_VIEW_SPRING = {
|
|
211
|
-
type: "spring",
|
|
212
|
-
stiffness: 400,
|
|
213
|
-
damping: 35,
|
|
214
|
-
} as const;
|
|
195
|
+
export const SEARCH_VIEW_SPRING = DEFAULT_SPATIAL_SPRING;
|
|
215
196
|
|
|
216
197
|
/**
|
|
217
198
|
* Title crossfade transition for flexible App Bars.
|
package/src/ui/badge.tsx
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from "motion/react";
|
|
33
33
|
import * as React from "react";
|
|
34
34
|
import { cn } from "../lib/utils";
|
|
35
|
+
import { FAST_SPATIAL_SPRING } from "./shared/motion-tokens";
|
|
35
36
|
|
|
36
37
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
38
|
// Types
|
|
@@ -159,7 +160,7 @@ const BadgeImpl = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
|
159
160
|
|
|
160
161
|
const springTransition = reducedMotion
|
|
161
162
|
? { duration: 0 }
|
|
162
|
-
:
|
|
163
|
+
: FAST_SPATIAL_SPRING;
|
|
163
164
|
|
|
164
165
|
const colorStyle: React.CSSProperties = {
|
|
165
166
|
...(containerColor && { backgroundColor: containerColor }),
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { cornerRadius } from "@bug-on/md3-tokens";
|
|
2
|
+
import type { Transition } from "motion";
|
|
3
|
+
import {
|
|
4
|
+
FAST_EFFECTS_SPRING,
|
|
5
|
+
FAST_SPATIAL_SPRING,
|
|
6
|
+
} from "../../shared/motion-tokens";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shape morphing spring animation for layout/width morphing.
|
|
10
|
+
* MD3: `fast.spatial` — stiffness 800, dampingRatio 0.6 (has bounce).
|
|
11
|
+
*/
|
|
12
|
+
export const BUTTON_SHAPE_MORPH_SPRING: Transition = FAST_SPATIAL_SPRING;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Spring for border-radius changes on press/release and shape toggle.
|
|
16
|
+
* MD3: `fast.effects` — stiffness 3800, critically damped (dampingRatio 1.0).
|
|
17
|
+
* No overshoot; border-radius never goes negative.
|
|
18
|
+
*/
|
|
19
|
+
export const BUTTON_RADIUS_SPRING: Transition = FAST_EFFECTS_SPRING;
|
|
20
|
+
|
|
21
|
+
export const ROUND_SHAPE_RADIUS = 9999;
|
|
22
|
+
|
|
23
|
+
export const BUTTON_SIZE_TOKENS = {
|
|
24
|
+
xs: {
|
|
25
|
+
height: "2rem", // 32dp
|
|
26
|
+
leadingSpace: "1rem", // 16dp
|
|
27
|
+
trailingSpace: "1rem", // 16dp
|
|
28
|
+
iconSize: "1.25rem", // 20dp
|
|
29
|
+
iconLabelSpace: "0.5rem", // 8dp
|
|
30
|
+
outlineWidth: 1, // 1dp
|
|
31
|
+
squareShapeRadius: cornerRadius.medium, // 12dp
|
|
32
|
+
pressedShapeRadius: cornerRadius.small, // 8dp
|
|
33
|
+
roundShapeRadius: 16,
|
|
34
|
+
},
|
|
35
|
+
sm: {
|
|
36
|
+
height: "2.5rem", // 40dp
|
|
37
|
+
leadingSpace: "1rem", // 16dp
|
|
38
|
+
trailingSpace: "1rem", // 16dp
|
|
39
|
+
iconSize: "1.25rem", // 20dp
|
|
40
|
+
iconLabelSpace: "0.5rem", // 8dp
|
|
41
|
+
outlineWidth: 1, // 1dp
|
|
42
|
+
squareShapeRadius: cornerRadius.medium, // 12dp
|
|
43
|
+
pressedShapeRadius: cornerRadius.small, // 8dp
|
|
44
|
+
roundShapeRadius: 20,
|
|
45
|
+
},
|
|
46
|
+
md: {
|
|
47
|
+
height: "3.5rem", // 56dp
|
|
48
|
+
leadingSpace: "1.5rem", // 24dp
|
|
49
|
+
trailingSpace: "1.5rem", // 24dp
|
|
50
|
+
iconSize: "1.5rem", // 24dp
|
|
51
|
+
iconLabelSpace: "0.5rem", // 8dp
|
|
52
|
+
outlineWidth: 1, // 1dp
|
|
53
|
+
squareShapeRadius: cornerRadius.large, // 16dp
|
|
54
|
+
pressedShapeRadius: cornerRadius.medium, // 12dp
|
|
55
|
+
roundShapeRadius: 28,
|
|
56
|
+
},
|
|
57
|
+
lg: {
|
|
58
|
+
height: "6rem", // 96dp
|
|
59
|
+
leadingSpace: "3rem", // 48dp
|
|
60
|
+
trailingSpace: "3rem", // 48dp
|
|
61
|
+
iconSize: "2rem", // 32dp
|
|
62
|
+
iconLabelSpace: "0.75rem", // 12dp
|
|
63
|
+
outlineWidth: 2, // 2dp
|
|
64
|
+
squareShapeRadius: cornerRadius.extraLarge, // 28dp
|
|
65
|
+
pressedShapeRadius: cornerRadius.large, // 16dp
|
|
66
|
+
roundShapeRadius: 48,
|
|
67
|
+
},
|
|
68
|
+
xl: {
|
|
69
|
+
height: "8.5rem", // 136dp
|
|
70
|
+
leadingSpace: "4rem", // 64dp
|
|
71
|
+
trailingSpace: "4rem", // 64dp
|
|
72
|
+
iconSize: "2.5rem", // 40dp
|
|
73
|
+
iconLabelSpace: "1rem", // 16dp
|
|
74
|
+
outlineWidth: 3, // 3dp
|
|
75
|
+
squareShapeRadius: cornerRadius.extraLarge, // 28dp
|
|
76
|
+
pressedShapeRadius: cornerRadius.large, // 16dp
|
|
77
|
+
roundShapeRadius: 68,
|
|
78
|
+
},
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
export const BUTTON_TYPOGRAPHY_TOKENS = {
|
|
82
|
+
xs: "text-sm font-medium tracking-wide", // labelLarge
|
|
83
|
+
sm: "text-sm font-medium tracking-wide", // labelLarge
|
|
84
|
+
md: "text-base font-medium", // titleMedium
|
|
85
|
+
lg: "text-xl font-normal", // headlineSmall
|
|
86
|
+
xl: "text-[2rem] font-normal leading-none", // headlineLarge
|
|
87
|
+
} as const;
|
|
88
|
+
|
|
89
|
+
export const BUTTON_COLOR_TOKENS = {
|
|
90
|
+
elevated: {
|
|
91
|
+
default: "bg-m3-surface-container-low text-m3-primary shadow-md",
|
|
92
|
+
selected: "bg-m3-primary text-m3-on-primary shadow-md",
|
|
93
|
+
unselected: "bg-m3-surface-container-low text-m3-primary shadow-md",
|
|
94
|
+
},
|
|
95
|
+
filled: {
|
|
96
|
+
default: "bg-m3-primary text-m3-on-primary",
|
|
97
|
+
selected: "bg-m3-primary text-m3-on-primary",
|
|
98
|
+
unselected: "bg-m3-surface-container text-m3-on-surface-variant",
|
|
99
|
+
},
|
|
100
|
+
tonal: {
|
|
101
|
+
default: "bg-m3-secondary-container text-m3-on-secondary-container",
|
|
102
|
+
selected: "bg-m3-secondary text-m3-on-secondary",
|
|
103
|
+
unselected: "bg-m3-secondary-container text-m3-on-secondary-container",
|
|
104
|
+
},
|
|
105
|
+
outlined: {
|
|
106
|
+
default:
|
|
107
|
+
"bg-transparent text-m3-on-surface-variant border-m3-outline-variant",
|
|
108
|
+
selected:
|
|
109
|
+
"bg-m3-inverse-surface text-m3-inverse-on-surface border-transparent",
|
|
110
|
+
unselected:
|
|
111
|
+
"bg-transparent text-m3-on-surface-variant border-m3-outline-variant",
|
|
112
|
+
},
|
|
113
|
+
text: {
|
|
114
|
+
default: "bg-transparent text-m3-primary",
|
|
115
|
+
selected: "bg-transparent text-m3-primary",
|
|
116
|
+
unselected: "bg-transparent text-m3-primary",
|
|
117
|
+
},
|
|
118
|
+
} as const;
|
|
@@ -4,14 +4,11 @@ import { fireEvent, render, screen } from "@testing-library/react";
|
|
|
4
4
|
import { describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { Button } from "./button";
|
|
6
6
|
|
|
7
|
-
// ── Inline SVG test icon (no external deps) ───────────────────────────────────
|
|
8
7
|
const TestIcon = () => (
|
|
9
8
|
<svg data-testid="test-icon" aria-hidden="true" viewBox="0 0 24 24" />
|
|
10
9
|
);
|
|
11
10
|
|
|
12
11
|
describe("Button Component", () => {
|
|
13
|
-
// ── Existing tests (preserved) ─────────────────────────────────────────────
|
|
14
|
-
|
|
15
12
|
it("renders correctly with default props", () => {
|
|
16
13
|
render(<Button>Click me</Button>);
|
|
17
14
|
const button = screen.getByRole("button");
|
|
@@ -56,8 +53,6 @@ describe("Button Component", () => {
|
|
|
56
53
|
fireEvent.pointerDown(button, { clientX: 10, clientY: 10 });
|
|
57
54
|
});
|
|
58
55
|
|
|
59
|
-
// ── New: Disabled state ────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
56
|
describe("disabled state", () => {
|
|
62
57
|
it("has disabled attribute when disabled prop is passed", () => {
|
|
63
58
|
render(<Button disabled>Disabled</Button>);
|
|
@@ -76,8 +71,6 @@ describe("Button Component", () => {
|
|
|
76
71
|
});
|
|
77
72
|
});
|
|
78
73
|
|
|
79
|
-
// ── New: Keyboard navigation ───────────────────────────────────────────────
|
|
80
|
-
|
|
81
74
|
describe("keyboard navigation", () => {
|
|
82
75
|
it("triggers onClick when Enter is pressed", () => {
|
|
83
76
|
const handleClick = vi.fn();
|
|
@@ -105,8 +98,6 @@ describe("Button Component", () => {
|
|
|
105
98
|
});
|
|
106
99
|
});
|
|
107
100
|
|
|
108
|
-
// ── New: Icon position ─────────────────────────────────────────────────────
|
|
109
|
-
|
|
110
101
|
describe("icon position", () => {
|
|
111
102
|
it("renders leading icon before label in DOM order", () => {
|
|
112
103
|
render(
|
|
@@ -153,7 +144,6 @@ describe("Button Component", () => {
|
|
|
153
144
|
</Button>,
|
|
154
145
|
);
|
|
155
146
|
const button = screen.getByRole("button");
|
|
156
|
-
// Icon wrapper span should be aria-hidden
|
|
157
147
|
const iconWrapper = button.querySelector(
|
|
158
148
|
"span[aria-hidden='true']:has([data-testid='test-icon'])",
|
|
159
149
|
);
|
|
@@ -178,8 +168,6 @@ describe("Button Component", () => {
|
|
|
178
168
|
});
|
|
179
169
|
});
|
|
180
170
|
|
|
181
|
-
// ── New: RTL layout ────────────────────────────────────────────────────────
|
|
182
|
-
|
|
183
171
|
describe("RTL layout", () => {
|
|
184
172
|
it("renders without crash inside dir=rtl container", () => {
|
|
185
173
|
const { container } = render(
|
|
@@ -215,13 +203,10 @@ describe("Button Component", () => {
|
|
|
215
203
|
const labelIndex = spans.findIndex((s) =>
|
|
216
204
|
s.textContent?.includes("RTL Trailing"),
|
|
217
205
|
);
|
|
218
|
-
// DOM order is still trailing (CSS handles visual flip in RTL via flex)
|
|
219
206
|
expect(iconIndex).toBeGreaterThan(labelIndex);
|
|
220
207
|
});
|
|
221
208
|
});
|
|
222
209
|
|
|
223
|
-
// ── New: aria-label ────────────────────────────────────────────────────────
|
|
224
|
-
|
|
225
210
|
describe("aria-label", () => {
|
|
226
211
|
it("sets aria-label to children string as fallback", () => {
|
|
227
212
|
render(<Button>Submit</Button>);
|
|
@@ -245,14 +230,10 @@ describe("Button Component", () => {
|
|
|
245
230
|
<span>Node child</span>
|
|
246
231
|
</Button>,
|
|
247
232
|
);
|
|
248
|
-
// aria-label should be undefined (not set) for non-string children
|
|
249
|
-
// unless explicitly provided
|
|
250
233
|
expect(screen.getByRole("button")).not.toHaveAttribute("aria-label");
|
|
251
234
|
});
|
|
252
235
|
});
|
|
253
236
|
|
|
254
|
-
// ── New: asChild prop ──────────────────────────────────────────────────────
|
|
255
|
-
|
|
256
237
|
describe("asChild prop", () => {
|
|
257
238
|
it("renders children as the root element", () => {
|
|
258
239
|
render(
|
|
@@ -281,8 +262,6 @@ describe("Button Component", () => {
|
|
|
281
262
|
});
|
|
282
263
|
});
|
|
283
264
|
|
|
284
|
-
// ── New: loading prop ──────────────────────────────────────────────────────
|
|
285
|
-
|
|
286
265
|
describe("loading prop", () => {
|
|
287
266
|
it("disables interaction and sets aria-busy", () => {
|
|
288
267
|
const handleClick = vi.fn();
|