@sproutsocial/seeds-react-drawer 1.2.11 → 2.2.7
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/dist/drawer.css +248 -0
- package/dist/esm/index.js +599 -189
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +109 -19
- package/dist/index.d.ts +109 -19
- package/dist/index.js +607 -191
- package/dist/index.js.map +1 -1
- package/package.json +24 -12
- package/.eslintignore +0 -6
- package/.eslintrc.js +0 -4
- package/.turbo/turbo-build.log +0 -21
- package/CHANGELOG.md +0 -350
- package/jest.config.js +0 -9
- package/src/Drawer.stories.tsx +0 -474
- package/src/Drawer.tsx +0 -291
- package/src/DrawerTypes.ts +0 -88
- package/src/__tests__/Drawer.test.tsx +0 -339
- package/src/__tests__/Drawer.typetest.tsx +0 -39
- package/src/index.ts +0 -5
- package/src/styles.ts +0 -35
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -12
package/src/Drawer.tsx
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useContext, useEffect, useRef } from "react";
|
|
3
|
-
import FocusLock from "react-focus-lock";
|
|
4
|
-
import { motion, AnimatePresence } from "motion/react";
|
|
5
|
-
import { MOTION_DURATION_MEDIUM } from "@sproutsocial/seeds-motion/unitless";
|
|
6
|
-
import Box from "@sproutsocial/seeds-react-box";
|
|
7
|
-
import Button from "@sproutsocial/seeds-react-button";
|
|
8
|
-
import Icon from "@sproutsocial/seeds-react-icon";
|
|
9
|
-
// eslint-disable-next-line import/no-deprecated
|
|
10
|
-
import Text from "@sproutsocial/seeds-react-text";
|
|
11
|
-
import Portal from "@sproutsocial/seeds-react-portal";
|
|
12
|
-
import Container, { Content } from "./styles";
|
|
13
|
-
import type {
|
|
14
|
-
TypeDrawerContext,
|
|
15
|
-
TypeDrawerCloseButtonProps,
|
|
16
|
-
TypeDrawerHeaderProps,
|
|
17
|
-
TypeDrawerProps,
|
|
18
|
-
TypeInnerDrawerProps,
|
|
19
|
-
TypeDrawerContentProps,
|
|
20
|
-
TypeUseCloseOnBodyClickProps,
|
|
21
|
-
} from "./DrawerTypes";
|
|
22
|
-
|
|
23
|
-
const MotionContainer = motion(Container as any);
|
|
24
|
-
|
|
25
|
-
const doesRefContainEventTarget = (
|
|
26
|
-
ref: { current: { contains: (arg0: any) => any } },
|
|
27
|
-
event: Event
|
|
28
|
-
) => {
|
|
29
|
-
return (
|
|
30
|
-
ref.current &&
|
|
31
|
-
event.target instanceof Node &&
|
|
32
|
-
ref.current.contains(event.target)
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const DrawerContext = React.createContext<TypeDrawerContext>({
|
|
37
|
-
isLocked: false,
|
|
38
|
-
setIsLocked: () => {},
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {
|
|
42
|
-
const drawerContext = useContext(DrawerContext);
|
|
43
|
-
|
|
44
|
-
if (props.render) {
|
|
45
|
-
return props.render(drawerContext) ?? null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Button
|
|
50
|
-
appearance="pill"
|
|
51
|
-
aria-label={drawerContext.closeButtonLabel}
|
|
52
|
-
onClick={drawerContext.onClose}
|
|
53
|
-
{...props}
|
|
54
|
-
>
|
|
55
|
-
{props.children || <Icon aria-hidden name="x-outline" />}
|
|
56
|
-
</Button>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const DrawerHeader = ({
|
|
61
|
-
title = "",
|
|
62
|
-
id = undefined,
|
|
63
|
-
children,
|
|
64
|
-
render,
|
|
65
|
-
...rest
|
|
66
|
-
}: TypeDrawerHeaderProps) => {
|
|
67
|
-
const drawerContext = useContext(DrawerContext);
|
|
68
|
-
|
|
69
|
-
if (render) {
|
|
70
|
-
return render(drawerContext);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<Box
|
|
75
|
-
display="flex"
|
|
76
|
-
flex="0 0 auto"
|
|
77
|
-
justifyContent="space-between"
|
|
78
|
-
alignItems="center"
|
|
79
|
-
pt={400}
|
|
80
|
-
px={450}
|
|
81
|
-
{...rest}
|
|
82
|
-
>
|
|
83
|
-
{children || (
|
|
84
|
-
<React.Fragment>
|
|
85
|
-
<Text
|
|
86
|
-
as="h2"
|
|
87
|
-
fontSize={400}
|
|
88
|
-
fontWeight="semibold"
|
|
89
|
-
color="text.headline"
|
|
90
|
-
id={id}
|
|
91
|
-
>
|
|
92
|
-
{title}
|
|
93
|
-
</Text>
|
|
94
|
-
<DrawerCloseButton />
|
|
95
|
-
</React.Fragment>
|
|
96
|
-
)}
|
|
97
|
-
</Box>
|
|
98
|
-
);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const DrawerContent = ({ children, ...rest }: TypeDrawerContentProps) => (
|
|
102
|
-
<Content height="100%" p={450} color="text.body" {...rest}>
|
|
103
|
-
{children}
|
|
104
|
-
</Content>
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const useCloseOnBodyClick = ({
|
|
108
|
-
isOpen,
|
|
109
|
-
ref,
|
|
110
|
-
disableCloseOnClickOutside,
|
|
111
|
-
onClose,
|
|
112
|
-
closeTargets,
|
|
113
|
-
}: TypeUseCloseOnBodyClickProps) => {
|
|
114
|
-
const { isLocked } = useContext(DrawerContext);
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
const documentBody = document.body;
|
|
118
|
-
|
|
119
|
-
if (!documentBody || !isOpen) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const onEsc = (event: KeyboardEvent): void => {
|
|
124
|
-
if (!isLocked && event.key === "Escape") {
|
|
125
|
-
onClose();
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const bodyClick = (event: Event): void => {
|
|
130
|
-
if (
|
|
131
|
-
// @ts-ignore I'm not sure how to type this ref properly
|
|
132
|
-
!doesRefContainEventTarget(ref, event) &&
|
|
133
|
-
!disableCloseOnClickOutside
|
|
134
|
-
) {
|
|
135
|
-
onClose();
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
documentBody?.addEventListener("keydown", onEsc, { capture: true });
|
|
140
|
-
|
|
141
|
-
if (closeTargets) {
|
|
142
|
-
closeTargets.forEach((targetElement) =>
|
|
143
|
-
targetElement?.addEventListener("click", bodyClick, { capture: true })
|
|
144
|
-
);
|
|
145
|
-
} else {
|
|
146
|
-
documentBody.firstElementChild?.addEventListener("click", bodyClick, {
|
|
147
|
-
capture: true,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return () => {
|
|
152
|
-
documentBody?.removeEventListener("keydown", onEsc, { capture: true });
|
|
153
|
-
|
|
154
|
-
if (closeTargets) {
|
|
155
|
-
closeTargets.forEach((targetElement) =>
|
|
156
|
-
targetElement?.removeEventListener("click", bodyClick, {
|
|
157
|
-
capture: true,
|
|
158
|
-
})
|
|
159
|
-
);
|
|
160
|
-
} else {
|
|
161
|
-
documentBody.firstElementChild?.removeEventListener(
|
|
162
|
-
"click",
|
|
163
|
-
bodyClick,
|
|
164
|
-
{ capture: true }
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
}, [
|
|
169
|
-
onClose,
|
|
170
|
-
disableCloseOnClickOutside,
|
|
171
|
-
closeTargets,
|
|
172
|
-
ref,
|
|
173
|
-
isLocked,
|
|
174
|
-
isOpen,
|
|
175
|
-
]);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const Drawer = ({
|
|
179
|
-
id,
|
|
180
|
-
offset,
|
|
181
|
-
direction,
|
|
182
|
-
children,
|
|
183
|
-
disableCloseOnClickOutside,
|
|
184
|
-
onClose,
|
|
185
|
-
zIndex,
|
|
186
|
-
closeTargets,
|
|
187
|
-
width,
|
|
188
|
-
focusLockExemptCheck,
|
|
189
|
-
isOpen,
|
|
190
|
-
...rest
|
|
191
|
-
}: TypeInnerDrawerProps) => {
|
|
192
|
-
const ref = useRef(null);
|
|
193
|
-
useCloseOnBodyClick({
|
|
194
|
-
ref,
|
|
195
|
-
disableCloseOnClickOutside,
|
|
196
|
-
onClose,
|
|
197
|
-
closeTargets,
|
|
198
|
-
isOpen,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const offset_x = width * (direction === "left" ? -1 : 1);
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<FocusLock
|
|
205
|
-
key={id}
|
|
206
|
-
autoFocus={true}
|
|
207
|
-
disabled={!isOpen}
|
|
208
|
-
returnFocus
|
|
209
|
-
whiteList={
|
|
210
|
-
focusLockExemptCheck ? (e) => !focusLockExemptCheck(e) : undefined
|
|
211
|
-
}
|
|
212
|
-
>
|
|
213
|
-
<AnimatePresence>
|
|
214
|
-
{isOpen && (
|
|
215
|
-
<MotionContainer
|
|
216
|
-
ref={ref}
|
|
217
|
-
style={{ zIndex }}
|
|
218
|
-
width={width}
|
|
219
|
-
offset={offset}
|
|
220
|
-
direction={direction}
|
|
221
|
-
data-qa-drawer={id}
|
|
222
|
-
role="dialog"
|
|
223
|
-
initial={{ opacity: 0, x: offset_x }}
|
|
224
|
-
animate={{ opacity: 1, x: 0 }}
|
|
225
|
-
exit={{ opacity: 0, x: offset_x }}
|
|
226
|
-
transition={{ duration: MOTION_DURATION_MEDIUM }}
|
|
227
|
-
{...rest}
|
|
228
|
-
>
|
|
229
|
-
{children}
|
|
230
|
-
</MotionContainer>
|
|
231
|
-
)}
|
|
232
|
-
</AnimatePresence>
|
|
233
|
-
</FocusLock>
|
|
234
|
-
);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const DrawerContainer = ({
|
|
238
|
-
children,
|
|
239
|
-
closeButtonLabel,
|
|
240
|
-
direction = "right",
|
|
241
|
-
disableCloseOnClickOutside = false,
|
|
242
|
-
id,
|
|
243
|
-
isOpen,
|
|
244
|
-
offset = 0,
|
|
245
|
-
onClose,
|
|
246
|
-
zIndex = 7,
|
|
247
|
-
closeTargets = [],
|
|
248
|
-
width = 600,
|
|
249
|
-
...rest
|
|
250
|
-
}: TypeDrawerProps) => {
|
|
251
|
-
const [isLocked, setIsLocked] = React.useState(false);
|
|
252
|
-
return (
|
|
253
|
-
<Portal id={id}>
|
|
254
|
-
<DrawerContext.Provider
|
|
255
|
-
value={{
|
|
256
|
-
onClose,
|
|
257
|
-
closeButtonLabel,
|
|
258
|
-
isLocked,
|
|
259
|
-
setIsLocked,
|
|
260
|
-
}}
|
|
261
|
-
>
|
|
262
|
-
<Drawer
|
|
263
|
-
isOpen={isOpen}
|
|
264
|
-
id={id}
|
|
265
|
-
offset={offset}
|
|
266
|
-
direction={direction}
|
|
267
|
-
disableCloseOnClickOutside={disableCloseOnClickOutside}
|
|
268
|
-
onClose={onClose}
|
|
269
|
-
zIndex={zIndex}
|
|
270
|
-
closeTargets={closeTargets}
|
|
271
|
-
width={width}
|
|
272
|
-
data-qa-drawer={id || ""}
|
|
273
|
-
data-qa-drawer-isopen={isOpen}
|
|
274
|
-
{...rest}
|
|
275
|
-
>
|
|
276
|
-
{children}
|
|
277
|
-
</Drawer>
|
|
278
|
-
</DrawerContext.Provider>
|
|
279
|
-
</Portal>
|
|
280
|
-
);
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
DrawerHeader.displayName = "Drawer.Header";
|
|
284
|
-
DrawerContent.displayName = "Drawer.Content";
|
|
285
|
-
DrawerCloseButton.displayName = "Drawer.CloseButton";
|
|
286
|
-
|
|
287
|
-
DrawerContainer.Header = DrawerHeader;
|
|
288
|
-
DrawerContainer.Content = DrawerContent;
|
|
289
|
-
DrawerContainer.CloseButton = DrawerCloseButton;
|
|
290
|
-
|
|
291
|
-
export default DrawerContainer;
|
package/src/DrawerTypes.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type {
|
|
3
|
-
TypeSystemCommonProps,
|
|
4
|
-
TypeStyledComponentsCommonProps,
|
|
5
|
-
} from "@sproutsocial/seeds-react-system-props";
|
|
6
|
-
import type { TypeBoxProps } from "@sproutsocial/seeds-react-box";
|
|
7
|
-
import type { TypeButtonProps } from "@sproutsocial/seeds-react-button";
|
|
8
|
-
|
|
9
|
-
type DrawerAnimationDirection = "left" | "right";
|
|
10
|
-
|
|
11
|
-
export interface TypeDrawerContext {
|
|
12
|
-
/** Callback for close button */
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
-
onClose?: () => any;
|
|
15
|
-
|
|
16
|
-
/** aria-label for drawer close button */
|
|
17
|
-
closeButtonLabel?: string;
|
|
18
|
-
/**
|
|
19
|
-
* isLocked and setIsLocked are used when a Drawer contains other components (like Menu) that also
|
|
20
|
-
* listen for Escape key events. By locking the Drawer, we can prevent conflicts
|
|
21
|
-
* where multiple components try to handle the same keydown event.
|
|
22
|
-
*/
|
|
23
|
-
isLocked: boolean;
|
|
24
|
-
setIsLocked: (locked: boolean) => void;
|
|
25
|
-
}
|
|
26
|
-
// TODO: Should the render prop be a React.FC?
|
|
27
|
-
export interface TypeDrawerCloseButtonProps
|
|
28
|
-
extends Omit<TypeButtonProps, "children"> {
|
|
29
|
-
/** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */
|
|
30
|
-
render?: React.FC<TypeDrawerContext>;
|
|
31
|
-
children?: React.ReactNode;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface TypeDrawerHeaderProps extends TypeBoxProps {
|
|
35
|
-
title?: string;
|
|
36
|
-
children?: React.ReactNode;
|
|
37
|
-
|
|
38
|
-
/** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the appearance of the header. */
|
|
39
|
-
render?: React.FC<TypeDrawerContext>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface TypeInnerDrawerProps
|
|
43
|
-
extends Omit<TypeDrawerProps, "closeButtonLabel"> {
|
|
44
|
-
width: number;
|
|
45
|
-
direction: DrawerAnimationDirection;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
type useBodyClicksProps = Pick<
|
|
49
|
-
TypeDrawerProps,
|
|
50
|
-
"closeTargets" | "onClose" | "disableCloseOnClickOutside"
|
|
51
|
-
>;
|
|
52
|
-
|
|
53
|
-
export interface TypeUseCloseOnBodyClickProps
|
|
54
|
-
extends Pick<
|
|
55
|
-
TypeDrawerProps,
|
|
56
|
-
"closeTargets" | "onClose" | "disableCloseOnClickOutside"
|
|
57
|
-
> {
|
|
58
|
-
ref?: React.RefObject<HTMLElement | null>;
|
|
59
|
-
isOpen: boolean;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface TypeDrawerProps
|
|
63
|
-
extends TypeStyledComponentsCommonProps,
|
|
64
|
-
TypeSystemCommonProps,
|
|
65
|
-
Omit<React.ComponentPropsWithoutRef<"nav">, "color"> {
|
|
66
|
-
children: React.ReactNode;
|
|
67
|
-
|
|
68
|
-
/** Label for the close button. Usually this should be "Close" */
|
|
69
|
-
closeButtonLabel: string;
|
|
70
|
-
|
|
71
|
-
/** Whether the drawer slides in from the left or right side of the screen */
|
|
72
|
-
direction?: DrawerAnimationDirection;
|
|
73
|
-
|
|
74
|
-
/** In some cases, you may not want the user to be able to click outside of the drawer to close it. You can disable that with this prop. */
|
|
75
|
-
disableCloseOnClickOutside?: boolean;
|
|
76
|
-
id: string;
|
|
77
|
-
isOpen: boolean;
|
|
78
|
-
offset?: number;
|
|
79
|
-
onClose: () => void;
|
|
80
|
-
zIndex?: number;
|
|
81
|
-
closeTargets?: Array<Element>;
|
|
82
|
-
width?: number;
|
|
83
|
-
focusLockExemptCheck?: (element: HTMLElement) => boolean;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface TypeDrawerContentProps extends TypeBoxProps {
|
|
87
|
-
children?: React.ReactNode;
|
|
88
|
-
}
|
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
/* eslint-disable testing-library/prefer-screen-queries */
|
|
2
|
-
|
|
3
|
-
import React, { useState, useContext } from "react";
|
|
4
|
-
import {
|
|
5
|
-
render as testRender,
|
|
6
|
-
waitFor,
|
|
7
|
-
screen,
|
|
8
|
-
act,
|
|
9
|
-
} from "@sproutsocial/seeds-react-testing-library";
|
|
10
|
-
import type { TypeDrawerProps } from "../DrawerTypes";
|
|
11
|
-
import Drawer, { DrawerContext } from "../Drawer";
|
|
12
|
-
|
|
13
|
-
const StatefulDrawer = ({
|
|
14
|
-
isOpen,
|
|
15
|
-
onClose,
|
|
16
|
-
children,
|
|
17
|
-
...rest
|
|
18
|
-
}: TypeDrawerProps) => {
|
|
19
|
-
const [isDrawerOpen, setIsDrawerOpen] = useState(isOpen || false);
|
|
20
|
-
|
|
21
|
-
const onDrawerClose = () => {
|
|
22
|
-
act(() => {
|
|
23
|
-
setIsDrawerOpen(false);
|
|
24
|
-
});
|
|
25
|
-
onClose();
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<Drawer
|
|
30
|
-
{...rest}
|
|
31
|
-
isOpen={isDrawerOpen}
|
|
32
|
-
onClose={onDrawerClose}
|
|
33
|
-
id="1"
|
|
34
|
-
closeButtonLabel="close button"
|
|
35
|
-
>
|
|
36
|
-
{children}
|
|
37
|
-
</Drawer>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const render = ({
|
|
42
|
-
isOpen = false,
|
|
43
|
-
direction,
|
|
44
|
-
disableCloseOnClickOutside,
|
|
45
|
-
children = (
|
|
46
|
-
<React.Fragment>
|
|
47
|
-
<Drawer.Header title="Drawer Header" id="drawer-1-header" />
|
|
48
|
-
<Drawer.Content>
|
|
49
|
-
<p>Drawer Content</p>
|
|
50
|
-
</Drawer.Content>
|
|
51
|
-
</React.Fragment>
|
|
52
|
-
),
|
|
53
|
-
offset,
|
|
54
|
-
onClose = jest.fn(),
|
|
55
|
-
id = "drawer",
|
|
56
|
-
closeButtonLabel = "close",
|
|
57
|
-
width = 600,
|
|
58
|
-
}: Partial<TypeDrawerProps>) => {
|
|
59
|
-
const { baseElement } = testRender(<div id="main-content" />);
|
|
60
|
-
const mainContentRef = baseElement.querySelector("#main-content");
|
|
61
|
-
return {
|
|
62
|
-
...testRender(
|
|
63
|
-
<div>
|
|
64
|
-
{baseElement.innerHTML}
|
|
65
|
-
<StatefulDrawer
|
|
66
|
-
isOpen={isOpen}
|
|
67
|
-
direction={direction}
|
|
68
|
-
offset={offset}
|
|
69
|
-
onClose={onClose}
|
|
70
|
-
closeButtonLabel={closeButtonLabel}
|
|
71
|
-
id={id}
|
|
72
|
-
disableCloseOnClickOutside={disableCloseOnClickOutside}
|
|
73
|
-
// @ts-ignore - We'll come back to test updating this
|
|
74
|
-
closeTargets={[mainContentRef]}
|
|
75
|
-
width={width}
|
|
76
|
-
>
|
|
77
|
-
{children}
|
|
78
|
-
</StatefulDrawer>
|
|
79
|
-
</div>
|
|
80
|
-
),
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
describe("Drawer", () => {
|
|
85
|
-
it("should not be in the document by default", () => {
|
|
86
|
-
render({});
|
|
87
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("should render drawer", () => {
|
|
91
|
-
render({
|
|
92
|
-
isOpen: true,
|
|
93
|
-
});
|
|
94
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should close drawer on header click", async () => {
|
|
98
|
-
const { user } = render({
|
|
99
|
-
isOpen: true,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
103
|
-
await act(async () => {
|
|
104
|
-
await user.click(screen.getByLabelText("close button"));
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await waitFor(() => {
|
|
108
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("should close drawer on header click when using custom header", async () => {
|
|
113
|
-
const { user } = render({
|
|
114
|
-
isOpen: true,
|
|
115
|
-
children: (
|
|
116
|
-
<React.Fragment>
|
|
117
|
-
<Drawer.Header>
|
|
118
|
-
<h1>Title</h1>
|
|
119
|
-
<Drawer.CloseButton />
|
|
120
|
-
</Drawer.Header>
|
|
121
|
-
<Drawer.Content>
|
|
122
|
-
<p>Drawer Content</p>
|
|
123
|
-
</Drawer.Content>
|
|
124
|
-
</React.Fragment>
|
|
125
|
-
),
|
|
126
|
-
});
|
|
127
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
128
|
-
await act(async () => {
|
|
129
|
-
await user.click(screen.getByLabelText("close button"));
|
|
130
|
-
});
|
|
131
|
-
await waitFor(() => {
|
|
132
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("should close drawer on outside click", async () => {
|
|
137
|
-
const { user, baseElement } = render({
|
|
138
|
-
isOpen: true,
|
|
139
|
-
});
|
|
140
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
141
|
-
await act(async () => {
|
|
142
|
-
await user.click(baseElement.querySelector("#main-content") as Element);
|
|
143
|
-
});
|
|
144
|
-
await waitFor(() => {
|
|
145
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("should not close drawer on outside click when disabled", async () => {
|
|
150
|
-
const { user, baseElement } = render({
|
|
151
|
-
isOpen: true,
|
|
152
|
-
disableCloseOnClickOutside: true,
|
|
153
|
-
});
|
|
154
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
155
|
-
await user.click(baseElement.querySelector("#main-content") as Element);
|
|
156
|
-
await waitFor(() => {
|
|
157
|
-
// eslint-disable-next-line testing-library/prefer-presence-queries
|
|
158
|
-
expect(screen.queryByText(/drawer content/i)).toBeInTheDocument();
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("should close drawer on esc key", async () => {
|
|
163
|
-
const { user, baseElement } = render({
|
|
164
|
-
isOpen: true,
|
|
165
|
-
disableCloseOnClickOutside: true,
|
|
166
|
-
});
|
|
167
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
168
|
-
await act(async () => {
|
|
169
|
-
await user.keyboard("{Escape}");
|
|
170
|
-
});
|
|
171
|
-
await waitFor(() => {
|
|
172
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("should call onClose callback when drawer is closed", async () => {
|
|
177
|
-
const onClose = jest.fn();
|
|
178
|
-
const { user } = render({
|
|
179
|
-
isOpen: true,
|
|
180
|
-
onClose,
|
|
181
|
-
});
|
|
182
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
183
|
-
await act(async () => {
|
|
184
|
-
await user.click(screen.getByLabelText("close button"));
|
|
185
|
-
});
|
|
186
|
-
await waitFor(() => {
|
|
187
|
-
expect(onClose).toHaveBeenCalledTimes(1);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("should not call onClose callback when drawer is already closed", async () => {
|
|
192
|
-
const onClose = jest.fn();
|
|
193
|
-
render({
|
|
194
|
-
isOpen: false,
|
|
195
|
-
onClose,
|
|
196
|
-
});
|
|
197
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
198
|
-
expect(onClose).toHaveBeenCalledTimes(0);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("should have dialog role", () => {
|
|
202
|
-
render({
|
|
203
|
-
isOpen: true,
|
|
204
|
-
});
|
|
205
|
-
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("should have focus on drawer when opened", () => {
|
|
209
|
-
render({
|
|
210
|
-
isOpen: true,
|
|
211
|
-
});
|
|
212
|
-
expect(screen.getByLabelText("close button")).toHaveFocus();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("should have an h2 with id", () => {
|
|
216
|
-
render({
|
|
217
|
-
isOpen: true,
|
|
218
|
-
});
|
|
219
|
-
expect(screen.getByRole("heading")).toHaveAttribute(
|
|
220
|
-
"id",
|
|
221
|
-
"drawer-1-header"
|
|
222
|
-
);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it("should be 600px wide by default", () => {
|
|
226
|
-
render({ isOpen: true });
|
|
227
|
-
expect(screen.getByRole("dialog")).toHaveStyle("width: 600px");
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("should customize the drawer width when provided", () => {
|
|
231
|
-
render({ isOpen: true, width: 400 });
|
|
232
|
-
expect(screen.getByRole("dialog")).toHaveStyle("width: 400px");
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const LockButton = () => {
|
|
237
|
-
const [isActive, setIsActive] = useState(false);
|
|
238
|
-
const { setIsLocked } = useContext(DrawerContext);
|
|
239
|
-
|
|
240
|
-
const onClick = () => {
|
|
241
|
-
setIsActive(true);
|
|
242
|
-
setIsLocked(true);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
React.useEffect(() => {
|
|
246
|
-
const documentBody = document.body;
|
|
247
|
-
|
|
248
|
-
if (isActive && documentBody) {
|
|
249
|
-
const onEsc = (e: KeyboardEvent): void => {
|
|
250
|
-
if (["Escape", "Esc"].includes(e.key)) {
|
|
251
|
-
e.stopPropagation();
|
|
252
|
-
setIsActive(false);
|
|
253
|
-
setIsLocked(false);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
documentBody.addEventListener("keydown", onEsc, { capture: true });
|
|
258
|
-
return () => {
|
|
259
|
-
documentBody.removeEventListener("keydown", onEsc, { capture: true });
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
}, [isActive, setIsActive]);
|
|
263
|
-
|
|
264
|
-
return <button onClick={onClick}>Click to lock Escape key</button>;
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const ChildrenWithLockButton = () => (
|
|
268
|
-
<React.Fragment>
|
|
269
|
-
<Drawer.Header title="Drawer Header" id="drawer-1-header" />
|
|
270
|
-
<Drawer.Content>
|
|
271
|
-
<p>Drawer Content</p>
|
|
272
|
-
<LockButton />
|
|
273
|
-
</Drawer.Content>
|
|
274
|
-
</React.Fragment>
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
describe("Drawer with Lock", () => {
|
|
278
|
-
describe("Unlocked", () => {
|
|
279
|
-
it("should close drawer on esc key", async () => {
|
|
280
|
-
const { user } = render({
|
|
281
|
-
isOpen: true,
|
|
282
|
-
children: <ChildrenWithLockButton />,
|
|
283
|
-
});
|
|
284
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
285
|
-
await act(async () => {
|
|
286
|
-
await user.keyboard("{Escape}");
|
|
287
|
-
});
|
|
288
|
-
await waitFor(() => {
|
|
289
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe("Locked", () => {
|
|
295
|
-
it("should not close drawer on first esc key", async () => {
|
|
296
|
-
const { user } = render({
|
|
297
|
-
isOpen: true,
|
|
298
|
-
children: <ChildrenWithLockButton />,
|
|
299
|
-
});
|
|
300
|
-
const lockButton = screen.getByText("Click to lock Escape key");
|
|
301
|
-
|
|
302
|
-
await act(async () => {
|
|
303
|
-
await user.click(lockButton);
|
|
304
|
-
});
|
|
305
|
-
await act(async () => {
|
|
306
|
-
await user.keyboard("{Escape}");
|
|
307
|
-
});
|
|
308
|
-
await waitFor(() => {
|
|
309
|
-
expect(screen.queryByText(/drawer content/i)).toBeInTheDocument();
|
|
310
|
-
});
|
|
311
|
-
// lock is reset, handle escape as usual
|
|
312
|
-
await act(async () => {
|
|
313
|
-
await user.keyboard("{Escape}");
|
|
314
|
-
});
|
|
315
|
-
await waitFor(() => {
|
|
316
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
it("should close drawer on outside click", async () => {
|
|
320
|
-
const { user, baseElement } = render({
|
|
321
|
-
isOpen: true,
|
|
322
|
-
children: <ChildrenWithLockButton />,
|
|
323
|
-
});
|
|
324
|
-
const lockButton = screen.getByText("Click to lock Escape key");
|
|
325
|
-
|
|
326
|
-
await act(async () => {
|
|
327
|
-
await user.click(lockButton);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
|
|
331
|
-
await act(async () => {
|
|
332
|
-
await user.click(baseElement.querySelector("#main-content") as Element);
|
|
333
|
-
});
|
|
334
|
-
await waitFor(() => {
|
|
335
|
-
expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
});
|