@hyphen/hyphen-components 2.25.2 → 3.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.
@@ -1,6 +1,15 @@
1
- import { Drawer, DrawerPlacementType } from './Drawer';
1
+ import {
2
+ Drawer,
3
+ DrawerCloseButton,
4
+ DrawerContent,
5
+ DrawerHeader,
6
+ DrawerPlacementType,
7
+ DrawerProvider,
8
+ DrawerTitle,
9
+ DrawerTrigger,
10
+ } from './Drawer';
2
11
  import type { Meta, StoryObj } from '@storybook/react';
3
- import React, { MutableRefObject, useEffect, useRef, useState } from 'react';
12
+ import React, { useRef, useState } from 'react';
4
13
  import { useOpenClose } from '../../hooks';
5
14
  import { Button } from '../Button/Button';
6
15
  import { Box } from '../Box/Box';
@@ -16,7 +25,50 @@ const meta: Meta<typeof Drawer> = {
16
25
  export default meta;
17
26
  type Story = StoryObj<typeof Drawer>;
18
27
 
19
- export const BasicUsage = () => {
28
+ const drawerContent: JSX.Element[] = [];
29
+ for (let i = 0; i < 50; i++) {
30
+ drawerContent.push(<Box key={i}>Drawer content&hellip;</Box>);
31
+ }
32
+
33
+ export const UncontrolledWithProvider = () => {
34
+ const ref = useRef(null);
35
+
36
+ return (
37
+ <div id="drawerContainer" ref={ref} style={{ height: '240px' }}>
38
+ <DrawerProvider defaultIsOpen={false}>
39
+ <DrawerTrigger asChild>
40
+ <Button variant="primary">Toggle Uncontrolled Drawer</Button>
41
+ </DrawerTrigger>
42
+ <Drawer ariaLabel="drawer component example" containerRef={ref}>
43
+ <DrawerHeader>
44
+ <DrawerTitle>Drawer Title</DrawerTitle>
45
+ <DrawerCloseButton />
46
+ </DrawerHeader>
47
+ <DrawerContent>{drawerContent}</DrawerContent>
48
+ </Drawer>
49
+ </DrawerProvider>
50
+ </div>
51
+ );
52
+ };
53
+
54
+ export const OpenUncontrolledWithProvider: Story = {
55
+ play: async ({ canvasElement, mount }) => {
56
+ await mount(<UncontrolledWithProvider />);
57
+ const canvas = within(canvasElement);
58
+
59
+ await userEvent.click(canvas.getByText('Toggle Uncontrolled Drawer'));
60
+
61
+ await expect(canvas.getByText('Drawer Title')).toBeInTheDocument();
62
+
63
+ await userEvent.click(canvas.getByLabelText('close'));
64
+
65
+ await expect(canvas.queryByText('Drawer Title')).toBeNull();
66
+
67
+ await userEvent.click(canvas.getByText('Toggle Uncontrolled Drawer'));
68
+ },
69
+ };
70
+
71
+ export const ControlledWithoutProvider = () => {
20
72
  const {
21
73
  isOpen: isDrawerOpen,
22
74
  handleOpen: openDrawer,
@@ -27,46 +79,89 @@ export const BasicUsage = () => {
27
79
 
28
80
  return (
29
81
  <div id="drawerContainer" ref={ref} style={{ height: '240px' }}>
30
- <Button variant="primary" onClick={openDrawer}>
31
- Open Drawer
32
- </Button>
82
+ <DrawerTrigger asChild onClick={openDrawer}>
83
+ <Button variant="primary">Open Drawer</Button>
84
+ </DrawerTrigger>
33
85
  <Drawer
34
86
  isOpen={isDrawerOpen}
35
- title="Drawer Title"
36
- onDismiss={closeDrawer}
37
87
  ariaLabel="drawer component example"
38
88
  containerRef={ref}
89
+ onDismiss={closeDrawer}
39
90
  >
40
- <Box
41
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
42
- display="block"
43
- childGap="md"
44
- >
45
- <Box>Drawer content</Box>
46
- </Box>
91
+ <DrawerHeader>
92
+ <DrawerTitle>Drawer Title</DrawerTitle>
93
+ <DrawerCloseButton onClose={closeDrawer} />
94
+ </DrawerHeader>
95
+ <DrawerContent>{drawerContent}</DrawerContent>
47
96
  </Drawer>
48
97
  </div>
49
98
  );
50
99
  };
51
100
 
52
- export const OpenDrawer: Story = {
101
+ ControlledWithoutProvider.parameters = {
102
+ chromatic: { disableSnapshot: true },
103
+ };
104
+
105
+ export const OpenControlledControlledWithoutProvider: Story = {
53
106
  play: async ({ canvasElement, mount }) => {
54
- await mount(<BasicUsage />);
107
+ await mount(<ControlledWithoutProvider />);
55
108
  const canvas = within(canvasElement);
56
109
 
57
110
  await userEvent.click(canvas.getByText('Open Drawer'));
58
111
 
59
- await expect(canvas.getByText('Drawer content')).toBeInTheDocument();
112
+ await expect(canvas.getByText('Drawer Title')).toBeInTheDocument();
113
+
114
+ await userEvent.click(canvas.getByLabelText('close'));
115
+
116
+ await expect(canvas.queryByText('Drawer Title')).toBeNull();
117
+
118
+ await userEvent.click(canvas.getByText('Open Drawer'));
119
+
120
+ await userEvent.click(
121
+ document.getElementsByClassName('ReactModal__Overlay')[0]
122
+ );
123
+
124
+ await expect(canvas.queryByText('Drawer Title')).toBeNull();
125
+
126
+ await userEvent.click(canvas.getByText('Open Drawer'));
60
127
  },
61
128
  };
62
129
 
63
- export const Placement = () => {
130
+ export const DefaultIsOpen = () => {
64
131
  const {
65
132
  isOpen: isDrawerOpen,
66
133
  handleOpen: openDrawer,
67
134
  handleClose: closeDrawer,
68
135
  } = useOpenClose();
69
- const [placement, setPlacement] = useState('right');
136
+
137
+ const ref = useRef(null);
138
+
139
+ return (
140
+ <div id="drawerContainer" ref={ref} style={{ height: '240px' }}>
141
+ <DrawerProvider defaultIsOpen>
142
+ <DrawerTrigger asChild onClick={openDrawer}>
143
+ <Button variant="primary">Open Drawer</Button>
144
+ </DrawerTrigger>
145
+ <Drawer
146
+ isOpen={isDrawerOpen}
147
+ ariaLabel="drawer component example"
148
+ containerRef={ref}
149
+ onDismiss={closeDrawer}
150
+ >
151
+ <DrawerHeader>
152
+ <DrawerTitle>Drawer Title</DrawerTitle>
153
+ <DrawerCloseButton onClose={closeDrawer} />
154
+ </DrawerHeader>
155
+ <DrawerContent>{drawerContent}</DrawerContent>
156
+ </Drawer>
157
+ </DrawerProvider>
158
+ </div>
159
+ );
160
+ };
161
+
162
+ export const Placement = () => {
163
+ const ref = useRef<HTMLDivElement>(null);
164
+ const [placement, setPlacement] = useState('bottom');
70
165
  const placementOptions = [
71
166
  {
72
167
  id: 'top',
@@ -90,7 +185,11 @@ export const Placement = () => {
90
185
  },
91
186
  ];
92
187
  return (
93
- <Box display="block" childGap="md">
188
+ <div
189
+ id="placementStory"
190
+ className="display-flex flex-direction-column align-items-flex-start g-lg"
191
+ ref={ref}
192
+ >
94
193
  <RadioGroup
95
194
  title="Placement"
96
195
  direction="row"
@@ -99,301 +198,150 @@ export const Placement = () => {
99
198
  value={placement}
100
199
  options={placementOptions}
101
200
  />
102
- <Button variant="primary" onClick={openDrawer}>
103
- Open Drawer
104
- </Button>
105
- <Drawer
106
- title="test"
107
- isOpen={isDrawerOpen}
108
- onDismiss={closeDrawer}
109
- placement={placement as DrawerPlacementType}
110
- ariaLabel="drawer component example"
111
- >
112
- <Box
113
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
114
- display="block"
115
- childGap="md"
201
+ <DrawerProvider>
202
+ <DrawerTrigger asChild>
203
+ <Button variant="primary">Open Drawer</Button>
204
+ </DrawerTrigger>
205
+ <Drawer
206
+ placement={placement as DrawerPlacementType}
207
+ ariaLabel="drawer component example"
116
208
  >
117
- <Box as="p">drawer content</Box>
118
- <Box as="p">drawer content</Box>
119
- <Box as="p">drawer content</Box>
120
- <Box as="p">drawer content</Box>
121
- <Box as="p">drawer content</Box>
122
- <Box as="p">drawer content</Box>
123
- <Box as="p">drawer content</Box>
124
- <Box as="p">drawer content</Box>
125
- <Box as="p">drawer content</Box>
126
- </Box>
127
- </Drawer>
128
- </Box>
209
+ <DrawerHeader>
210
+ <DrawerTitle>{placement} placement</DrawerTitle>
211
+ <DrawerCloseButton />
212
+ </DrawerHeader>
213
+ <DrawerContent height="8xl">{drawerContent}</DrawerContent>
214
+ </Drawer>
215
+ </DrawerProvider>
216
+ </div>
129
217
  );
130
218
  };
131
219
 
132
- export const DrawerHeader = () => {
133
- const {
134
- isOpen: isDrawerOpen,
135
- handleOpen: openDrawer,
136
- handleClose: closeDrawer,
137
- } = useOpenClose();
138
- const drawerContent = [];
139
- for (let i = 0; i < 50; i++) {
140
- drawerContent.push(<Box key={i}>Drawer content&hellip;</Box>);
141
- }
142
- return (
143
- <>
144
- <Button variant="primary" onClick={openDrawer}>
145
- Title and Close Button
146
- </Button>
147
- <Drawer
148
- ariaLabel="drawer component example"
149
- isOpen={isDrawerOpen}
150
- onDismiss={closeDrawer}
151
- title="Drawer Title"
152
- >
153
- <Box
154
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
155
- display="block"
156
- childGap="md"
157
- >
158
- {drawerContent}
159
- </Box>
160
- </Drawer>
161
- </>
162
- );
220
+ Placement.parameters = {
221
+ chromatic: { disableSnapshot: true },
163
222
  };
164
223
 
165
- export const TitleAndCloseButton = () => {
166
- const {
167
- isOpen: isDrawerOpen,
168
- handleOpen: openDrawer,
169
- handleClose: closeDrawer,
170
- } = useOpenClose();
171
- const drawerContent = [];
172
- for (let i = 0; i < 50; i++) {
173
- drawerContent.push(<Box key={i}>Drawer content&hellip;</Box>);
174
- }
175
- return (
176
- <>
177
- <Button variant="primary" onClick={openDrawer}>
178
- Title and Close Button
179
- </Button>
180
- <Drawer
181
- ariaLabel="drawer component example"
182
- isOpen={isDrawerOpen}
183
- onDismiss={closeDrawer}
184
- title="Drawer Title"
185
- >
186
- <Box
187
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
188
- display="block"
189
- childGap="md"
190
- >
191
- {drawerContent}
192
- </Box>
193
- </Drawer>
194
- </>
195
- );
196
- };
224
+ export const OpenBottomDrawer: Story = {
225
+ play: async ({ canvasElement, mount }) => {
226
+ await mount(<Placement />);
227
+ const canvas = within(canvasElement);
197
228
 
198
- export const CloseButtonOnly = () => {
199
- const {
200
- isOpen: isDrawerOpen,
201
- handleOpen: openDrawer,
202
- handleClose: closeDrawer,
203
- } = useOpenClose();
204
- const drawerContent = [];
205
- for (let i = 0; i < 50; i++) {
206
- drawerContent.push(<Box key={i}>Drawer content&hellip;</Box>);
207
- }
208
- return (
209
- <>
210
- <Button variant="primary" onClick={openDrawer}>
211
- Close Button Only
212
- </Button>
213
- <Drawer
214
- ariaLabel="drawer component example"
215
- isOpen={isDrawerOpen}
216
- onDismiss={closeDrawer}
217
- closeButton
218
- >
219
- <Box
220
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
221
- display="block"
222
- childGap="md"
223
- >
224
- {drawerContent}
225
- </Box>
226
- </Drawer>
227
- </>
228
- );
229
+ await userEvent.click(canvas.getByText('Open Drawer'));
230
+ },
229
231
  };
230
232
 
231
233
  export const Width = () => {
232
- const {
233
- isOpen: isDrawerOpen,
234
- handleOpen: openDrawer,
235
- handleClose: closeDrawer,
236
- } = useOpenClose();
237
234
  const [width, setWidth] = React.useState('default');
238
235
  const handleClick = (newWidth: WidthSize) => {
239
236
  setWidth(newWidth);
240
- openDrawer();
241
237
  };
242
238
  const widths = ['16rem', '400px', '100%'];
243
239
  return (
244
- <>
245
- <Box gap="sm" direction="row">
240
+ <DrawerProvider defaultIsOpen={false}>
241
+ <Box gap="md" direction="row">
246
242
  {widths.map((width: string) => (
247
- <Button
248
- variant="primary"
249
- onClick={() => handleClick(width as WidthSize)}
250
- key={width}
251
- >
252
- {`Open ${width} Drawer `}
253
- </Button>
243
+ <DrawerTrigger asChild>
244
+ <Button
245
+ variant="primary"
246
+ onClick={() => handleClick(width as WidthSize)}
247
+ key={width}
248
+ >
249
+ {`Open ${width} Drawer `}
250
+ </Button>
251
+ </DrawerTrigger>
254
252
  ))}
255
253
  </Box>
256
- <Drawer
257
- width={width as WidthSize}
258
- title={`${width} wide drawer`}
259
- isOpen={isDrawerOpen}
260
- onDismiss={closeDrawer}
261
- closeButton
262
- ariaLabel="drawer component example"
263
- >
264
- <Box
265
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
266
- display="block"
267
- childGap="md"
268
- >
269
- <Box>drawer content</Box>
270
- </Box>
254
+ <Drawer width={width as WidthSize} ariaLabel="drawer component example">
255
+ <DrawerHeader>
256
+ <DrawerTitle>Drawer Title</DrawerTitle>
257
+ <DrawerCloseButton />
258
+ </DrawerHeader>
259
+ <DrawerContent>{drawerContent}</DrawerContent>
271
260
  </Drawer>
272
- </>
261
+ </DrawerProvider>
273
262
  );
274
263
  };
275
264
 
276
- export const Height = () => {
277
- const {
278
- isOpen: isDrawerOpen,
279
- handleOpen: openDrawer,
280
- handleClose: closeDrawer,
281
- } = useOpenClose();
282
- return (
283
- <>
284
- <Button variant="primary" onClick={openDrawer}>
285
- Open Drawer
286
- </Button>
287
- <Drawer
288
- placement="top"
289
- isOpen={isDrawerOpen}
290
- onDismiss={closeDrawer}
291
- closeButton
292
- ariaLabel="drawer component example"
293
- >
294
- <Box padding="lg" height="4xl" display="block" childGap="md">
295
- <Box>4xl Height</Box>
296
- </Box>
297
- </Drawer>
298
- </>
299
- );
265
+ Width.parameters = {
266
+ chromatic: { disableSnapshot: true },
300
267
  };
301
268
 
302
269
  export const HiddenOverlay = () => {
303
- const closeBtnRef = useRef<HTMLButtonElement>();
304
- const returnFocusRef = useRef<HTMLButtonElement>();
305
- const returnFocus = () => {
306
- if (returnFocusRef && returnFocusRef.current) {
307
- returnFocusRef.current.focus();
308
- }
309
- };
310
- const {
311
- isOpen: isDrawerOpen,
312
- handleToggle: handleDrawerToggle,
313
- handleClose: closeDrawer,
314
- } = useOpenClose({ onClose: returnFocus });
315
- useEffect(() => {
316
- setTimeout(() => {
317
- if (closeBtnRef && closeBtnRef.current) {
318
- closeBtnRef.current.focus();
319
- }
320
- }, 100);
321
- }, [isDrawerOpen]);
322
270
  return (
323
- <>
324
- <Button
325
- variant="primary"
326
- onClick={handleDrawerToggle}
327
- ref={returnFocusRef as MutableRefObject<HTMLButtonElement>}
328
- >
329
- Toggle Drawer
330
- </Button>
331
- <Drawer
332
- isOpen={isDrawerOpen}
333
- onDismiss={closeDrawer}
334
- ariaLabel="drawer component example"
335
- hideOverlay
336
- >
337
- <Box
338
- padding={{ base: '2xl', tablet: '4xl' }}
339
- display="block"
340
- childGap="md"
341
- >
342
- <Button
343
- ref={closeBtnRef as MutableRefObject<HTMLButtonElement>}
344
- onClick={closeDrawer}
345
- variant="primary"
346
- >
347
- close
348
- </Button>
349
- <Box>drawer content</Box>
350
- </Box>
271
+ <DrawerProvider defaultIsOpen={false}>
272
+ <DrawerTrigger asChild>
273
+ <Button variant="primary">Toggle Drawer</Button>
274
+ </DrawerTrigger>
275
+ <Drawer hideOverlay ariaLabel="drawer component example">
276
+ <DrawerHeader>
277
+ <DrawerTitle>Drawer Title</DrawerTitle>
278
+ <DrawerCloseButton />
279
+ </DrawerHeader>
280
+ <DrawerContent>{drawerContent}</DrawerContent>
351
281
  </Drawer>
352
- </>
282
+ </DrawerProvider>
353
283
  );
354
284
  };
355
285
 
286
+ HiddenOverlay.parameters = {
287
+ chromatic: { disableSnapshot: true },
288
+ };
289
+
290
+ export const OpenHiddenOverlay: Story = {
291
+ play: async ({ canvasElement, mount }) => {
292
+ await mount(<HiddenOverlay />);
293
+ const canvas = within(canvasElement);
294
+
295
+ await userEvent.click(canvas.getByText('Toggle Drawer'));
296
+ },
297
+ };
298
+
356
299
  export const InitialFocusRef = () => {
357
- const {
358
- isOpen: isDrawerOpen,
359
- handleOpen: openDrawer,
360
- handleClose: closeDrawer,
361
- } = useOpenClose();
362
300
  const ref = useRef(null);
301
+ const containerRef = useRef(null);
363
302
  return (
364
- <>
365
- <Button variant="primary" onClick={openDrawer}>
366
- Open Drawer
367
- </Button>
368
- <Drawer
369
- isOpen={isDrawerOpen}
370
- onDismiss={closeDrawer}
371
- initialFocusRef={ref}
372
- title="initialFocusRef"
373
- ariaLabel="drawer component example"
374
- >
375
- <Box
376
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
377
- display="block"
378
- childGap="md"
303
+ <div ref={containerRef}>
304
+ <DrawerProvider defaultIsOpen={false}>
305
+ <DrawerTrigger asChild>
306
+ <Button variant="primary">Toggle Drawer</Button>
307
+ </DrawerTrigger>
308
+ <Drawer
309
+ initialFocusRef={ref}
310
+ containerRef={containerRef}
311
+ ariaLabel="drawer component example"
379
312
  >
380
- <Box>drawer content</Box>
381
- <Button variant="primary" ref={ref} onClick={closeDrawer}>
382
- I receive focus
383
- </Button>
384
- </Box>
385
- </Drawer>
386
- </>
313
+ <DrawerHeader>
314
+ <DrawerTitle>Drawer Title</DrawerTitle>
315
+ <DrawerCloseButton />
316
+ </DrawerHeader>
317
+ <DrawerContent>
318
+ <DrawerTrigger asChild>
319
+ <Button variant="primary" ref={ref} data-testid="focus-button">
320
+ I receive focus
321
+ </Button>
322
+ </DrawerTrigger>
323
+ </DrawerContent>
324
+ </Drawer>
325
+ </DrawerProvider>
326
+ </div>
387
327
  );
388
328
  };
389
329
 
330
+ InitialFocusRef.parameters = {
331
+ chromatic: { disableSnapshot: true },
332
+ };
333
+
334
+ export const OpenInitialFocusRef: Story = {
335
+ play: async ({ canvasElement, mount }) => {
336
+ await mount(<InitialFocusRef />);
337
+ const canvas = within(canvasElement);
338
+
339
+ await userEvent.click(canvas.getByText('Toggle Drawer'));
340
+ },
341
+ };
342
+
390
343
  export const ContainedDrawer = () => {
391
- const containerRef = useRef<HTMLDivElement>();
392
- const {
393
- isOpen: isDrawerOpen,
394
- handleOpen: openDrawer,
395
- handleClose: closeDrawer,
396
- } = useOpenClose();
344
+ const containerRef = useRef(null);
397
345
  return (
398
346
  <Box
399
347
  position="relative"
@@ -404,27 +352,38 @@ export const ContainedDrawer = () => {
404
352
  background="info"
405
353
  padding="lg"
406
354
  overflow="hidden"
355
+ borderWidth="sm"
407
356
  >
408
- <Button variant="primary" onClick={openDrawer}>
409
- Show Drawer
410
- </Button>
411
- <Drawer
412
- isOpen={isDrawerOpen}
413
- onDismiss={closeDrawer}
414
- containerRef={containerRef as MutableRefObject<HTMLDivElement>}
415
- dangerouslyBypassScrollLock
416
- hideOverlay
417
- title="containerRef"
418
- ariaLabel="drawer component example"
419
- >
420
- <Box
421
- padding={{ base: '0 2xl 2xl 2xl', tablet: '0 4xl 4xl 4xl' }}
422
- as="p"
357
+ <DrawerProvider defaultIsOpen={false}>
358
+ <DrawerTrigger asChild>
359
+ <Button variant="primary">Toggle Drawer</Button>
360
+ </DrawerTrigger>
361
+ <Drawer
362
+ containerRef={containerRef}
363
+ ariaLabel="drawer component example"
423
364
  >
424
- This drawer is rendered inside it&apos;s containing div, rather than
425
- the document.body
426
- </Box>
427
- </Drawer>
365
+ <DrawerHeader>
366
+ <DrawerTitle>Drawer Title</DrawerTitle>
367
+ <DrawerCloseButton />
368
+ </DrawerHeader>
369
+ <DrawerContent>{drawerContent}</DrawerContent>
370
+ </Drawer>
371
+ </DrawerProvider>
428
372
  </Box>
429
373
  );
430
374
  };
375
+
376
+ ContainedDrawer.parameters = {
377
+ chromatic: { disableSnapshot: true },
378
+ };
379
+
380
+ export const OpenContainedDrawer: Story = {
381
+ play: async ({ canvasElement, mount }) => {
382
+ await mount(<ContainedDrawer />);
383
+ const canvas = within(canvasElement);
384
+
385
+ await userEvent.click(canvas.getByText('Toggle Drawer'));
386
+
387
+ await expect(canvas.getByText('Drawer Title')).toBeInTheDocument();
388
+ },
389
+ };