-
Notifications
You must be signed in to change notification settings - Fork 0
/
Modal.tsx
119 lines (106 loc) · 3.19 KB
/
Modal.tsx
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
import { ReactNode, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
import useFocusTrap from "../../hooks/useFocusTrap";
import { Button } from "../Button/Button.styles";
import {
Description,
Heading,
ModalContent,
ModalOverlay,
ModalWrapper
} from "./Modal.styles";
type ModalProps = {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
triggerText: string;
heading: string;
description?: string;
content: ReactNode;
};
const Modal = ({
isOpen,
setIsOpen,
triggerText,
heading,
description,
content
}: ModalProps) => {
const [modalRef, handleKeyDown] = useFocusTrap();
const lastFocusedElement = useRef<Element | null>(null);
const headingId = "modal-heading";
const descriptionId = "modal-description";
// ENHANCEMENT Move useEffect into custom useModal hook
useEffect(() => {
const closeOnEscapePress = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setIsOpen(false);
}
};
if (isOpen) {
// prevent overflow
document.body.style.overflow = "hidden";
// add escape key listener
document.addEventListener("keydown", closeOnEscapePress);
// focus first valid element in modal
if (modalRef.current) {
const focusableElements = modalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
) as NodeListOf<HTMLElement>;
if (focusableElements.length > 0) {
// store last focused element
lastFocusedElement.current = document.activeElement;
// focus first valid element in modal
focusableElements[0].focus();
}
}
} else {
document.body.style.overflow = "unset";
document.removeEventListener("keydown", closeOnEscapePress);
}
return () => {
document.body.style.overflow = "unset";
document.removeEventListener("keydown", closeOnEscapePress);
// refocus last focused element
if (lastFocusedElement.current instanceof HTMLElement) {
lastFocusedElement.current.focus();
}
};
}, [isOpen, setIsOpen, modalRef]);
return (
<>
{/* ENHANCEMENT Allow custom button be passed in */}
<Button onClick={() => setIsOpen(true)}>{triggerText}</Button>
{isOpen &&
createPortal(
<>
<ModalOverlay
data-testid="modal-overlay"
onClick={() => {
setIsOpen(false);
}}
/>
<ModalWrapper
aria-modal
aria-labelledby={headingId}
aria-describedby={descriptionId}
tabIndex={-1}
role="dialog"
ref={modalRef}
onKeyDown={handleKeyDown}
>
<ModalContent>
<Heading id={headingId}>{heading}</Heading>
<Description id={descriptionId}>
{description}
</Description>
{/* ENHANCEMENT Add close button */}
{content}
</ModalContent>
</ModalWrapper>
</>,
document.body
)}
</>
);
};
export default Modal;