@sproutsocial/seeds-react-drawer 1.0.0
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/.eslintignore +6 -0
- package/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +21 -0
- package/CHANGELOG.md +12 -0
- package/dist/esm/index.js +291 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +328 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +9 -0
- package/package.json +50 -0
- package/src/Drawer.stories.tsx +378 -0
- package/src/Drawer.tsx +308 -0
- package/src/DrawerTypes.ts +80 -0
- package/src/__tests__/Drawer.test.tsx +188 -0
- package/src/__tests__/Drawer.typetest.tsx +39 -0
- package/src/index.ts +5 -0
- package/src/styles.ts +35 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
4
|
+
import { Box } from "@sproutsocial/seeds-react-box";
|
|
5
|
+
import { Button } from "@sproutsocial/seeds-react-button";
|
|
6
|
+
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
7
|
+
// import { Tabs } from "@sproutsocial/seeds-react-tabs";
|
|
8
|
+
import { Text } from "@sproutsocial/seeds-react-text";
|
|
9
|
+
import Drawer from "./Drawer";
|
|
10
|
+
|
|
11
|
+
// @ts-ignore We'll come back to this later
|
|
12
|
+
const StatefulDrawer = ({ isOpen = true, onClose, children, ...rest }) => {
|
|
13
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(isOpen);
|
|
14
|
+
|
|
15
|
+
const onDrawerClose = () => {
|
|
16
|
+
setIsDrawerOpen(false);
|
|
17
|
+
onClose();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<React.Fragment>
|
|
22
|
+
<Drawer
|
|
23
|
+
{...rest}
|
|
24
|
+
isOpen={isDrawerOpen}
|
|
25
|
+
onClose={onDrawerClose}
|
|
26
|
+
closeButtonLabel="closer drawer"
|
|
27
|
+
id="drawer-1"
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Drawer>
|
|
31
|
+
<Button
|
|
32
|
+
appearance="primary"
|
|
33
|
+
onClick={() => setIsDrawerOpen(!isDrawerOpen)}
|
|
34
|
+
>
|
|
35
|
+
Toggle Drawer
|
|
36
|
+
</Button>
|
|
37
|
+
</React.Fragment>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// @ts-ignore We'll come back to this later
|
|
42
|
+
const DrawerComponent = ({ direction, offset, width }) => {
|
|
43
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
44
|
+
|
|
45
|
+
const onOpen = () => setIsOpen(true);
|
|
46
|
+
|
|
47
|
+
const onClose = () => setIsOpen(false);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Box>
|
|
51
|
+
<Box
|
|
52
|
+
width="100%"
|
|
53
|
+
bg="neutral.800"
|
|
54
|
+
display="flex"
|
|
55
|
+
justifyContent="flex-end"
|
|
56
|
+
padding={300}
|
|
57
|
+
>
|
|
58
|
+
<Button
|
|
59
|
+
appearance="secondary"
|
|
60
|
+
onClick={onOpen}
|
|
61
|
+
aria-label="View Notifications"
|
|
62
|
+
>
|
|
63
|
+
<Icon name="bell-outline" aria-hidden />
|
|
64
|
+
</Button>
|
|
65
|
+
</Box>
|
|
66
|
+
<Drawer
|
|
67
|
+
onClose={onClose}
|
|
68
|
+
isOpen={isOpen}
|
|
69
|
+
direction={direction}
|
|
70
|
+
offset={offset}
|
|
71
|
+
width={width}
|
|
72
|
+
id="notifications-drawer"
|
|
73
|
+
closeButtonLabel="close drawer"
|
|
74
|
+
>
|
|
75
|
+
<Drawer.Header title="Drawer Header" />
|
|
76
|
+
<Drawer.Content>
|
|
77
|
+
<Text color="text.body">Drawer Content</Text>
|
|
78
|
+
</Drawer.Content>
|
|
79
|
+
</Drawer>
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const BigDrawerContent = () => (
|
|
85
|
+
<Text.BodyCopy as="p">
|
|
86
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam ac consectetur
|
|
87
|
+
tortor. Fusce ac rutrum nibh. Phasellus rhoncus sed neque vitae dictum. Nunc
|
|
88
|
+
viverra venenatis pellentesque. Proin lorem massa, ornare in mauris eget,
|
|
89
|
+
commodo malesuada lacus. Phasellus quis purus in velit pharetra malesuada
|
|
90
|
+
sit amet nec libero. Fusce porta nulla id elit condimentum, a aliquam quam
|
|
91
|
+
placerat. Duis cursus vel felis non tincidunt. Cras sed quam at sapien
|
|
92
|
+
posuere scelerisque. Vivamus tortor sem, faucibus faucibus feugiat eu,
|
|
93
|
+
auctor vitae ex. Nunc vitae purus sit amet nulla imperdiet bibendum sit amet
|
|
94
|
+
elementum tortor. Nam vitae dui vitae metus aliquam aliquet. Curabitur
|
|
95
|
+
faucibus lacus ante, eu volutpat urna placerat vitae. Sed et porta mauris.
|
|
96
|
+
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
|
|
97
|
+
inceptos himenaeos. Praesent vel leo lectus.
|
|
98
|
+
<br />
|
|
99
|
+
<br />
|
|
100
|
+
Fusce ac vehicula dui. Quisque nec lacus a sapien lobortis sagittis eget
|
|
101
|
+
vitae lacus. Aenean quis lorem nec sapien vehicula consequat. Quisque
|
|
102
|
+
efficitur et eros in dapibus. Pellentesque sagittis vel dolor vitae
|
|
103
|
+
hendrerit. Ut elementum euismod iaculis. Maecenas lobortis urna lectus, nec
|
|
104
|
+
consequat quam facilisis a. Praesent aliquam ipsum quam, id dapibus nunc
|
|
105
|
+
facilisis ut. Ut rhoncus eu mauris nec congue. Etiam tempor ante in eros
|
|
106
|
+
ullamcorper feugiat. In nec diam leo. Quisque dignissim, purus nec porttitor
|
|
107
|
+
viverra, nibh odio gravida orci, at tempor odio arcu eget felis.
|
|
108
|
+
<br />
|
|
109
|
+
<br />
|
|
110
|
+
Sed fringilla sem leo, eget molestie elit lobortis eu. Donec eget risus quis
|
|
111
|
+
ante placerat vulputate sit amet at ex. Curabitur pulvinar pellentesque
|
|
112
|
+
justo sed iaculis. Praesent bibendum fringilla eros, at dapibus augue.
|
|
113
|
+
Pellentesque iaculis, nulla vestibulum pretium placerat, leo lorem laoreet
|
|
114
|
+
lorem, vel dignissim urna nunc vel leo. Suspendisse eu ipsum pellentesque,
|
|
115
|
+
pharetra diam sed, sollicitudin neque. Duis ornare dolor at ultricies
|
|
116
|
+
facilisis. Proin vel ullamcorper justo, tincidunt condimentum nunc.
|
|
117
|
+
<br />
|
|
118
|
+
<br />
|
|
119
|
+
Morbi et elementum diam. In et imperdiet sem, in viverra lorem. Fusce sit
|
|
120
|
+
amet urna felis. Cras eget dolor ac diam gravida hendrerit. Proin id urna
|
|
121
|
+
sed lorem mattis vehicula. Nullam nec magna auctor, consequat metus quis,
|
|
122
|
+
fermentum urna. Suspendisse nec turpis sodales, condimentum enim ut, sodales
|
|
123
|
+
diam. Donec luctus enim mauris, at semper lectus sagittis a. Suspendisse mi
|
|
124
|
+
enim, convallis et aliquam quis, pharetra quis lorem. Praesent congue
|
|
125
|
+
posuere tellus, id ullamcorper mi bibendum sit amet. Praesent turpis leo,
|
|
126
|
+
tempor quis ex sit amet, efficitur viverra velit. Praesent nisi sem, aliquet
|
|
127
|
+
non nunc a, fringilla efficitur neque. Pellentesque elit est, malesuada
|
|
128
|
+
finibus orci quis, lobortis varius tellus.
|
|
129
|
+
<br />
|
|
130
|
+
<br />
|
|
131
|
+
Sed interdum nulla massa, non mollis velit imperdiet et. Nam risus magna,
|
|
132
|
+
pulvinar vulputate tristique a, ornare blandit nunc. Nam massa odio,
|
|
133
|
+
venenatis ut accumsan vel, maximus lobortis purus. Vestibulum vitae ultrices
|
|
134
|
+
odio. Morbi vitae ante sed tellus molestie blandit. Morbi porttitor mi quis
|
|
135
|
+
laoreet commodo. Donec vel rutrum ipsum. Nunc sollicitudin lacinia eros quis
|
|
136
|
+
mollis. Sed interdum nulla massa, non mollis velit imperdiet et. Nam risus
|
|
137
|
+
magna, pulvinar vulputate tristique a, ornare blandit nunc. Nam massa odio,
|
|
138
|
+
venenatis ut accumsan vel, maximus lobortis purus. Vestibulum vitae ultrices
|
|
139
|
+
odio. Morbi vitae ante sed tellus molestie blandit. Morbi porttitor mi quis
|
|
140
|
+
laoreet commodo. Donec vel rutrum ipsum. Nunc sollicitudin lacinia eros quis
|
|
141
|
+
mollis. Sed interdum nulla massa, non mollis velit imperdiet et. Nam risus
|
|
142
|
+
magna, pulvinar vulputate tristique a, ornare blandit nunc. Nam massa odio,
|
|
143
|
+
venenatis ut accumsan vel, maximus lobortis purus. Vestibulum vitae ultrices
|
|
144
|
+
odio. Morbi vitae ante sed tellus molestie blandit. Morbi porttitor mi quis
|
|
145
|
+
laoreet commodo. Donec vel rutrum ipsum. Nunc sollicitudin lacinia eros quis
|
|
146
|
+
mollis. Sed interdum nulla massa, non mollis velit imperdiet et. Nam risus
|
|
147
|
+
magna, pulvinar vulputate tristique a, ornare blandit nunc. Nam massa odio,
|
|
148
|
+
venenatis ut accumsan vel, maximus lobortis purus. Vestibulum vitae ultrices
|
|
149
|
+
odio. Morbi vitae ante sed tellus molestie blandit. Morbi porttitor mi quis
|
|
150
|
+
laoreet commodo. Donec vel rutrum ipsum. Nunc sollicitudin lacinia eros quis
|
|
151
|
+
mollis.
|
|
152
|
+
</Text.BodyCopy>
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const meta: Meta<typeof Drawer> = {
|
|
156
|
+
title: "Components/Drawer",
|
|
157
|
+
component: Drawer,
|
|
158
|
+
args: {
|
|
159
|
+
direction: "right",
|
|
160
|
+
offset: 0,
|
|
161
|
+
isOpen: true,
|
|
162
|
+
width: 600,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
export default meta;
|
|
166
|
+
type Story = StoryObj<typeof Drawer>;
|
|
167
|
+
|
|
168
|
+
export const DrawerWithDefaults: Story = {
|
|
169
|
+
name: "Drawer With Defaults",
|
|
170
|
+
render: (args) => (
|
|
171
|
+
<StatefulDrawer
|
|
172
|
+
direction={args.direction}
|
|
173
|
+
offset={args.offset}
|
|
174
|
+
isOpen={args.isOpen}
|
|
175
|
+
width={args.width}
|
|
176
|
+
onClose={() => {}}
|
|
177
|
+
id="drawer"
|
|
178
|
+
closeButtonLabel="close drawer"
|
|
179
|
+
aria-labeledby="drawer-1-header"
|
|
180
|
+
aria-describedby="drawer-1-content"
|
|
181
|
+
>
|
|
182
|
+
<Drawer.Header title="Drawer" id="drawer-1-header" />
|
|
183
|
+
<Drawer.Content id="drawer-1-content">
|
|
184
|
+
<BigDrawerContent />
|
|
185
|
+
</Drawer.Content>
|
|
186
|
+
</StatefulDrawer>
|
|
187
|
+
),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const DrawerWithHeaderChildren: Story = {
|
|
191
|
+
name: "Drawer With Header Children",
|
|
192
|
+
render: (args) => (
|
|
193
|
+
<StatefulDrawer
|
|
194
|
+
direction={args.direction}
|
|
195
|
+
offset={args.offset}
|
|
196
|
+
isOpen={args.isOpen}
|
|
197
|
+
width={args.width}
|
|
198
|
+
onClose={() => {}}
|
|
199
|
+
id="drawer"
|
|
200
|
+
closeButtonLabel="close drawer"
|
|
201
|
+
>
|
|
202
|
+
<Drawer.Header>
|
|
203
|
+
<Box flexGrow={1}>
|
|
204
|
+
{/* <Tabs
|
|
205
|
+
fullWidth
|
|
206
|
+
selectedId="1"
|
|
207
|
+
onSelect={() => {
|
|
208
|
+
// eslint-disable-next-line no-console
|
|
209
|
+
console.log("does nothing");
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
<Tabs.Button id="1">
|
|
213
|
+
<Box display="flex" justifyContent="center" alignItems="center">
|
|
214
|
+
<Icon name="bell-outline" mr={350} aria-hidden />
|
|
215
|
+
<Text as="div" fontSize={200} fontWeight="semibold">
|
|
216
|
+
Notifications
|
|
217
|
+
</Text>
|
|
218
|
+
</Box>
|
|
219
|
+
</Tabs.Button>
|
|
220
|
+
<Tabs.Button id="2">
|
|
221
|
+
<Box display="flex" justifyContent="center" alignItems="center">
|
|
222
|
+
<Icon name="triangle-exclamation-outline" mr={350} aria-hidden />
|
|
223
|
+
<Text as="div" fontSize={200} fontWeight="semibold">
|
|
224
|
+
Issues
|
|
225
|
+
</Text>
|
|
226
|
+
</Box>
|
|
227
|
+
</Tabs.Button>
|
|
228
|
+
</Tabs> */}
|
|
229
|
+
</Box>
|
|
230
|
+
<Box display="flex">
|
|
231
|
+
<Button
|
|
232
|
+
onClick={() => {
|
|
233
|
+
// eslint-disable-next-line no-console
|
|
234
|
+
console.log("does nothing");
|
|
235
|
+
}}
|
|
236
|
+
data-qa-button="Filter"
|
|
237
|
+
mr={300}
|
|
238
|
+
aria-label="Filter"
|
|
239
|
+
>
|
|
240
|
+
<Icon name="filter-outline" aria-hidden />
|
|
241
|
+
</Button>
|
|
242
|
+
<Button
|
|
243
|
+
mr={300}
|
|
244
|
+
onClick={() => {
|
|
245
|
+
// eslint-disable-next-line no-console
|
|
246
|
+
console.log("does nothing");
|
|
247
|
+
}}
|
|
248
|
+
aria-label="Settings"
|
|
249
|
+
>
|
|
250
|
+
<Icon name="gear-outline" aria-hidden />
|
|
251
|
+
</Button>
|
|
252
|
+
<Drawer.CloseButton />
|
|
253
|
+
</Box>
|
|
254
|
+
</Drawer.Header>
|
|
255
|
+
<Drawer.Content>
|
|
256
|
+
<BigDrawerContent />
|
|
257
|
+
</Drawer.Content>
|
|
258
|
+
</StatefulDrawer>
|
|
259
|
+
),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export const DrawerWithHeaderRender: Story = {
|
|
263
|
+
name: "Drawer With Header Render",
|
|
264
|
+
render: (args) => (
|
|
265
|
+
<StatefulDrawer
|
|
266
|
+
direction={args.direction}
|
|
267
|
+
offset={args.offset}
|
|
268
|
+
isOpen={args.isOpen}
|
|
269
|
+
width={args.width}
|
|
270
|
+
onClose={() => {}}
|
|
271
|
+
id="drawer"
|
|
272
|
+
closeButtonLabel="close drawer"
|
|
273
|
+
>
|
|
274
|
+
<Drawer.Header
|
|
275
|
+
render={({ onClose }) => (
|
|
276
|
+
<Box
|
|
277
|
+
display="flex"
|
|
278
|
+
justifyContent="space-between"
|
|
279
|
+
p={500}
|
|
280
|
+
border="2px solid purple"
|
|
281
|
+
>
|
|
282
|
+
<Text color="text.body">Type "close" to close drawer</Text>
|
|
283
|
+
<input
|
|
284
|
+
onChange={(event) =>
|
|
285
|
+
event.target.value === "close" && (onClose ? onClose() : null)
|
|
286
|
+
}
|
|
287
|
+
/>
|
|
288
|
+
</Box>
|
|
289
|
+
)}
|
|
290
|
+
/>
|
|
291
|
+
<Drawer.Content>
|
|
292
|
+
<BigDrawerContent />
|
|
293
|
+
</Drawer.Content>
|
|
294
|
+
</StatefulDrawer>
|
|
295
|
+
),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const DrawerWithCloseButtonChildren: Story = {
|
|
299
|
+
name: "Drawer With Close Button Children",
|
|
300
|
+
render: (args) => (
|
|
301
|
+
<StatefulDrawer
|
|
302
|
+
direction={args.direction}
|
|
303
|
+
offset={args.offset}
|
|
304
|
+
isOpen={args.isOpen}
|
|
305
|
+
width={args.width}
|
|
306
|
+
onClose={() => {}}
|
|
307
|
+
id="drawer"
|
|
308
|
+
closeButtonLabel="close drawer"
|
|
309
|
+
aria-labeledby="drawer-with-close-button-header"
|
|
310
|
+
aria-describedby="drawer-with-close-button-content"
|
|
311
|
+
>
|
|
312
|
+
<Drawer.Header alignItems="center">
|
|
313
|
+
<Text.Headline as="h2" id="drawer-with-close-button-header">
|
|
314
|
+
Hello
|
|
315
|
+
</Text.Headline>
|
|
316
|
+
<Drawer.CloseButton appearance="primary">Close Me</Drawer.CloseButton>
|
|
317
|
+
</Drawer.Header>
|
|
318
|
+
<Drawer.Content id="drawer-with-close-button-content">
|
|
319
|
+
<BigDrawerContent />
|
|
320
|
+
</Drawer.Content>
|
|
321
|
+
</StatefulDrawer>
|
|
322
|
+
),
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export const DrawerWithCloseButtonRender: Story = {
|
|
326
|
+
name: "Drawer With Close Button Render",
|
|
327
|
+
render: (args) => (
|
|
328
|
+
<StatefulDrawer
|
|
329
|
+
direction={args.direction}
|
|
330
|
+
offset={args.offset}
|
|
331
|
+
isOpen={args.isOpen}
|
|
332
|
+
width={args.width}
|
|
333
|
+
onClose={() => {}}
|
|
334
|
+
id="drawer"
|
|
335
|
+
closeButtonLabel="close drawer"
|
|
336
|
+
>
|
|
337
|
+
<Drawer.Header alignItems="center">
|
|
338
|
+
<Text.BodyCopy>
|
|
339
|
+
This Drawer Will Close three seconds after clicking the "X"
|
|
340
|
+
</Text.BodyCopy>
|
|
341
|
+
<Drawer.CloseButton
|
|
342
|
+
render={({ onClose, closeButtonLabel }) => {
|
|
343
|
+
const newOnClose = () => {
|
|
344
|
+
setTimeout(() => {
|
|
345
|
+
// eslint-disable-next-line no-console
|
|
346
|
+
console.log("close");
|
|
347
|
+
onClose && onClose();
|
|
348
|
+
}, 3000);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<Button
|
|
353
|
+
appearance="pill"
|
|
354
|
+
onClick={newOnClose}
|
|
355
|
+
ariaLabel={closeButtonLabel}
|
|
356
|
+
>
|
|
357
|
+
<Icon name="x-outline" aria-hidden />
|
|
358
|
+
</Button>
|
|
359
|
+
);
|
|
360
|
+
}}
|
|
361
|
+
/>
|
|
362
|
+
</Drawer.Header>
|
|
363
|
+
<Drawer.Content>
|
|
364
|
+
<BigDrawerContent />
|
|
365
|
+
</Drawer.Content>
|
|
366
|
+
</StatefulDrawer>
|
|
367
|
+
),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export const fromLeft: Story = {
|
|
371
|
+
name: "From left",
|
|
372
|
+
args: {
|
|
373
|
+
direction: "left",
|
|
374
|
+
offset: 0,
|
|
375
|
+
},
|
|
376
|
+
// @ts-ignore We'll come back to this later
|
|
377
|
+
render: (args) => <DrawerComponent {...args} />,
|
|
378
|
+
};
|
package/src/Drawer.tsx
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useContext, useEffect, useRef } from "react";
|
|
3
|
+
import FocusLock from "react-focus-lock";
|
|
4
|
+
import { animated, useTransition } from "@react-spring/web";
|
|
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 useSlideTransition = ({
|
|
24
|
+
isVisible,
|
|
25
|
+
width,
|
|
26
|
+
direction,
|
|
27
|
+
}: {
|
|
28
|
+
isVisible: boolean;
|
|
29
|
+
width: number;
|
|
30
|
+
direction: "left" | "right";
|
|
31
|
+
}) => {
|
|
32
|
+
const offset = width * (direction === "left" ? -1 : 1);
|
|
33
|
+
|
|
34
|
+
return useTransition(isVisible, {
|
|
35
|
+
from: {
|
|
36
|
+
opacity: 0,
|
|
37
|
+
x: offset,
|
|
38
|
+
},
|
|
39
|
+
enter: {
|
|
40
|
+
opacity: 1,
|
|
41
|
+
x: 0,
|
|
42
|
+
},
|
|
43
|
+
leave: {
|
|
44
|
+
opacity: 0,
|
|
45
|
+
x: offset,
|
|
46
|
+
},
|
|
47
|
+
config: {
|
|
48
|
+
duration: MOTION_DURATION_MEDIUM * 1000,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const AnimatedDrawer = animated(Container);
|
|
54
|
+
|
|
55
|
+
const doesRefContainEventTarget = (
|
|
56
|
+
ref: { current: { contains: (arg0: any) => any } },
|
|
57
|
+
event: Event
|
|
58
|
+
) => {
|
|
59
|
+
return (
|
|
60
|
+
ref.current &&
|
|
61
|
+
event.target instanceof Node &&
|
|
62
|
+
ref.current.contains(event.target)
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const DrawerContext = React.createContext<TypeDrawerContext>({});
|
|
67
|
+
|
|
68
|
+
const DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {
|
|
69
|
+
const { onClose, closeButtonLabel } = useContext(DrawerContext);
|
|
70
|
+
|
|
71
|
+
if (props.render) {
|
|
72
|
+
return (
|
|
73
|
+
props.render({
|
|
74
|
+
onClose,
|
|
75
|
+
closeButtonLabel,
|
|
76
|
+
}) ?? null
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Button
|
|
82
|
+
appearance="pill"
|
|
83
|
+
aria-label={closeButtonLabel}
|
|
84
|
+
onClick={onClose}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
{props.children || <Icon aria-hidden name="x-outline" />}
|
|
88
|
+
</Button>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const DrawerHeader = ({
|
|
93
|
+
title = "",
|
|
94
|
+
id = undefined,
|
|
95
|
+
children,
|
|
96
|
+
render,
|
|
97
|
+
...rest
|
|
98
|
+
}: TypeDrawerHeaderProps) => {
|
|
99
|
+
const drawerContext = useContext(DrawerContext);
|
|
100
|
+
|
|
101
|
+
if (render) {
|
|
102
|
+
return render(drawerContext);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Box
|
|
107
|
+
display="flex"
|
|
108
|
+
flex="0 0 auto"
|
|
109
|
+
justifyContent="space-between"
|
|
110
|
+
alignItems="center"
|
|
111
|
+
pt={400}
|
|
112
|
+
px={450}
|
|
113
|
+
{...rest}
|
|
114
|
+
>
|
|
115
|
+
{children || (
|
|
116
|
+
<React.Fragment>
|
|
117
|
+
<Text
|
|
118
|
+
as="h2"
|
|
119
|
+
fontSize={400}
|
|
120
|
+
fontWeight="semibold"
|
|
121
|
+
color="text.headline"
|
|
122
|
+
id={id}
|
|
123
|
+
>
|
|
124
|
+
{title}
|
|
125
|
+
</Text>
|
|
126
|
+
<DrawerCloseButton />
|
|
127
|
+
</React.Fragment>
|
|
128
|
+
)}
|
|
129
|
+
</Box>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const DrawerContent = ({ children, ...rest }: TypeDrawerContentProps) => (
|
|
134
|
+
<Content height="100%" p={450} color="text.body" {...rest}>
|
|
135
|
+
{children}
|
|
136
|
+
</Content>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const useCloseOnBodyClick = ({
|
|
140
|
+
ref,
|
|
141
|
+
disableCloseOnClickOutside,
|
|
142
|
+
onClose,
|
|
143
|
+
closeTargets,
|
|
144
|
+
}: TypeUseCloseOnBodyClickProps) => {
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const documentBody = document.body;
|
|
147
|
+
|
|
148
|
+
if (!documentBody) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const onEsc = (event: KeyboardEvent): void => {
|
|
153
|
+
if (event.key === "Escape") {
|
|
154
|
+
onClose();
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const bodyClick = (event: Event): void => {
|
|
159
|
+
if (
|
|
160
|
+
// @ts-ignore I'm not sure how to type this ref properly
|
|
161
|
+
!doesRefContainEventTarget(ref, event) &&
|
|
162
|
+
!disableCloseOnClickOutside
|
|
163
|
+
) {
|
|
164
|
+
onClose();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
documentBody?.addEventListener("keydown", onEsc, { capture: true });
|
|
169
|
+
|
|
170
|
+
if (closeTargets) {
|
|
171
|
+
closeTargets.forEach((targetElement) =>
|
|
172
|
+
targetElement?.addEventListener("click", bodyClick, { capture: true })
|
|
173
|
+
);
|
|
174
|
+
} else {
|
|
175
|
+
documentBody.firstElementChild?.addEventListener("click", bodyClick, {
|
|
176
|
+
capture: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return () => {
|
|
181
|
+
documentBody?.removeEventListener("keydown", onEsc, { capture: true });
|
|
182
|
+
|
|
183
|
+
if (closeTargets) {
|
|
184
|
+
closeTargets.forEach((targetElement) =>
|
|
185
|
+
targetElement?.removeEventListener("click", bodyClick, {
|
|
186
|
+
capture: true,
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
documentBody.firstElementChild?.removeEventListener(
|
|
191
|
+
"click",
|
|
192
|
+
bodyClick,
|
|
193
|
+
{ capture: true }
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}, [onClose, disableCloseOnClickOutside, closeTargets, ref]);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const Drawer = ({
|
|
201
|
+
id,
|
|
202
|
+
offset,
|
|
203
|
+
direction,
|
|
204
|
+
children,
|
|
205
|
+
disableCloseOnClickOutside,
|
|
206
|
+
onClose,
|
|
207
|
+
zIndex,
|
|
208
|
+
closeTargets,
|
|
209
|
+
width,
|
|
210
|
+
focusLockExemptCheck,
|
|
211
|
+
isOpen,
|
|
212
|
+
...rest
|
|
213
|
+
}: TypeInnerDrawerProps) => {
|
|
214
|
+
const ref = useRef(null);
|
|
215
|
+
useCloseOnBodyClick({
|
|
216
|
+
ref,
|
|
217
|
+
disableCloseOnClickOutside,
|
|
218
|
+
onClose,
|
|
219
|
+
closeTargets,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const transition = useSlideTransition({
|
|
223
|
+
isVisible: isOpen,
|
|
224
|
+
width,
|
|
225
|
+
direction,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<FocusLock
|
|
230
|
+
key={id}
|
|
231
|
+
autoFocus={true}
|
|
232
|
+
returnFocus
|
|
233
|
+
whiteList={
|
|
234
|
+
focusLockExemptCheck ? (e) => !focusLockExemptCheck(e) : undefined
|
|
235
|
+
}
|
|
236
|
+
>
|
|
237
|
+
{transition((style, isVisible) =>
|
|
238
|
+
isVisible ? (
|
|
239
|
+
<AnimatedDrawer
|
|
240
|
+
ref={ref}
|
|
241
|
+
style={{ ...style, zIndex }}
|
|
242
|
+
width={width}
|
|
243
|
+
offset={offset}
|
|
244
|
+
direction={direction}
|
|
245
|
+
data-qa-drawer={id}
|
|
246
|
+
role="dialog"
|
|
247
|
+
{...rest}
|
|
248
|
+
>
|
|
249
|
+
{children}
|
|
250
|
+
</AnimatedDrawer>
|
|
251
|
+
) : null
|
|
252
|
+
)}
|
|
253
|
+
</FocusLock>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const DrawerContainer = ({
|
|
258
|
+
children,
|
|
259
|
+
closeButtonLabel,
|
|
260
|
+
direction = "right",
|
|
261
|
+
disableCloseOnClickOutside = false,
|
|
262
|
+
id,
|
|
263
|
+
isOpen,
|
|
264
|
+
offset = 0,
|
|
265
|
+
onClose,
|
|
266
|
+
zIndex = 7,
|
|
267
|
+
closeTargets = [],
|
|
268
|
+
width = 600,
|
|
269
|
+
...rest
|
|
270
|
+
}: TypeDrawerProps) => {
|
|
271
|
+
return (
|
|
272
|
+
<Portal id={id}>
|
|
273
|
+
<DrawerContext.Provider
|
|
274
|
+
value={{
|
|
275
|
+
onClose,
|
|
276
|
+
closeButtonLabel,
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
<Drawer
|
|
280
|
+
isOpen={isOpen}
|
|
281
|
+
id={id}
|
|
282
|
+
offset={offset}
|
|
283
|
+
direction={direction}
|
|
284
|
+
disableCloseOnClickOutside={disableCloseOnClickOutside}
|
|
285
|
+
onClose={onClose}
|
|
286
|
+
zIndex={zIndex}
|
|
287
|
+
closeTargets={closeTargets}
|
|
288
|
+
width={width}
|
|
289
|
+
data-qa-drawer={id || ""}
|
|
290
|
+
data-qa-drawer-isopen={isOpen}
|
|
291
|
+
{...rest}
|
|
292
|
+
>
|
|
293
|
+
{children}
|
|
294
|
+
</Drawer>
|
|
295
|
+
</DrawerContext.Provider>
|
|
296
|
+
</Portal>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
DrawerHeader.displayName = "Drawer.Header";
|
|
301
|
+
DrawerContent.displayName = "Drawer.Content";
|
|
302
|
+
DrawerCloseButton.displayName = "Drawer.CloseButton";
|
|
303
|
+
|
|
304
|
+
DrawerContainer.Header = DrawerHeader;
|
|
305
|
+
DrawerContainer.Content = DrawerContent;
|
|
306
|
+
DrawerContainer.CloseButton = DrawerCloseButton;
|
|
307
|
+
|
|
308
|
+
export default DrawerContainer;
|