-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathdrawer.tsx
More file actions
executable file
·125 lines (117 loc) · 4.11 KB
/
drawer.tsx
File metadata and controls
executable file
·125 lines (117 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, { useContext, useEffect, useId, useRef, useState } from 'react';
import classNames from 'classnames';
import Transition from '../transition';
import Overlay from '../overlay';
import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
import { DrawerProps } from './types';
const Drawer = React.forwardRef<HTMLDivElement, DrawerProps>((props, ref) => {
const {
visible,
placement = 'right',
size = 256,
closable = true,
unmountOnClose = true,
maskType = 'default',
maskClosable = true,
onClose,
prefixCls: customisedCls,
afterClose,
zIndex = 1000,
header,
footer,
className,
maskStyle,
style,
children,
} = props;
const [drawerVisible, setDrawerVisible] = useState(visible);
const configContext = useContext(ConfigContext);
const prefixCls = getPrefixCls('drawer', configContext.prefixCls, customisedCls);
const cls = classNames(prefixCls, className, `${prefixCls}_${placement}`);
const sty: React.CSSProperties =
placement === 'top' || placement === 'bottom' ? { height: size } : { width: size };
const nodeRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
const titleId = useId();
// Focus trap + Escape key
useEffect(() => {
if (!visible) return;
previousFocusRef.current = document.activeElement as HTMLElement;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose?.(e as unknown as React.MouseEvent);
return;
}
if (e.key === 'Tab' && nodeRef.current) {
const focusable = nodeRef.current.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) { e.preventDefault(); last.focus(); }
} else {
if (document.activeElement === last) { e.preventDefault(); first.focus(); }
}
}
};
document.addEventListener('keydown', handleKeyDown);
requestAnimationFrame(() => {
if (nodeRef.current) {
const focusable = nodeRef.current.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusable.length > 0) focusable[0].focus();
}
});
return () => {
document.removeEventListener('keydown', handleKeyDown);
previousFocusRef.current?.focus();
};
}, [visible, onClose]);
return (
<Overlay
onEnter={(): void => setDrawerVisible(true)}
onExit={(): void => setDrawerVisible(false)}
zIndex={zIndex}
type={maskType}
unmountOnExit={unmountOnClose}
isShow={visible}
onExited={afterClose}
clickCallback={(e: React.MouseEvent): void => {
maskClosable && onClose ? onClose(e) : undefined;
}}
style={maskStyle}>
<div ref={ref} className={cls} style={{ ...style, ...sty }}>
<Transition
appear={true}
nodeRef={nodeRef}
in={drawerVisible}
timeout={0}
unmountOnExit={false}
classNames={`${prefixCls}__content_move`}>
<div
ref={nodeRef}
className={`${prefixCls}__content`}
role="dialog"
aria-modal="true"
aria-labelledby={header ? titleId : undefined}
onClick={(e) => e.stopPropagation()}>
{closable && (
<button type="button" className={`${prefixCls}__close-btn`} onClick={onClose} aria-label="Close">
✕
</button>
)}
{header && <div className={`${prefixCls}__header`} id={titleId}>{header}</div>}
<div className={`${prefixCls}__body`}>{children}</div>
{footer && <div className={`${prefixCls}__footer`}>{footer}</div>}
</div>
</Transition>
</div>
</Overlay>
);
});
Drawer.displayName = 'Drawer';
export default Drawer;