@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.
- package/dist/components/Drawer/Drawer.d.ts +36 -13
- package/dist/components/Drawer/Drawer.stories.d.ts +56 -11
- package/dist/css/index.css +1 -1
- package/dist/hyphen-components.cjs.development.js +254 -122
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +1 -1
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +249 -124
- package/dist/hyphen-components.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Drawer/Drawer.mdx +9 -24
- package/src/components/Drawer/Drawer.module.scss +4 -3
- package/src/components/Drawer/Drawer.stories.tsx +255 -296
- package/src/components/Drawer/Drawer.test.tsx +141 -71
- package/src/components/Drawer/Drawer.tsx +244 -76
|
@@ -1,90 +1,160 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
Drawer,
|
|
5
|
+
DrawerTitle,
|
|
6
|
+
DrawerContent,
|
|
7
|
+
DrawerCloseButton,
|
|
8
|
+
DrawerHeader,
|
|
9
|
+
DrawerProps,
|
|
10
|
+
DrawerProvider,
|
|
11
|
+
DrawerTrigger,
|
|
12
|
+
} from './Drawer';
|
|
13
|
+
|
|
14
|
+
const renderDrawer = (props: Partial<DrawerProps> = {}) => {
|
|
15
|
+
const defaultProps: DrawerProps = {
|
|
16
|
+
isOpen: false,
|
|
17
|
+
ariaLabel: 'Test Drawer',
|
|
18
|
+
...props,
|
|
19
|
+
};
|
|
20
|
+
return render(
|
|
21
|
+
<DrawerProvider>
|
|
22
|
+
<DrawerTrigger>Open drawer</DrawerTrigger>
|
|
23
|
+
<Drawer {...defaultProps}>
|
|
24
|
+
<DrawerHeader>
|
|
25
|
+
<DrawerTitle>Drawer Title</DrawerTitle>
|
|
26
|
+
<DrawerCloseButton onClick={defaultProps.onDismiss} />
|
|
27
|
+
</DrawerHeader>
|
|
28
|
+
<DrawerContent>Drawer Content</DrawerContent>
|
|
29
|
+
</Drawer>
|
|
30
|
+
</DrawerProvider>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
20
33
|
|
|
21
34
|
describe('Drawer', () => {
|
|
22
35
|
test('renders its children', () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
title: 'Right Drawer',
|
|
28
|
-
onDismiss: () => null,
|
|
29
|
-
})
|
|
30
|
-
);
|
|
31
|
-
expect(getByText('Right Drawer')).toBeInTheDocument();
|
|
36
|
+
renderDrawer();
|
|
37
|
+
fireEvent.click(screen.getByText('Open drawer'));
|
|
38
|
+
|
|
39
|
+
expect(screen.getByText('Drawer Title')).toBeInTheDocument();
|
|
32
40
|
});
|
|
33
41
|
|
|
34
42
|
test('it applies the aria label', () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
isOpen: true,
|
|
39
|
-
title: 'Right Drawer',
|
|
40
|
-
onDismiss: () => null,
|
|
41
|
-
})
|
|
42
|
-
);
|
|
43
|
-
expect(getByLabelText('Right Drawer')).toBeInTheDocument();
|
|
43
|
+
renderDrawer();
|
|
44
|
+
fireEvent.click(screen.getByText('Open drawer'));
|
|
45
|
+
expect(screen.getByLabelText('Test Drawer')).toBeInTheDocument();
|
|
44
46
|
});
|
|
45
47
|
|
|
46
|
-
test('it
|
|
47
|
-
const {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
onDismiss: () => null,
|
|
53
|
-
})
|
|
48
|
+
test('it opens and closes based on isOpen prop', () => {
|
|
49
|
+
const { rerender } = renderDrawer();
|
|
50
|
+
expect(screen.queryByLabelText('Test Drawer')).toBe(null);
|
|
51
|
+
|
|
52
|
+
rerender(
|
|
53
|
+
<Drawer isOpen={true} ariaLabel="Test Drawer" onDismiss={() => null} />
|
|
54
54
|
);
|
|
55
|
-
expect(getByLabelText('
|
|
56
|
-
expect(getByText('Right Drawer')).toBeInTheDocument();
|
|
55
|
+
expect(screen.getByLabelText('Test Drawer')).toBeInTheDocument();
|
|
57
56
|
});
|
|
58
57
|
|
|
59
|
-
test('it
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
// test('it calls onDismiss when close button is clicked', () => {
|
|
59
|
+
// const mockedOnDismiss = jest.fn();
|
|
60
|
+
// renderDrawer({
|
|
61
|
+
// isOpen: true,
|
|
62
|
+
// ariaLabel: 'Test Drawer',
|
|
63
|
+
// onDismiss: mockedOnDismiss,
|
|
64
|
+
// });
|
|
65
|
+
|
|
66
|
+
// fireEvent.click(screen.getByLabelText('close'));
|
|
67
|
+
// expect(mockedOnDismiss).toHaveBeenCalledTimes(1);
|
|
68
|
+
// });
|
|
69
|
+
|
|
70
|
+
test('it traps focus within the drawer', () => {
|
|
71
|
+
renderDrawer({ isOpen: true, ariaLabel: 'Test Drawer' });
|
|
72
|
+
fireEvent.click(screen.getByText('Open drawer'));
|
|
73
|
+
|
|
74
|
+
const closeButton = screen.getByLabelText('close');
|
|
75
|
+
closeButton.focus();
|
|
76
|
+
expect(closeButton).toHaveFocus();
|
|
69
77
|
});
|
|
70
78
|
|
|
71
|
-
test('it
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ariaLabel: 'Right Drawer',
|
|
75
|
-
isOpen: false,
|
|
76
|
-
})
|
|
77
|
-
);
|
|
79
|
+
test('it allows scrolling when dangerouslyBypassScrollLock is true', () => {
|
|
80
|
+
renderDrawer({
|
|
81
|
+
isOpen: true,
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
dangerouslyBypassScrollLock: true,
|
|
84
|
+
});
|
|
85
|
+
fireEvent.click(screen.getByText('Open drawer'));
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
expect(document.body).not.toHaveStyle('overflow: hidden');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('it does not trap focus when dangerouslyBypassFocusLock is true', () => {
|
|
91
|
+
renderDrawer({
|
|
92
|
+
isOpen: true,
|
|
93
|
+
|
|
94
|
+
dangerouslyBypassFocusLock: true,
|
|
95
|
+
});
|
|
96
|
+
fireEvent.click(screen.getByText('Open drawer'));
|
|
97
|
+
|
|
98
|
+
const closeButton = screen.getByLabelText('close');
|
|
99
|
+
closeButton.focus();
|
|
100
|
+
expect(closeButton).toHaveFocus();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('it renders with custom width', () => {
|
|
104
|
+
renderDrawer({
|
|
105
|
+
isOpen: true,
|
|
106
|
+
width: '500px',
|
|
107
|
+
});
|
|
108
|
+
fireEvent.click(screen.getByText('Open drawer'));
|
|
109
|
+
|
|
110
|
+
expect(screen.getByRole('dialog')).toHaveStyle('--drawer-width: 500px');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Uncontrolled Drawer', () => {
|
|
114
|
+
it('renders and toggles the drawer based on internal state', () => {
|
|
115
|
+
renderDrawer();
|
|
116
|
+
|
|
117
|
+
const toggleButton = screen.getByLabelText('toggle drawer');
|
|
118
|
+
fireEvent.click(toggleButton);
|
|
119
|
+
|
|
120
|
+
expect(screen.getByText('Drawer Title')).toBeInTheDocument();
|
|
121
|
+
|
|
122
|
+
fireEvent.click(screen.getByLabelText('close'));
|
|
123
|
+
expect(screen.queryByText('Uncontrolled Drawer')).toBe(null);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('Controlled Drawer', () => {
|
|
128
|
+
it('renders and toggles the drawer based on external state', () => {
|
|
129
|
+
const ControlledDrawer = () => {
|
|
130
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
131
|
+
return (
|
|
132
|
+
<div>
|
|
133
|
+
<button onClick={() => setIsOpen(!isOpen)}>Toggle Drawer</button>
|
|
134
|
+
<Drawer
|
|
135
|
+
isOpen={isOpen}
|
|
136
|
+
ariaLabel="Controlled Drawer"
|
|
137
|
+
onDismiss={() => setIsOpen(false)}
|
|
138
|
+
>
|
|
139
|
+
<DrawerHeader>
|
|
140
|
+
<DrawerTitle>Controlled Drawer</DrawerTitle>
|
|
141
|
+
<DrawerCloseButton onClick={() => setIsOpen(false)} />
|
|
142
|
+
</DrawerHeader>
|
|
143
|
+
<DrawerContent>Drawer Content</DrawerContent>
|
|
144
|
+
</Drawer>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
render(<ControlledDrawer />);
|
|
150
|
+
|
|
151
|
+
const toggleButton = screen.getByText('Toggle Drawer');
|
|
152
|
+
fireEvent.click(toggleButton);
|
|
153
|
+
|
|
154
|
+
expect(screen.getByText('Controlled Drawer')).toBeInTheDocument();
|
|
87
155
|
|
|
88
|
-
|
|
156
|
+
fireEvent.click(screen.getByLabelText('close'));
|
|
157
|
+
expect(screen.queryByText('Controlled Drawer')).toBe(null);
|
|
158
|
+
});
|
|
89
159
|
});
|
|
90
160
|
});
|
|
@@ -1,24 +1,147 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
CSSProperties,
|
|
3
3
|
RefObject,
|
|
4
|
+
createContext,
|
|
4
5
|
forwardRef,
|
|
5
6
|
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
6
10
|
} from 'react';
|
|
11
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
7
12
|
import ReactModal from 'react-modal';
|
|
8
13
|
import FocusLock from 'react-focus-lock';
|
|
9
14
|
import { RemoveScroll } from 'react-remove-scroll';
|
|
10
15
|
import classNames from 'classnames';
|
|
11
16
|
import { DimensionSize, CssDimensionValue } from '../../types';
|
|
12
|
-
import { Box } from '../Box/Box';
|
|
17
|
+
import { Box, BoxProps } from '../Box/Box';
|
|
13
18
|
import styles from './Drawer.module.scss';
|
|
14
19
|
import { Button } from '../Button/Button';
|
|
15
20
|
|
|
21
|
+
interface DrawerContextProps {
|
|
22
|
+
open: boolean;
|
|
23
|
+
setOpen: (open: boolean) => void;
|
|
24
|
+
toggleDrawer: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DrawerContext = createContext<DrawerContextProps | null>(null);
|
|
28
|
+
|
|
29
|
+
export function useDrawer() {
|
|
30
|
+
const context = useContext(DrawerContext);
|
|
31
|
+
if (!context) {
|
|
32
|
+
throw new Error('useDrawer must be used within a DrawerProvider.');
|
|
33
|
+
}
|
|
34
|
+
return context;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface DrawerProviderProps extends React.ComponentProps<'div'> {
|
|
38
|
+
defaultIsOpen?: boolean;
|
|
39
|
+
open?: boolean;
|
|
40
|
+
onOpenChange?: (open: boolean) => void;
|
|
41
|
+
}
|
|
42
|
+
export const DrawerProvider = forwardRef<HTMLDivElement, DrawerProviderProps>(
|
|
43
|
+
(
|
|
44
|
+
{
|
|
45
|
+
defaultIsOpen = false,
|
|
46
|
+
open: openProp,
|
|
47
|
+
onOpenChange: setOpenProp,
|
|
48
|
+
className,
|
|
49
|
+
children,
|
|
50
|
+
...props
|
|
51
|
+
},
|
|
52
|
+
ref
|
|
53
|
+
) => {
|
|
54
|
+
const [_open, _setOpen] = useState(openProp ?? defaultIsOpen);
|
|
55
|
+
const open = openProp ?? _open;
|
|
56
|
+
|
|
57
|
+
const setOpen = useCallback(
|
|
58
|
+
(value: boolean | ((prev: boolean) => boolean)) => {
|
|
59
|
+
const newOpen = typeof value === 'function' ? value(open) : value;
|
|
60
|
+
if (newOpen !== open) {
|
|
61
|
+
if (setOpenProp) {
|
|
62
|
+
setOpenProp(newOpen); // Controlled
|
|
63
|
+
} else {
|
|
64
|
+
_setOpen(newOpen); // Uncontrolled
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[open, setOpenProp]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const toggleDrawer = useCallback(() => {
|
|
72
|
+
setOpen((prev) => !prev);
|
|
73
|
+
}, [setOpen]);
|
|
74
|
+
|
|
75
|
+
const contextValue = useMemo(
|
|
76
|
+
() => ({ open, setOpen, toggleDrawer }),
|
|
77
|
+
[open, setOpen, toggleDrawer]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<DrawerContext.Provider value={contextValue}>
|
|
82
|
+
<div
|
|
83
|
+
className={classNames(
|
|
84
|
+
'drawer-container',
|
|
85
|
+
{ 'drawer-open': open },
|
|
86
|
+
className
|
|
87
|
+
)}
|
|
88
|
+
ref={ref}
|
|
89
|
+
{...props}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
</DrawerContext.Provider>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
DrawerProvider.displayName = 'DrawerProvider';
|
|
99
|
+
|
|
100
|
+
const DrawerTrigger = React.forwardRef<
|
|
101
|
+
HTMLButtonElement,
|
|
102
|
+
React.ComponentProps<'button'> & {
|
|
103
|
+
asChild?: boolean;
|
|
104
|
+
}
|
|
105
|
+
>(({ asChild = false, onClick, ...triggerProps }, ref) => {
|
|
106
|
+
const context = useContext(DrawerContext);
|
|
107
|
+
const isStandalone = !context;
|
|
108
|
+
|
|
109
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
110
|
+
onClick?.(event);
|
|
111
|
+
|
|
112
|
+
if (!isStandalone) {
|
|
113
|
+
// Use context to toggle the drawer
|
|
114
|
+
context?.toggleDrawer();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const Comp = asChild ? Slot : 'button';
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Comp
|
|
122
|
+
ref={ref}
|
|
123
|
+
data-drawer="trigger"
|
|
124
|
+
aria-haspopup="dialog"
|
|
125
|
+
aria-expanded={context?.open}
|
|
126
|
+
data-state={context?.open}
|
|
127
|
+
aria-label="toggle drawer"
|
|
128
|
+
{...triggerProps}
|
|
129
|
+
onClick={handleClick}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
DrawerTrigger.displayName = 'SidebarTrigger';
|
|
134
|
+
|
|
16
135
|
export type DrawerPlacementType = 'left' | 'right' | 'top' | 'bottom';
|
|
17
136
|
export interface DrawerProps {
|
|
18
137
|
/**
|
|
19
|
-
* If the drawer is open
|
|
138
|
+
* If the drawer is open (controlled mode)
|
|
139
|
+
*/
|
|
140
|
+
isOpen?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* If the drawer starts open (uncontrolled mode)
|
|
20
143
|
*/
|
|
21
|
-
|
|
144
|
+
defaultIsOpen?: boolean;
|
|
22
145
|
/**
|
|
23
146
|
* Handle zoom/pinch gestures on iOS devices when scroll locking is enabled.
|
|
24
147
|
*/
|
|
@@ -41,11 +164,6 @@ export interface DrawerProps {
|
|
|
41
164
|
* Additional class names to add to the drawer content.
|
|
42
165
|
*/
|
|
43
166
|
className?: string;
|
|
44
|
-
/**
|
|
45
|
-
* Whether the drawer has a visible close button.
|
|
46
|
-
* If a title is defined, then a close button will be rendered
|
|
47
|
-
*/
|
|
48
|
-
closeButton?: boolean;
|
|
49
167
|
/**
|
|
50
168
|
* If true, the drawer will close when the overlay is clicked
|
|
51
169
|
*/
|
|
@@ -87,21 +205,13 @@ export interface DrawerProps {
|
|
|
87
205
|
* the "Escape" key, clicks the close button icon, or clicks the overlay.
|
|
88
206
|
*/
|
|
89
207
|
onDismiss?: (event?: React.SyntheticEvent) => void;
|
|
90
|
-
/**
|
|
91
|
-
* Title to be displayed at the top of the Drawer.
|
|
92
|
-
* A close button will be rendered automatically if this prop is defined.
|
|
93
|
-
*/
|
|
94
|
-
title?: string;
|
|
95
208
|
/**
|
|
96
209
|
* The width of the Drawer when opened. Can be given a standard css value (px, rem, em, %),
|
|
97
210
|
* or a [width token](/?path=/story/design-tokens-design-tokens--page#width)
|
|
98
211
|
*/
|
|
99
212
|
width?: DimensionSize | CssDimensionValue;
|
|
100
213
|
}
|
|
101
|
-
|
|
102
|
-
HTMLDivElement,
|
|
103
|
-
DrawerProps
|
|
104
|
-
>(
|
|
214
|
+
const Drawer: React.FC<DrawerProps> = forwardRef<HTMLDivElement, DrawerProps>(
|
|
105
215
|
(
|
|
106
216
|
{
|
|
107
217
|
ariaLabel = undefined,
|
|
@@ -109,21 +219,40 @@ export const Drawer: React.FC<DrawerProps> = forwardRef<
|
|
|
109
219
|
allowPinchZoom = false,
|
|
110
220
|
children = undefined,
|
|
111
221
|
className = undefined,
|
|
112
|
-
closeButton = false,
|
|
113
222
|
closeOnOverlayClick = true,
|
|
114
223
|
containerRef = undefined,
|
|
115
224
|
dangerouslyBypassFocusLock = false,
|
|
116
225
|
dangerouslyBypassScrollLock = false,
|
|
117
226
|
hideOverlay = false,
|
|
118
227
|
initialFocusRef = undefined,
|
|
119
|
-
isOpen,
|
|
228
|
+
isOpen: controlledIsOpen,
|
|
229
|
+
defaultIsOpen = false,
|
|
120
230
|
onDismiss = undefined,
|
|
121
231
|
placement = 'right',
|
|
122
|
-
title = undefined,
|
|
123
232
|
width = undefined,
|
|
124
233
|
},
|
|
125
234
|
ref
|
|
126
235
|
) => {
|
|
236
|
+
const context = useContext(DrawerContext);
|
|
237
|
+
const isStandalone = !context; // Determine if there's no provider
|
|
238
|
+
|
|
239
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultIsOpen);
|
|
240
|
+
const isOpen = isStandalone
|
|
241
|
+
? controlledIsOpen ?? uncontrolledOpen // Use internal or prop-based state
|
|
242
|
+
: context.open; // Use context-provided state
|
|
243
|
+
|
|
244
|
+
const setOpen = isStandalone
|
|
245
|
+
? setUncontrolledOpen // Update internal state
|
|
246
|
+
: context.setOpen; // Update context state
|
|
247
|
+
|
|
248
|
+
const handleDismiss = useCallback(
|
|
249
|
+
(event?: React.SyntheticEvent) => {
|
|
250
|
+
setOpen(false); // Update state (context or standalone)
|
|
251
|
+
onDismiss?.(event); // Trigger external callback
|
|
252
|
+
},
|
|
253
|
+
[setOpen, onDismiss]
|
|
254
|
+
);
|
|
255
|
+
|
|
127
256
|
const activateFocusLock = useCallback(() => {
|
|
128
257
|
setTimeout(() => {
|
|
129
258
|
if (initialFocusRef && initialFocusRef.current) {
|
|
@@ -136,7 +265,7 @@ export const Drawer: React.FC<DrawerProps> = forwardRef<
|
|
|
136
265
|
|
|
137
266
|
const dynamicStyle: CSSProperties = {
|
|
138
267
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
-
['--
|
|
268
|
+
['--drawer-width' as any]: dynamicWidth,
|
|
140
269
|
};
|
|
141
270
|
|
|
142
271
|
const overlayClassnames = classNames(styles.overlay, styles.drawer, {
|
|
@@ -151,60 +280,9 @@ export const Drawer: React.FC<DrawerProps> = forwardRef<
|
|
|
151
280
|
styles[placement],
|
|
152
281
|
{
|
|
153
282
|
[styles['hide-overlay']]: hideOverlay,
|
|
154
|
-
'overflow-auto': !closeButton && !title,
|
|
155
|
-
className,
|
|
156
283
|
}
|
|
157
284
|
);
|
|
158
285
|
|
|
159
|
-
const renderHeader = () => {
|
|
160
|
-
if (closeButton && onDismiss && !title) {
|
|
161
|
-
return (
|
|
162
|
-
<Box alignItems="flex-end" justifyContent="center" padding="md lg">
|
|
163
|
-
<Button
|
|
164
|
-
variant="tertiary"
|
|
165
|
-
onClick={onDismiss}
|
|
166
|
-
aria-label="close"
|
|
167
|
-
type="button"
|
|
168
|
-
iconPrefix="remove"
|
|
169
|
-
/>
|
|
170
|
-
</Box>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
if (title) {
|
|
174
|
-
return (
|
|
175
|
-
<Box
|
|
176
|
-
direction="row"
|
|
177
|
-
justifyContent="space-between"
|
|
178
|
-
alignItems="center"
|
|
179
|
-
padding={{ base: '2xl', tablet: '4xl' }}
|
|
180
|
-
>
|
|
181
|
-
<Box className={styles.title} fontWeight="bold">
|
|
182
|
-
{title}
|
|
183
|
-
</Box>
|
|
184
|
-
{onDismiss && (
|
|
185
|
-
<Button
|
|
186
|
-
variant="tertiary"
|
|
187
|
-
onClick={onDismiss}
|
|
188
|
-
aria-label="close"
|
|
189
|
-
type="button"
|
|
190
|
-
iconPrefix="remove"
|
|
191
|
-
/>
|
|
192
|
-
)}
|
|
193
|
-
</Box>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return null;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const content =
|
|
200
|
-
title || closeButton ? (
|
|
201
|
-
<Box flex="auto" overflow="auto">
|
|
202
|
-
{children}
|
|
203
|
-
</Box>
|
|
204
|
-
) : (
|
|
205
|
-
children
|
|
206
|
-
);
|
|
207
|
-
|
|
208
286
|
const parentElement = containerRef?.current
|
|
209
287
|
? (containerRef.current as HTMLElement)
|
|
210
288
|
: document.body;
|
|
@@ -228,7 +306,7 @@ export const Drawer: React.FC<DrawerProps> = forwardRef<
|
|
|
228
306
|
isOpen={isOpen}
|
|
229
307
|
overlayClassName={overlayClassnames}
|
|
230
308
|
className={contentClassnames}
|
|
231
|
-
onRequestClose={closeOnOverlayClick ?
|
|
309
|
+
onRequestClose={closeOnOverlayClick ? handleDismiss : undefined}
|
|
232
310
|
ariaHideApp={false}
|
|
233
311
|
style={{
|
|
234
312
|
content: dynamicStyle,
|
|
@@ -239,10 +317,11 @@ export const Drawer: React.FC<DrawerProps> = forwardRef<
|
|
|
239
317
|
<Box
|
|
240
318
|
aria-label={ariaLabel}
|
|
241
319
|
aria-labelledby={ariaLabelledBy}
|
|
242
|
-
height="100
|
|
320
|
+
height="100"
|
|
321
|
+
data-testid="drawer-content"
|
|
322
|
+
className={className}
|
|
243
323
|
>
|
|
244
|
-
{
|
|
245
|
-
{content}
|
|
324
|
+
{children}
|
|
246
325
|
</Box>
|
|
247
326
|
</ReactModal>
|
|
248
327
|
</Box>
|
|
@@ -251,3 +330,92 @@ export const Drawer: React.FC<DrawerProps> = forwardRef<
|
|
|
251
330
|
);
|
|
252
331
|
}
|
|
253
332
|
);
|
|
333
|
+
|
|
334
|
+
const DrawerHeader = React.forwardRef<HTMLDivElement, BoxProps>(
|
|
335
|
+
({ className, ...props }, ref) => {
|
|
336
|
+
return (
|
|
337
|
+
<Box
|
|
338
|
+
ref={ref}
|
|
339
|
+
data-drawer="header"
|
|
340
|
+
direction="row"
|
|
341
|
+
justifyContent="space-between"
|
|
342
|
+
alignItems="center"
|
|
343
|
+
padding={{ base: '2xl 2xl 0 2xl', tablet: '3xl 3xl 0 3xl' }}
|
|
344
|
+
{...props}
|
|
345
|
+
/>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
DrawerHeader.displayName = 'DrawerHeader';
|
|
350
|
+
|
|
351
|
+
const DrawerTitle = React.forwardRef<HTMLDivElement, BoxProps>(
|
|
352
|
+
({ ...props }, ref) => {
|
|
353
|
+
return <Box ref={ref} data-drawer="title" fontWeight="bold" {...props} />;
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const DrawerCloseButton = React.forwardRef<
|
|
358
|
+
React.ElementRef<typeof Button>,
|
|
359
|
+
React.ComponentProps<typeof Button> & {
|
|
360
|
+
onClose?: () => void; // Fallback to onClose if provided
|
|
361
|
+
}
|
|
362
|
+
>(({ className, onClick, onClose, ...props }, ref) => {
|
|
363
|
+
const context = useContext(DrawerContext);
|
|
364
|
+
const isStandalone = !context;
|
|
365
|
+
|
|
366
|
+
const handleClick = (
|
|
367
|
+
event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>
|
|
368
|
+
) => {
|
|
369
|
+
onClick?.(event);
|
|
370
|
+
|
|
371
|
+
if (isStandalone) {
|
|
372
|
+
// Fallback to onClose if provided
|
|
373
|
+
onClose?.();
|
|
374
|
+
} else {
|
|
375
|
+
// Use context to toggle the drawer
|
|
376
|
+
context?.toggleDrawer();
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<Button
|
|
382
|
+
ref={ref}
|
|
383
|
+
variant="tertiary"
|
|
384
|
+
aria-label="close"
|
|
385
|
+
type="button"
|
|
386
|
+
iconPrefix="remove"
|
|
387
|
+
data-drawer="close"
|
|
388
|
+
className={classNames('m-left-auto', className)}
|
|
389
|
+
size="sm"
|
|
390
|
+
onClick={handleClick}
|
|
391
|
+
{...props}
|
|
392
|
+
/>
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
DrawerCloseButton.displayName = 'DrawerCloseButton';
|
|
396
|
+
|
|
397
|
+
const DrawerContent = React.forwardRef<HTMLDivElement, BoxProps>(
|
|
398
|
+
({ className, ...props }, ref) => {
|
|
399
|
+
return (
|
|
400
|
+
<Box
|
|
401
|
+
ref={ref}
|
|
402
|
+
data-drawer="content"
|
|
403
|
+
flex="auto"
|
|
404
|
+
overflow="auto"
|
|
405
|
+
alignItems="flex-start"
|
|
406
|
+
padding={{ base: '2xl', tablet: '3xl' }}
|
|
407
|
+
gap="md"
|
|
408
|
+
{...props}
|
|
409
|
+
/>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
export {
|
|
415
|
+
Drawer,
|
|
416
|
+
DrawerContent,
|
|
417
|
+
DrawerHeader,
|
|
418
|
+
DrawerTitle,
|
|
419
|
+
DrawerTrigger,
|
|
420
|
+
DrawerCloseButton,
|
|
421
|
+
};
|