@bug-on/md3-react 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +42 -42
- package/CHANGELOG.md +10 -0
- package/dist/index.css +107 -0
- package/dist/index.d.mts +1491 -1053
- package/dist/index.d.ts +1491 -1053
- package/dist/index.js +4457 -3156
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4394 -3109
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -6
- package/scripts/copy-assets.js +113 -8
- package/src/index.ts +66 -18
- package/src/test/button.test.tsx +1 -1
- package/src/ui/app-bar/app-bar.tokens.ts +5 -24
- package/src/ui/badge.tsx +2 -1
- package/src/ui/buttons/button/button-tokens.ts +118 -0
- package/src/ui/{button.test.tsx → buttons/button/button.test.tsx} +0 -21
- package/src/ui/buttons/button/button.tsx +381 -0
- package/src/ui/buttons/button/index.ts +3 -0
- package/src/ui/buttons/button/types.ts +90 -0
- package/src/ui/buttons/button-group/button-group-defaults.ts +95 -0
- package/src/ui/buttons/button-group/button-group-tokens.ts +20 -0
- package/src/ui/{button-group.test.tsx → buttons/button-group/button-group.test.tsx} +9 -10
- package/src/ui/buttons/button-group/button-group.tsx +699 -0
- package/src/ui/buttons/button-group/index.ts +8 -0
- package/src/ui/buttons/button-group/types.ts +77 -0
- package/src/ui/{fab.tsx → buttons/fabs/fab/fab.tsx} +6 -6
- package/src/ui/buttons/fabs/fab/index.ts +1 -0
- package/src/ui/{fab-menu.tsx → buttons/fabs/fab-menu/fab-menu.tsx} +7 -4
- package/src/ui/buttons/fabs/fab-menu/index.ts +1 -0
- package/src/ui/buttons/fabs/index.ts +2 -0
- package/src/ui/{icon-button.tsx → buttons/icon-button/icon-button.tsx} +6 -6
- package/src/ui/buttons/icon-button/index.ts +1 -0
- package/src/ui/buttons/index.ts +4 -0
- package/src/ui/code-block.tsx +1 -1
- package/src/ui/dialog.tsx +4 -7
- package/src/ui/drawer.tsx +4 -7
- package/src/ui/menu/menu-animations.ts +14 -20
- package/src/ui/menu/menu-tokens.ts +7 -5
- package/src/ui/menu/menu.test.tsx +9 -4
- package/src/ui/navigation-bar.test.tsx +111 -0
- package/src/ui/navigation-bar.tsx +464 -0
- package/src/ui/navigation-rail.test.tsx +5 -4
- package/src/ui/navigation-rail.tsx +32 -23
- package/src/ui/scroll-area.tsx +4 -0
- package/src/ui/search/search-view-fullscreen.tsx +1 -1
- package/src/ui/search/search.tokens.ts +9 -43
- package/src/ui/search/trailing-action.tsx +1 -1
- package/src/ui/shared/constants.ts +25 -27
- package/src/ui/shared/motion-tokens.ts +238 -0
- package/src/ui/snackbar/snackbar.tsx +4 -6
- package/src/ui/switch/switch.tsx +12 -18
- package/src/ui/text-field/text-field.tokens.ts +12 -12
- package/src/ui/text-field/text-field.tsx +31 -19
- package/src/ui/theme-provider/index.tsx +1 -5
- package/src/ui/toc.tsx +1 -1
- package/src/ui/toolbar/__snapshots__/bottom-docked-toolbar.test.tsx.snap +51 -0
- package/src/ui/toolbar/__snapshots__/floating-toolbar-with-fab.test.tsx.snap +113 -0
- package/src/ui/toolbar/__snapshots__/floating-toolbar.test.tsx.snap +169 -0
- package/src/ui/toolbar/bottom-docked-toolbar.test.tsx +114 -0
- package/src/ui/toolbar/docked-toolbar.tsx +186 -0
- package/src/ui/toolbar/floating-toolbar-with-fab.test.tsx +139 -0
- package/src/ui/toolbar/floating-toolbar-with-fab.tsx +199 -0
- package/src/ui/toolbar/floating-toolbar.test.tsx +230 -0
- package/src/ui/toolbar/floating-toolbar.tsx +344 -0
- package/src/ui/toolbar/index.ts +35 -0
- package/src/ui/toolbar/toolbar-colors.ts +37 -0
- package/src/ui/toolbar/toolbar-context.tsx +13 -0
- package/src/ui/toolbar/toolbar-divider.test.tsx +54 -0
- package/src/ui/toolbar/toolbar-divider.tsx +73 -0
- package/src/ui/toolbar/toolbar-icon-button.test.tsx +68 -0
- package/src/ui/toolbar/toolbar-icon-button.tsx +136 -0
- package/src/ui/toolbar/toolbar-scroll-behavior.ts +140 -0
- package/src/ui/toolbar/toolbar-tokens.ts +51 -0
- package/test-clip.html +31 -0
- package/test-shadow.html +5 -1
- package/test-width.html +34 -0
- package/src/ui/button-group.tsx +0 -350
- package/src/ui/button.tsx +0 -665
- package/test-render.tsx +0 -4
- package/test_output.txt +0 -164
- package/test_output_v2.txt +0 -5
- /package/src/ui/{fab.test.tsx → buttons/fabs/fab/fab.test.tsx} +0 -0
- /package/src/ui/{fab-menu.test.tsx → buttons/fabs/fab-menu/fab-menu.test.tsx} +0 -0
- /package/src/ui/{icon-button.test.tsx → buttons/icon-button/icon-button.test.tsx} +0 -0
- /package/src/ui/{Text.tsx → text.tsx} +0 -0
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
|
|
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
|
+
`;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`FloatingToolbarWithFab > snapshots: horizontal with fab end 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="flex items-center justify-center pointer-events-auto flex-row h-(--toolbar-size)"
|
|
7
|
+
style="--fab-size: 56px; --toolbar-size: 64px; gap: 8px;"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
class="flex items-center shrink-0 min-w-0 min-h-0 overflow-hidden relative z-0"
|
|
11
|
+
style="padding: 24px; margin: -24px; opacity: 1; width: auto; height: auto; transform: none;"
|
|
12
|
+
>
|
|
13
|
+
<div
|
|
14
|
+
style="transform: none;"
|
|
15
|
+
>
|
|
16
|
+
<div
|
|
17
|
+
aria-expanded="true"
|
|
18
|
+
aria-label="Floating Toolbar"
|
|
19
|
+
aria-orientation="horizontal"
|
|
20
|
+
class="flex pointer-events-auto relative max-w-[90vw] h-(--toolbar-size) rounded-full shadow-md"
|
|
21
|
+
role="toolbar"
|
|
22
|
+
style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); --toolbar-size: 64px; background-color: var(--toolbar-bg); color: var(--toolbar-color);"
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
class="flex-1 w-full h-full rounded-[inherit] scrollbar-hide overflow-x-auto overflow-y-visible"
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
class="flex items-center gap-1 min-w-0 min-h-0 flex-row h-full px-4"
|
|
29
|
+
>
|
|
30
|
+
<div
|
|
31
|
+
class="flex flex-1 items-center min-w-0 min-h-0 h-full flex-row justify-center"
|
|
32
|
+
style="gap: 4px;"
|
|
33
|
+
>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
>
|
|
37
|
+
Center
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div
|
|
46
|
+
class="flex shrink-0 items-center justify-center relative z-10 *:w-full *:h-full"
|
|
47
|
+
style="width: 56px; height: 56px;"
|
|
48
|
+
>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
>
|
|
52
|
+
FAB
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
exports[`FloatingToolbarWithFab > snapshots: vertical with fab bottom 1`] = `
|
|
60
|
+
<div>
|
|
61
|
+
<div
|
|
62
|
+
class="flex items-center justify-center pointer-events-auto flex-col w-(--toolbar-size) h-fit"
|
|
63
|
+
style="--fab-size: 56px; --toolbar-size: 64px; gap: 8px;"
|
|
64
|
+
>
|
|
65
|
+
<div
|
|
66
|
+
class="flex items-center shrink-0 min-w-0 min-h-0 overflow-hidden relative z-0"
|
|
67
|
+
style="padding: 24px; margin: -24px; opacity: 1; width: auto; height: auto; transform: none;"
|
|
68
|
+
>
|
|
69
|
+
<div
|
|
70
|
+
style="transform: none;"
|
|
71
|
+
>
|
|
72
|
+
<div
|
|
73
|
+
aria-expanded="true"
|
|
74
|
+
aria-label="Floating Toolbar"
|
|
75
|
+
aria-orientation="vertical"
|
|
76
|
+
class="flex pointer-events-auto relative max-h-[90vh] w-(--toolbar-size) h-fit rounded-full shadow-md"
|
|
77
|
+
role="toolbar"
|
|
78
|
+
style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); --toolbar-size: 64px; background-color: var(--toolbar-bg); color: var(--toolbar-color);"
|
|
79
|
+
>
|
|
80
|
+
<div
|
|
81
|
+
class="flex-1 w-full h-full rounded-[inherit] scrollbar-hide overflow-y-auto overflow-x-visible"
|
|
82
|
+
>
|
|
83
|
+
<div
|
|
84
|
+
class="flex items-center gap-1 min-w-0 min-h-0 flex-col w-full py-6"
|
|
85
|
+
>
|
|
86
|
+
<div
|
|
87
|
+
class="flex flex-1 items-center min-w-0 min-h-0 h-full flex-col justify-center"
|
|
88
|
+
style="gap: 4px;"
|
|
89
|
+
>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
>
|
|
93
|
+
Center
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<div
|
|
102
|
+
class="flex shrink-0 items-center justify-center relative z-10 *:w-full *:h-full"
|
|
103
|
+
style="width: 56px; height: 56px;"
|
|
104
|
+
>
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
>
|
|
108
|
+
FAB
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
`;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`HorizontalFloatingToolbar > snapshots: horizontal collapsed 1`] = `
|
|
4
|
+
<div
|
|
5
|
+
style="transform: none;"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
aria-expanded="false"
|
|
9
|
+
aria-label="Floating Toolbar"
|
|
10
|
+
aria-orientation="horizontal"
|
|
11
|
+
class="flex pointer-events-auto relative max-w-[90vw] h-(--toolbar-size) rounded-full shadow-sm"
|
|
12
|
+
role="toolbar"
|
|
13
|
+
style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); --toolbar-size: 64px; background-color: var(--toolbar-bg); color: var(--toolbar-color);"
|
|
14
|
+
>
|
|
15
|
+
<div
|
|
16
|
+
class="flex-1 w-full h-full rounded-[inherit] scrollbar-hide overflow-x-auto overflow-y-visible"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
class="flex items-center gap-1 min-w-0 min-h-0 flex-row h-full px-4"
|
|
20
|
+
>
|
|
21
|
+
<div
|
|
22
|
+
class="flex flex-1 items-center min-w-0 min-h-0 h-full flex-row justify-center"
|
|
23
|
+
style="gap: 4px;"
|
|
24
|
+
>
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
>
|
|
28
|
+
Center
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
exports[`HorizontalFloatingToolbar > snapshots: horizontal expanded 1`] = `
|
|
38
|
+
<div
|
|
39
|
+
style="transform: none;"
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
aria-expanded="true"
|
|
43
|
+
aria-label="Floating Toolbar"
|
|
44
|
+
aria-orientation="horizontal"
|
|
45
|
+
class="flex pointer-events-auto relative max-w-[90vw] h-(--toolbar-size) rounded-full shadow-md"
|
|
46
|
+
role="toolbar"
|
|
47
|
+
style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); --toolbar-size: 64px; background-color: var(--toolbar-bg); color: var(--toolbar-color);"
|
|
48
|
+
>
|
|
49
|
+
<div
|
|
50
|
+
class="flex-1 w-full h-full rounded-[inherit] scrollbar-hide overflow-x-auto overflow-y-visible"
|
|
51
|
+
>
|
|
52
|
+
<div
|
|
53
|
+
class="flex items-center gap-1 min-w-0 min-h-0 flex-row h-full px-4"
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
class="flex shrink-0 overflow-hidden flex-row"
|
|
57
|
+
style="opacity: 1; width: auto; height: auto; transform: none;"
|
|
58
|
+
>
|
|
59
|
+
<span>
|
|
60
|
+
Lead
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div
|
|
64
|
+
class="flex flex-1 items-center min-w-0 min-h-0 h-full flex-row justify-center"
|
|
65
|
+
style="gap: 4px;"
|
|
66
|
+
>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
>
|
|
70
|
+
Center
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
<div
|
|
74
|
+
class="flex shrink-0 overflow-hidden flex-row"
|
|
75
|
+
style="opacity: 1; width: auto; height: auto; transform: none;"
|
|
76
|
+
>
|
|
77
|
+
<span>
|
|
78
|
+
Trail
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
exports[`VerticalFloatingToolbar > snapshots: vertical collapsed 1`] = `
|
|
88
|
+
<div
|
|
89
|
+
style="transform: none;"
|
|
90
|
+
>
|
|
91
|
+
<div
|
|
92
|
+
aria-expanded="false"
|
|
93
|
+
aria-label="Floating Toolbar"
|
|
94
|
+
aria-orientation="vertical"
|
|
95
|
+
class="flex pointer-events-auto relative max-h-[90vh] w-(--toolbar-size) h-fit rounded-full shadow-sm"
|
|
96
|
+
role="toolbar"
|
|
97
|
+
style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); --toolbar-size: 64px; background-color: var(--toolbar-bg); color: var(--toolbar-color);"
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
class="flex-1 w-full h-full rounded-[inherit] scrollbar-hide overflow-y-auto overflow-x-visible"
|
|
101
|
+
>
|
|
102
|
+
<div
|
|
103
|
+
class="flex items-center gap-1 min-w-0 min-h-0 flex-col w-full py-6"
|
|
104
|
+
>
|
|
105
|
+
<div
|
|
106
|
+
class="flex flex-1 items-center min-w-0 min-h-0 h-full flex-col justify-center"
|
|
107
|
+
style="gap: 4px;"
|
|
108
|
+
>
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
>
|
|
112
|
+
Center
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
exports[`VerticalFloatingToolbar > snapshots: vertical expanded 1`] = `
|
|
122
|
+
<div
|
|
123
|
+
style="transform: none;"
|
|
124
|
+
>
|
|
125
|
+
<div
|
|
126
|
+
aria-expanded="true"
|
|
127
|
+
aria-label="Floating Toolbar"
|
|
128
|
+
aria-orientation="vertical"
|
|
129
|
+
class="flex pointer-events-auto relative max-h-[90vh] w-(--toolbar-size) h-fit rounded-full shadow-md"
|
|
130
|
+
role="toolbar"
|
|
131
|
+
style="--toolbar-bg: var(--md-sys-color-surface-container); --toolbar-color: var(--md-sys-color-on-surface); --toolbar-size: 64px; background-color: var(--toolbar-bg); color: var(--toolbar-color);"
|
|
132
|
+
>
|
|
133
|
+
<div
|
|
134
|
+
class="flex-1 w-full h-full rounded-[inherit] scrollbar-hide overflow-y-auto overflow-x-visible"
|
|
135
|
+
>
|
|
136
|
+
<div
|
|
137
|
+
class="flex items-center gap-1 min-w-0 min-h-0 flex-col w-full py-6"
|
|
138
|
+
>
|
|
139
|
+
<div
|
|
140
|
+
class="flex shrink-0 overflow-hidden flex-col"
|
|
141
|
+
style="opacity: 1; width: auto; height: auto; transform: none;"
|
|
142
|
+
>
|
|
143
|
+
<span>
|
|
144
|
+
Lead
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div
|
|
148
|
+
class="flex flex-1 items-center min-w-0 min-h-0 h-full flex-col justify-center"
|
|
149
|
+
style="gap: 4px;"
|
|
150
|
+
>
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
>
|
|
154
|
+
Center
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
<div
|
|
158
|
+
class="flex shrink-0 overflow-hidden flex-col"
|
|
159
|
+
style="opacity: 1; width: auto; height: auto; transform: none;"
|
|
160
|
+
>
|
|
161
|
+
<span>
|
|
162
|
+
Trail
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
`;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { BottomDockedToolbar } from "./docked-toolbar";
|
|
5
|
+
|
|
6
|
+
describe("BottomDockedToolbar", () => {
|
|
7
|
+
it("renders at bottom of screen (position fixed)", () => {
|
|
8
|
+
const { container } = render(
|
|
9
|
+
<BottomDockedToolbar>Test</BottomDockedToolbar>,
|
|
10
|
+
);
|
|
11
|
+
expect(container.firstChild).toHaveClass("fixed bottom-0");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("renders full-width (w-full)", () => {
|
|
15
|
+
const { container } = render(
|
|
16
|
+
<BottomDockedToolbar>Test</BottomDockedToolbar>,
|
|
17
|
+
);
|
|
18
|
+
expect(container.firstChild).toHaveClass("w-full");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders with correct height (h-16 = 64px)", () => {
|
|
22
|
+
const { container } = render(
|
|
23
|
+
<BottomDockedToolbar>Test</BottomDockedToolbar>,
|
|
24
|
+
);
|
|
25
|
+
expect(container.firstChild).toHaveClass("h-16");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders startContent", () => {
|
|
29
|
+
render(
|
|
30
|
+
<BottomDockedToolbar startContent={<span data-testid="leading" />}>
|
|
31
|
+
Test
|
|
32
|
+
</BottomDockedToolbar>,
|
|
33
|
+
);
|
|
34
|
+
expect(screen.getByTestId("leading")).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders endContent", () => {
|
|
38
|
+
render(
|
|
39
|
+
<BottomDockedToolbar endContent={<span data-testid="trailing" />}>
|
|
40
|
+
Test
|
|
41
|
+
</BottomDockedToolbar>,
|
|
42
|
+
);
|
|
43
|
+
expect(screen.getByTestId("trailing")).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("renders children in center", () => {
|
|
47
|
+
render(
|
|
48
|
+
<BottomDockedToolbar>
|
|
49
|
+
<span data-testid="center" />
|
|
50
|
+
</BottomDockedToolbar>,
|
|
51
|
+
);
|
|
52
|
+
expect(screen.getByTestId("center")).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("standard and vibrant color variants", () => {
|
|
56
|
+
const { container: c1 } = render(
|
|
57
|
+
<BottomDockedToolbar variant="standard">Test</BottomDockedToolbar>,
|
|
58
|
+
);
|
|
59
|
+
expect(
|
|
60
|
+
(c1.firstChild as HTMLElement).style.getPropertyValue("--toolbar-bg"),
|
|
61
|
+
).toBe("var(--md-sys-color-surface-container)");
|
|
62
|
+
|
|
63
|
+
const { container: c2 } = render(
|
|
64
|
+
<BottomDockedToolbar variant="vibrant">Test</BottomDockedToolbar>,
|
|
65
|
+
);
|
|
66
|
+
expect(
|
|
67
|
+
(c2.firstChild as HTMLElement).style.getPropertyValue("--toolbar-bg"),
|
|
68
|
+
).toBe("var(--md-sys-color-primary-container)");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("no rounded corners by default", () => {
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<BottomDockedToolbar>Test</BottomDockedToolbar>,
|
|
74
|
+
);
|
|
75
|
+
// Tailwind rounded classes aren't present
|
|
76
|
+
expect(container.firstChild).not.toHaveClass("rounded-full");
|
|
77
|
+
expect(container.firstChild).not.toHaveClass("rounded-2xl");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("applies aria-label", () => {
|
|
81
|
+
render(
|
|
82
|
+
<BottomDockedToolbar aria-label="Test Docked">Test</BottomDockedToolbar>,
|
|
83
|
+
);
|
|
84
|
+
expect(screen.getByRole("toolbar")).toHaveAttribute(
|
|
85
|
+
"aria-label",
|
|
86
|
+
"Test Docked",
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("forwards ref", () => {
|
|
91
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
92
|
+
render(<BottomDockedToolbar ref={ref}>Test</BottomDockedToolbar>);
|
|
93
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("snapshots: with leading and trailing content", () => {
|
|
97
|
+
const { container } = render(
|
|
98
|
+
<BottomDockedToolbar
|
|
99
|
+
startContent={<span>Lead</span>}
|
|
100
|
+
endContent={<span>Trail</span>}
|
|
101
|
+
>
|
|
102
|
+
Center
|
|
103
|
+
</BottomDockedToolbar>,
|
|
104
|
+
);
|
|
105
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("snapshots: without optional content", () => {
|
|
109
|
+
const { container } = render(
|
|
110
|
+
<BottomDockedToolbar>Center</BottomDockedToolbar>,
|
|
111
|
+
);
|
|
112
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { m, useMotionValueEvent, useScroll } from "motion/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import { SPRING_TRANSITION } from "../shared/constants";
|
|
5
|
+
import {
|
|
6
|
+
type FloatingToolbarColors,
|
|
7
|
+
standardFloatingToolbarColors,
|
|
8
|
+
vibrantFloatingToolbarColors,
|
|
9
|
+
} from "./toolbar-colors";
|
|
10
|
+
import { ToolbarContext } from "./toolbar-context";
|
|
11
|
+
|
|
12
|
+
export interface BottomDockedToolbarProps {
|
|
13
|
+
/** Color variant: standard or vibrant */
|
|
14
|
+
variant?: "standard" | "vibrant";
|
|
15
|
+
/** Custom colors override */
|
|
16
|
+
colors?: FloatingToolbarColors;
|
|
17
|
+
/** Whether to hide on scroll */
|
|
18
|
+
hideOnScroll?: boolean;
|
|
19
|
+
/** Scroll container ref for scroll behavior (if empty, uses window scroll) */
|
|
20
|
+
scrollContainerRef?: React.RefObject<HTMLElement | null>;
|
|
21
|
+
/** Start content (left-aligned) */
|
|
22
|
+
startContent?: React.ReactNode;
|
|
23
|
+
/** End content (right-aligned) */
|
|
24
|
+
endContent?: React.ReactNode;
|
|
25
|
+
/** Center content */
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Horizontal padding (px) applied to the toolbar container.
|
|
29
|
+
* MD3 spec requires a minimum of 16dp on leading and trailing edges.
|
|
30
|
+
* @default 16
|
|
31
|
+
*/
|
|
32
|
+
paddingX?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Layout distribution of toolbar content.
|
|
35
|
+
* - `"between"` – leading/center/trailing spread across full width (default, compact screens).
|
|
36
|
+
* - `"center"` – all content centered; ideal for medium+ screen widths.
|
|
37
|
+
* - `"end-weighted"` – key action is centered while others are pushed to the edges.
|
|
38
|
+
* @default "between"
|
|
39
|
+
*/
|
|
40
|
+
justify?: "between" | "center" | "end-weighted";
|
|
41
|
+
/**
|
|
42
|
+
* Container shape.
|
|
43
|
+
* - `"none"` – straight corners (MD3 default for mobile docked toolbars).
|
|
44
|
+
* - `"large"` – large rounded corners (recommended for web / large screens).
|
|
45
|
+
* - `"full"` – fully pill-shaped.
|
|
46
|
+
*
|
|
47
|
+
* MD3 guideline: *"On web and large screens, the docked toolbar can be rounded."*
|
|
48
|
+
* @default "none"
|
|
49
|
+
*/
|
|
50
|
+
shape?: "none" | "large" | "full";
|
|
51
|
+
className?: string;
|
|
52
|
+
"aria-label"?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A full-width docked toolbar typically fixed at the bottom of the screen.
|
|
57
|
+
* Replaces the deprecated Bottom App Bar in MD3 Expressive.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* <BottomDockedToolbar hideOnScroll startContent={<IconButton aria-label="Menu"><Icon name="menu" /></IconButton>}>
|
|
62
|
+
* <div className="flex gap-2">
|
|
63
|
+
* <IconButton aria-label="Search"><Icon name="search" /></IconButton>
|
|
64
|
+
* <IconButton aria-label="Edit"><Icon name="edit" /></IconButton>
|
|
65
|
+
* </div>
|
|
66
|
+
* </BottomDockedToolbar>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const BottomDockedToolbar = React.forwardRef<
|
|
70
|
+
HTMLDivElement,
|
|
71
|
+
BottomDockedToolbarProps
|
|
72
|
+
>(
|
|
73
|
+
(
|
|
74
|
+
{
|
|
75
|
+
variant = "standard",
|
|
76
|
+
colors: customColors,
|
|
77
|
+
hideOnScroll = false,
|
|
78
|
+
scrollContainerRef,
|
|
79
|
+
startContent,
|
|
80
|
+
endContent,
|
|
81
|
+
children,
|
|
82
|
+
paddingX = 16,
|
|
83
|
+
justify = "between",
|
|
84
|
+
shape = "none",
|
|
85
|
+
className,
|
|
86
|
+
"aria-label": ariaLabel,
|
|
87
|
+
...props
|
|
88
|
+
},
|
|
89
|
+
ref,
|
|
90
|
+
) => {
|
|
91
|
+
const colors =
|
|
92
|
+
customColors ||
|
|
93
|
+
(variant === "vibrant"
|
|
94
|
+
? vibrantFloatingToolbarColors
|
|
95
|
+
: standardFloatingToolbarColors);
|
|
96
|
+
|
|
97
|
+
const { scrollY } = useScroll(
|
|
98
|
+
scrollContainerRef ? { container: scrollContainerRef } : undefined,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const [isHidden, setIsHidden] = React.useState(false);
|
|
102
|
+
const lastScrollY = React.useRef(0);
|
|
103
|
+
|
|
104
|
+
useMotionValueEvent(scrollY, "change", (latest) => {
|
|
105
|
+
if (!hideOnScroll) return;
|
|
106
|
+
|
|
107
|
+
const diff = latest - lastScrollY.current;
|
|
108
|
+
|
|
109
|
+
if (latest <= 0) {
|
|
110
|
+
setIsHidden(false);
|
|
111
|
+
lastScrollY.current = 0;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (Math.abs(diff) > 10) {
|
|
116
|
+
setIsHidden(diff > 0);
|
|
117
|
+
lastScrollY.current = latest;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const cssVars = {
|
|
122
|
+
"--toolbar-bg": colors.toolbarContainerColor,
|
|
123
|
+
"--toolbar-color": colors.toolbarContentColor,
|
|
124
|
+
} as React.CSSProperties;
|
|
125
|
+
|
|
126
|
+
const SHAPE_CLASSES = {
|
|
127
|
+
none: "",
|
|
128
|
+
large: "rounded-2xl",
|
|
129
|
+
full: "rounded-full",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const justifyClass =
|
|
133
|
+
justify === "center" ? "justify-center gap-4" : "justify-between";
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<ToolbarContext.Provider value={{ orientation: "horizontal" }}>
|
|
137
|
+
<m.div
|
|
138
|
+
ref={ref}
|
|
139
|
+
role="toolbar"
|
|
140
|
+
aria-label={ariaLabel || "Bottom Docked Toolbar"}
|
|
141
|
+
initial={false}
|
|
142
|
+
animate={{
|
|
143
|
+
y: isHidden ? "100%" : "0%",
|
|
144
|
+
}}
|
|
145
|
+
transition={SPRING_TRANSITION}
|
|
146
|
+
style={{
|
|
147
|
+
...cssVars,
|
|
148
|
+
backgroundColor: "var(--toolbar-bg)",
|
|
149
|
+
color: "var(--toolbar-color)",
|
|
150
|
+
// Apply safe area inset for mobile bottom indicators
|
|
151
|
+
paddingBottom: "env(safe-area-inset-bottom, 0px)",
|
|
152
|
+
paddingLeft: paddingX,
|
|
153
|
+
paddingRight: paddingX,
|
|
154
|
+
}}
|
|
155
|
+
className={cn(
|
|
156
|
+
"fixed bottom-0 left-0 right-0 w-full z-40",
|
|
157
|
+
"flex items-center h-16",
|
|
158
|
+
justifyClass,
|
|
159
|
+
SHAPE_CLASSES[shape],
|
|
160
|
+
"shadow-[0_-1px_3px_rgba(0,0,0,0.1)] pointer-events-auto",
|
|
161
|
+
className,
|
|
162
|
+
)}
|
|
163
|
+
{...props}
|
|
164
|
+
>
|
|
165
|
+
<div className="flex items-center justify-start flex-1 shrink-0">
|
|
166
|
+
{startContent}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div
|
|
170
|
+
className={cn(
|
|
171
|
+
"flex items-center justify-center",
|
|
172
|
+
justify === "center" ? "gap-1" : "flex-1 shrink-0",
|
|
173
|
+
)}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div className="flex items-center justify-end flex-1 shrink-0">
|
|
179
|
+
{endContent}
|
|
180
|
+
</div>
|
|
181
|
+
</m.div>
|
|
182
|
+
</ToolbarContext.Provider>
|
|
183
|
+
);
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
BottomDockedToolbar.displayName = "BottomDockedToolbar";
|