@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
HorizontalFloatingToolbarWithFab,
|
|
6
|
+
VerticalFloatingToolbarWithFab,
|
|
7
|
+
} from "./floating-toolbar-with-fab";
|
|
8
|
+
|
|
9
|
+
describe("FloatingToolbarWithFab", () => {
|
|
10
|
+
it("renders toolbar with FAB at end position (default)", () => {
|
|
11
|
+
render(
|
|
12
|
+
<HorizontalFloatingToolbarWithFab
|
|
13
|
+
expanded={true}
|
|
14
|
+
floatingActionButton={
|
|
15
|
+
<button type="button" data-testid="fab">
|
|
16
|
+
FAB
|
|
17
|
+
</button>
|
|
18
|
+
}
|
|
19
|
+
>
|
|
20
|
+
<button type="button">Center</button>
|
|
21
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
22
|
+
);
|
|
23
|
+
const fab = screen.getByTestId("fab");
|
|
24
|
+
expect(fab).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("renders toolbar with FAB at start position", () => {
|
|
28
|
+
render(
|
|
29
|
+
<HorizontalFloatingToolbarWithFab
|
|
30
|
+
expanded={true}
|
|
31
|
+
fabPosition="start"
|
|
32
|
+
floatingActionButton={
|
|
33
|
+
<button type="button" data-testid="fab">
|
|
34
|
+
FAB
|
|
35
|
+
</button>
|
|
36
|
+
}
|
|
37
|
+
>
|
|
38
|
+
<button type="button">Center</button>
|
|
39
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
40
|
+
);
|
|
41
|
+
expect(screen.getByTestId("fab")).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("toolbar and FAB maintain 8px gap via gap property", () => {
|
|
45
|
+
const { container } = render(
|
|
46
|
+
<HorizontalFloatingToolbarWithFab
|
|
47
|
+
expanded={true}
|
|
48
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
49
|
+
>
|
|
50
|
+
<button type="button">Center</button>
|
|
51
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
52
|
+
);
|
|
53
|
+
// Gap is now applied as a style on the parent m.div
|
|
54
|
+
const innerFlex = container.firstChild?.firstChild as HTMLElement;
|
|
55
|
+
expect(innerFlex).toHaveStyle({ gap: "8px" });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("standard and vibrant color variants (via colors prop)", () => {
|
|
59
|
+
const vibrantColors = {
|
|
60
|
+
toolbarContainerColor: "var(--md-sys-color-primary-container)",
|
|
61
|
+
toolbarContentColor: "var(--md-sys-color-on-primary-container)",
|
|
62
|
+
};
|
|
63
|
+
const { container } = render(
|
|
64
|
+
<HorizontalFloatingToolbarWithFab
|
|
65
|
+
expanded={true}
|
|
66
|
+
colors={vibrantColors}
|
|
67
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
68
|
+
>
|
|
69
|
+
<button type="button">Center</button>
|
|
70
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
71
|
+
);
|
|
72
|
+
// It passes colors to the child Toolbar
|
|
73
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("expanded/collapsed states resize the FAB container", () => {
|
|
77
|
+
// expanded=true size=80, expanded=false size=56
|
|
78
|
+
const { rerender, container } = render(
|
|
79
|
+
<HorizontalFloatingToolbarWithFab
|
|
80
|
+
expanded={false}
|
|
81
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
82
|
+
>
|
|
83
|
+
<button type="button">Center</button>
|
|
84
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// We cannot reliably assert motion inline styles instantly in standard jsdom without timers,
|
|
88
|
+
// but we know it doesn't throw.
|
|
89
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
90
|
+
|
|
91
|
+
rerender(
|
|
92
|
+
<HorizontalFloatingToolbarWithFab
|
|
93
|
+
expanded={true}
|
|
94
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
95
|
+
>
|
|
96
|
+
<button type="button">Center</button>
|
|
97
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
98
|
+
);
|
|
99
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("forwards ref", () => {
|
|
103
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
104
|
+
render(
|
|
105
|
+
<HorizontalFloatingToolbarWithFab
|
|
106
|
+
expanded={true}
|
|
107
|
+
ref={ref}
|
|
108
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
109
|
+
>
|
|
110
|
+
<button type="button">Center</button>
|
|
111
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
112
|
+
);
|
|
113
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("snapshots: horizontal with fab end", () => {
|
|
117
|
+
const { container } = render(
|
|
118
|
+
<HorizontalFloatingToolbarWithFab
|
|
119
|
+
expanded={true}
|
|
120
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
121
|
+
>
|
|
122
|
+
<button type="button">Center</button>
|
|
123
|
+
</HorizontalFloatingToolbarWithFab>,
|
|
124
|
+
);
|
|
125
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("snapshots: vertical with fab bottom", () => {
|
|
129
|
+
const { container } = render(
|
|
130
|
+
<VerticalFloatingToolbarWithFab
|
|
131
|
+
expanded={true}
|
|
132
|
+
floatingActionButton={<button type="button">FAB</button>}
|
|
133
|
+
>
|
|
134
|
+
<button type="button">Center</button>
|
|
135
|
+
</VerticalFloatingToolbarWithFab>,
|
|
136
|
+
);
|
|
137
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { AnimatePresence, m } 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 FloatingToolbarProps,
|
|
7
|
+
HorizontalFloatingToolbar,
|
|
8
|
+
VerticalFloatingToolbar,
|
|
9
|
+
} from "./floating-toolbar";
|
|
10
|
+
import { standardFloatingToolbarColors } from "./toolbar-colors";
|
|
11
|
+
import {
|
|
12
|
+
FabBaselineTokens,
|
|
13
|
+
FloatingToolbarTokens,
|
|
14
|
+
ToolbarToFabGap,
|
|
15
|
+
} from "./toolbar-tokens";
|
|
16
|
+
|
|
17
|
+
export interface FloatingToolbarWithFabProps extends FloatingToolbarProps {
|
|
18
|
+
/** FAB element (use FAB component or custom node) */
|
|
19
|
+
floatingActionButton: React.ReactNode;
|
|
20
|
+
/** FAB position for horizontal: 'start' | 'end'. For vertical: 'top' | 'bottom' */
|
|
21
|
+
fabPosition?: "start" | "end" | "top" | "bottom";
|
|
22
|
+
/** Animation duration override */
|
|
23
|
+
animationDuration?: number;
|
|
24
|
+
/** Start content (explicitly declared to resolve type issues) */
|
|
25
|
+
startContent?: React.ReactNode;
|
|
26
|
+
/** End content (explicitly declared to resolve type issues) */
|
|
27
|
+
endContent?: React.ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Size of the FAB container.
|
|
30
|
+
* - `"default"` → 56×56px (`FabBaselineTokens.ContainerHeight`)
|
|
31
|
+
* - `"medium"` → 80×80px (`FabBaselineTokens.MediumContainerHeight`)
|
|
32
|
+
*
|
|
33
|
+
* MD3: pair a medium FAB with the toolbar to give it greater visual prominence.
|
|
34
|
+
* @default "default"
|
|
35
|
+
*/
|
|
36
|
+
fabSize?: "default" | "medium";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const FloatingToolbarWithFabBase = React.forwardRef<
|
|
40
|
+
HTMLDivElement,
|
|
41
|
+
FloatingToolbarWithFabProps
|
|
42
|
+
>(
|
|
43
|
+
(
|
|
44
|
+
{
|
|
45
|
+
expanded,
|
|
46
|
+
orientation = "horizontal",
|
|
47
|
+
colors = standardFloatingToolbarColors,
|
|
48
|
+
floatingActionButton,
|
|
49
|
+
fabPosition = orientation === "horizontal" ? "end" : "bottom",
|
|
50
|
+
animationDuration = 0.3,
|
|
51
|
+
scrollBehavior,
|
|
52
|
+
fabSize = "default",
|
|
53
|
+
className,
|
|
54
|
+
style,
|
|
55
|
+
...props
|
|
56
|
+
},
|
|
57
|
+
ref,
|
|
58
|
+
) => {
|
|
59
|
+
const isHorizontal = orientation === "horizontal";
|
|
60
|
+
|
|
61
|
+
const effectiveExpanded =
|
|
62
|
+
expanded && (!scrollBehavior || scrollBehavior.isExpanded);
|
|
63
|
+
|
|
64
|
+
const isFabBefore = fabPosition === "start" || fabPosition === "top";
|
|
65
|
+
|
|
66
|
+
const resolvedFabSize =
|
|
67
|
+
fabSize === "medium"
|
|
68
|
+
? FabBaselineTokens.MediumContainerHeight
|
|
69
|
+
: FabBaselineTokens.ContainerHeight;
|
|
70
|
+
|
|
71
|
+
const transitionSpec = {
|
|
72
|
+
...SPRING_TRANSITION,
|
|
73
|
+
duration: animationDuration,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const SHADOW_PADDING = 24;
|
|
77
|
+
|
|
78
|
+
const fabContainerNode = (
|
|
79
|
+
<m.div
|
|
80
|
+
layout
|
|
81
|
+
key="fab-container"
|
|
82
|
+
style={{
|
|
83
|
+
width: `${resolvedFabSize}px`,
|
|
84
|
+
height: `${resolvedFabSize}px`,
|
|
85
|
+
}}
|
|
86
|
+
className={cn(
|
|
87
|
+
"flex shrink-0 items-center justify-center relative z-10",
|
|
88
|
+
"*:w-full *:h-full",
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
{floatingActionButton}
|
|
92
|
+
</m.div>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div ref={ref} style={style}>
|
|
97
|
+
<m.div
|
|
98
|
+
layout
|
|
99
|
+
initial={false}
|
|
100
|
+
animate={{
|
|
101
|
+
gap: effectiveExpanded ? `${ToolbarToFabGap}px` : "0px",
|
|
102
|
+
}}
|
|
103
|
+
transition={transitionSpec}
|
|
104
|
+
className={cn(
|
|
105
|
+
"flex items-center justify-center pointer-events-auto",
|
|
106
|
+
isHorizontal
|
|
107
|
+
? "flex-row h-(--toolbar-size)"
|
|
108
|
+
: "flex-col w-(--toolbar-size) h-fit",
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
style={
|
|
112
|
+
{
|
|
113
|
+
"--fab-size": `${resolvedFabSize}px`,
|
|
114
|
+
"--toolbar-size": `${FloatingToolbarTokens.ContainerHeight}px`,
|
|
115
|
+
} as React.CSSProperties
|
|
116
|
+
}
|
|
117
|
+
>
|
|
118
|
+
{isFabBefore && fabContainerNode}
|
|
119
|
+
|
|
120
|
+
<AnimatePresence initial={false}>
|
|
121
|
+
{effectiveExpanded && (
|
|
122
|
+
<m.div
|
|
123
|
+
key="toolbar-content-wrapper"
|
|
124
|
+
initial={{
|
|
125
|
+
opacity: 0,
|
|
126
|
+
scale: 0.9,
|
|
127
|
+
width: isHorizontal ? 0 : "auto",
|
|
128
|
+
height: !isHorizontal ? 0 : "auto",
|
|
129
|
+
}}
|
|
130
|
+
animate={{
|
|
131
|
+
opacity: 1,
|
|
132
|
+
scale: 1,
|
|
133
|
+
width: "auto",
|
|
134
|
+
height: "auto",
|
|
135
|
+
}}
|
|
136
|
+
exit={{
|
|
137
|
+
opacity: 0,
|
|
138
|
+
scale: 0.9,
|
|
139
|
+
width: isHorizontal ? 0 : "auto",
|
|
140
|
+
height: !isHorizontal ? 0 : "auto",
|
|
141
|
+
}}
|
|
142
|
+
transition={transitionSpec}
|
|
143
|
+
style={{
|
|
144
|
+
padding: SHADOW_PADDING,
|
|
145
|
+
margin: -SHADOW_PADDING,
|
|
146
|
+
}}
|
|
147
|
+
className="flex items-center shrink-0 min-w-0 min-h-0 overflow-hidden relative z-0"
|
|
148
|
+
>
|
|
149
|
+
{isHorizontal ? (
|
|
150
|
+
<HorizontalFloatingToolbar
|
|
151
|
+
expanded={expanded}
|
|
152
|
+
colors={colors}
|
|
153
|
+
disableScrollTranslation
|
|
154
|
+
disableLayoutAnimation
|
|
155
|
+
{...props}
|
|
156
|
+
/>
|
|
157
|
+
) : (
|
|
158
|
+
<VerticalFloatingToolbar
|
|
159
|
+
expanded={expanded}
|
|
160
|
+
colors={colors}
|
|
161
|
+
disableScrollTranslation
|
|
162
|
+
disableLayoutAnimation
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
</m.div>
|
|
167
|
+
)}
|
|
168
|
+
</AnimatePresence>
|
|
169
|
+
|
|
170
|
+
{!isFabBefore && fabContainerNode}
|
|
171
|
+
</m.div>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
FloatingToolbarWithFabBase.displayName = "FloatingToolbarWithFabBase";
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A horizontal floating toolbar paired with a FAB.
|
|
180
|
+
*/
|
|
181
|
+
export const HorizontalFloatingToolbarWithFab = React.forwardRef<
|
|
182
|
+
HTMLDivElement,
|
|
183
|
+
FloatingToolbarWithFabProps
|
|
184
|
+
>((props, ref) => (
|
|
185
|
+
<FloatingToolbarWithFabBase ref={ref} orientation="horizontal" {...props} />
|
|
186
|
+
));
|
|
187
|
+
HorizontalFloatingToolbarWithFab.displayName =
|
|
188
|
+
"HorizontalFloatingToolbarWithFab";
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A vertical floating toolbar paired with a FAB.
|
|
192
|
+
*/
|
|
193
|
+
export const VerticalFloatingToolbarWithFab = React.forwardRef<
|
|
194
|
+
HTMLDivElement,
|
|
195
|
+
FloatingToolbarWithFabProps
|
|
196
|
+
>((props, ref) => (
|
|
197
|
+
<FloatingToolbarWithFabBase ref={ref} orientation="vertical" {...props} />
|
|
198
|
+
));
|
|
199
|
+
VerticalFloatingToolbarWithFab.displayName = "VerticalFloatingToolbarWithFab";
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
HorizontalFloatingToolbar,
|
|
6
|
+
VerticalFloatingToolbar,
|
|
7
|
+
} from "./floating-toolbar";
|
|
8
|
+
import { useFloatingToolbarScrollBehavior } from "./toolbar-scroll-behavior";
|
|
9
|
+
|
|
10
|
+
// Mock matchMedia for jsdom
|
|
11
|
+
Object.defineProperty(window, "matchMedia", {
|
|
12
|
+
writable: true,
|
|
13
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
14
|
+
matches: false,
|
|
15
|
+
media: query,
|
|
16
|
+
onchange: null,
|
|
17
|
+
addListener: vi.fn(), // Deprecated
|
|
18
|
+
removeListener: vi.fn(), // Deprecated
|
|
19
|
+
addEventListener: vi.fn(),
|
|
20
|
+
removeEventListener: vi.fn(),
|
|
21
|
+
dispatchEvent: vi.fn(),
|
|
22
|
+
})),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("HorizontalFloatingToolbar", () => {
|
|
26
|
+
it('renders horizontal variant with correct role="toolbar"', () => {
|
|
27
|
+
render(
|
|
28
|
+
<HorizontalFloatingToolbar expanded={true}>
|
|
29
|
+
<button type="button">Test</button>
|
|
30
|
+
</HorizontalFloatingToolbar>,
|
|
31
|
+
);
|
|
32
|
+
expect(screen.getByRole("toolbar")).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("shows startContent when expanded=true", () => {
|
|
36
|
+
render(
|
|
37
|
+
<HorizontalFloatingToolbar
|
|
38
|
+
expanded={true}
|
|
39
|
+
startContent={<span data-testid="leading" />}
|
|
40
|
+
>
|
|
41
|
+
<button type="button">Test</button>
|
|
42
|
+
</HorizontalFloatingToolbar>,
|
|
43
|
+
);
|
|
44
|
+
expect(screen.getByTestId("leading")).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("hides startContent when expanded=false", () => {
|
|
48
|
+
render(
|
|
49
|
+
<HorizontalFloatingToolbar
|
|
50
|
+
expanded={false}
|
|
51
|
+
startContent={<span data-testid="leading" />}
|
|
52
|
+
>
|
|
53
|
+
<button type="button">Test</button>
|
|
54
|
+
</HorizontalFloatingToolbar>,
|
|
55
|
+
);
|
|
56
|
+
// Framer motion with AnimatePresence might still have it briefly during exit, but initial=false means it shouldn't render at all
|
|
57
|
+
expect(screen.queryByTestId("leading")).not.toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("shows endContent when expanded=true", () => {
|
|
61
|
+
render(
|
|
62
|
+
<HorizontalFloatingToolbar
|
|
63
|
+
expanded={true}
|
|
64
|
+
endContent={<span data-testid="trailing" />}
|
|
65
|
+
>
|
|
66
|
+
<button type="button">Test</button>
|
|
67
|
+
</HorizontalFloatingToolbar>,
|
|
68
|
+
);
|
|
69
|
+
expect(screen.getByTestId("trailing")).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("applies standard colors by default", () => {
|
|
73
|
+
render(
|
|
74
|
+
<HorizontalFloatingToolbar expanded={true}>
|
|
75
|
+
<button type="button">Test</button>
|
|
76
|
+
</HorizontalFloatingToolbar>,
|
|
77
|
+
);
|
|
78
|
+
// standard colors map to surface-container
|
|
79
|
+
expect(screen.getByRole("toolbar")).toHaveStyle({
|
|
80
|
+
backgroundColor: "var(--toolbar-bg)",
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("applies vibrant colors when custom colors are passed", () => {
|
|
85
|
+
const vibrantColors = {
|
|
86
|
+
toolbarContainerColor: "var(--md-sys-color-primary-container)",
|
|
87
|
+
toolbarContentColor: "var(--md-sys-color-on-primary-container)",
|
|
88
|
+
};
|
|
89
|
+
render(
|
|
90
|
+
<HorizontalFloatingToolbar expanded={true} colors={vibrantColors}>
|
|
91
|
+
<button type="button">Test</button>
|
|
92
|
+
</HorizontalFloatingToolbar>,
|
|
93
|
+
);
|
|
94
|
+
const style = screen.getByRole("toolbar").style;
|
|
95
|
+
expect(style.getPropertyValue("--toolbar-bg")).toBe(
|
|
96
|
+
"var(--md-sys-color-primary-container)",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("applies custom className", () => {
|
|
101
|
+
render(
|
|
102
|
+
<HorizontalFloatingToolbar expanded={true} className="custom-class">
|
|
103
|
+
<button type="button">Test</button>
|
|
104
|
+
</HorizontalFloatingToolbar>,
|
|
105
|
+
);
|
|
106
|
+
expect(screen.getByRole("toolbar")).toHaveClass("custom-class");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("forwards ref to container element", () => {
|
|
110
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
111
|
+
render(
|
|
112
|
+
<HorizontalFloatingToolbar expanded={true} ref={ref}>
|
|
113
|
+
<button type="button">Test</button>
|
|
114
|
+
</HorizontalFloatingToolbar>,
|
|
115
|
+
);
|
|
116
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("aria-label is applied to toolbar container", () => {
|
|
120
|
+
render(
|
|
121
|
+
<HorizontalFloatingToolbar expanded={true} aria-label="Test Label">
|
|
122
|
+
<button type="button">Test</button>
|
|
123
|
+
</HorizontalFloatingToolbar>,
|
|
124
|
+
);
|
|
125
|
+
expect(screen.getByRole("toolbar")).toHaveAttribute(
|
|
126
|
+
"aria-label",
|
|
127
|
+
"Test Label",
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("snapshots: horizontal expanded", () => {
|
|
132
|
+
const { container } = render(
|
|
133
|
+
<HorizontalFloatingToolbar
|
|
134
|
+
expanded={true}
|
|
135
|
+
startContent={<span>Lead</span>}
|
|
136
|
+
endContent={<span>Trail</span>}
|
|
137
|
+
>
|
|
138
|
+
<button type="button">Center</button>
|
|
139
|
+
</HorizontalFloatingToolbar>,
|
|
140
|
+
);
|
|
141
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("snapshots: horizontal collapsed", () => {
|
|
145
|
+
const { container } = render(
|
|
146
|
+
<HorizontalFloatingToolbar
|
|
147
|
+
expanded={false}
|
|
148
|
+
startContent={<span>Lead</span>}
|
|
149
|
+
endContent={<span>Trail</span>}
|
|
150
|
+
>
|
|
151
|
+
<button type="button">Center</button>
|
|
152
|
+
</HorizontalFloatingToolbar>,
|
|
153
|
+
);
|
|
154
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("VerticalFloatingToolbar", () => {
|
|
159
|
+
it("renders vertical variant", () => {
|
|
160
|
+
render(
|
|
161
|
+
<VerticalFloatingToolbar expanded={true}>
|
|
162
|
+
<button type="button">Test</button>
|
|
163
|
+
</VerticalFloatingToolbar>,
|
|
164
|
+
);
|
|
165
|
+
expect(screen.getByRole("toolbar")).toHaveAttribute(
|
|
166
|
+
"aria-orientation",
|
|
167
|
+
"vertical",
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("snapshots: vertical expanded", () => {
|
|
172
|
+
const { container } = render(
|
|
173
|
+
<VerticalFloatingToolbar
|
|
174
|
+
expanded={true}
|
|
175
|
+
startContent={<span>Lead</span>}
|
|
176
|
+
endContent={<span>Trail</span>}
|
|
177
|
+
>
|
|
178
|
+
<button type="button">Center</button>
|
|
179
|
+
</VerticalFloatingToolbar>,
|
|
180
|
+
);
|
|
181
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("snapshots: vertical collapsed", () => {
|
|
185
|
+
const { container } = render(
|
|
186
|
+
<VerticalFloatingToolbar
|
|
187
|
+
expanded={false}
|
|
188
|
+
startContent={<span>Lead</span>}
|
|
189
|
+
endContent={<span>Trail</span>}
|
|
190
|
+
>
|
|
191
|
+
<button type="button">Center</button>
|
|
192
|
+
</VerticalFloatingToolbar>,
|
|
193
|
+
);
|
|
194
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("useFloatingToolbarScrollBehavior", () => {
|
|
199
|
+
it("calls onScroll handler when scrollBehavior is provided", () => {
|
|
200
|
+
const TestComponent = () => {
|
|
201
|
+
const scrollBehavior = useFloatingToolbarScrollBehavior({
|
|
202
|
+
collapseThreshold: 0,
|
|
203
|
+
expandThreshold: 0,
|
|
204
|
+
});
|
|
205
|
+
return (
|
|
206
|
+
<div
|
|
207
|
+
data-testid="scrollable"
|
|
208
|
+
onScroll={scrollBehavior.onScroll}
|
|
209
|
+
style={{ height: 100, overflow: "auto" }}
|
|
210
|
+
>
|
|
211
|
+
<div style={{ height: 500 }}>
|
|
212
|
+
<HorizontalFloatingToolbar
|
|
213
|
+
expanded={scrollBehavior.isExpanded}
|
|
214
|
+
scrollBehavior={scrollBehavior}
|
|
215
|
+
>
|
|
216
|
+
<button type="button">Test</button>
|
|
217
|
+
</HorizontalFloatingToolbar>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
render(<TestComponent />);
|
|
224
|
+
const scrollable = screen.getByTestId("scrollable");
|
|
225
|
+
|
|
226
|
+
fireEvent.scroll(scrollable, { target: { scrollTop: 50 } });
|
|
227
|
+
// Without full DOM we can't easily test the exact behavior of the hook state visually
|
|
228
|
+
// but we check it doesn't crash
|
|
229
|
+
});
|
|
230
|
+
});
|