react-responsive-modal 5.2.6 → 6.2.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/README.md +2 -7
- package/dist/CloseIcon.d.ts +2 -2
- package/dist/FocusTrap.d.ts +2 -1
- package/dist/index.d.ts +23 -5
- package/dist/modalManager.d.ts +11 -17
- package/dist/react-responsive-modal.cjs.development.js +160 -117
- package/dist/react-responsive-modal.cjs.development.js.map +1 -1
- package/dist/react-responsive-modal.cjs.production.min.js +1 -1
- package/dist/react-responsive-modal.cjs.production.min.js.map +1 -1
- package/dist/react-responsive-modal.esm.js +160 -117
- package/dist/react-responsive-modal.esm.js.map +1 -1
- package/dist/useScrollLock.d.ts +1 -0
- package/dist/utils.d.ts +0 -2
- package/package.json +18 -27
- package/src/CloseIcon.tsx +3 -4
- package/src/FocusTrap.tsx +18 -5
- package/src/index.tsx +229 -179
- package/src/lib/focusTrapJs.ts +29 -2
- package/src/modalManager.ts +22 -19
- package/src/useScrollLock.ts +25 -0
- package/src/utils.ts +0 -16
- package/styles.css +59 -17
- package/CHANGELOG.md +0 -41
- package/LICENSE +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-responsive-modal",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "A simple responsive and accessible react modal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
"test": "tsdx test --passWithNoTests",
|
|
13
13
|
"lint": "tsdx lint",
|
|
14
14
|
"prepare": "tsdx build",
|
|
15
|
-
"
|
|
16
|
-
"docz:build": "docz build",
|
|
17
|
-
"size": "size-limit",
|
|
18
|
-
"prettier": "prettier --write \"**/*.{js,ts,tsx,css,scss,json,md,mdx}\""
|
|
15
|
+
"size": "size-limit"
|
|
19
16
|
},
|
|
20
17
|
"files": [
|
|
21
18
|
"dist",
|
|
@@ -26,18 +23,13 @@
|
|
|
26
23
|
"setupFilesAfterEnv": [
|
|
27
24
|
"./__tests__/setupTests.ts"
|
|
28
25
|
],
|
|
26
|
+
"modulePathIgnorePatterns": [
|
|
27
|
+
"cypress"
|
|
28
|
+
],
|
|
29
29
|
"coveragePathIgnorePatterns": [
|
|
30
30
|
"src/lib"
|
|
31
31
|
]
|
|
32
32
|
},
|
|
33
|
-
"prettier": {
|
|
34
|
-
"singleQuote": true
|
|
35
|
-
},
|
|
36
|
-
"husky": {
|
|
37
|
-
"hooks": {
|
|
38
|
-
"pre-commit": "tsdx lint"
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
33
|
"keywords": [
|
|
42
34
|
"react",
|
|
43
35
|
"responsive",
|
|
@@ -55,16 +47,17 @@
|
|
|
55
47
|
"size-limit": [
|
|
56
48
|
{
|
|
57
49
|
"path": "dist/react-responsive-modal.cjs.production.min.js",
|
|
58
|
-
"limit": "
|
|
50
|
+
"limit": "4.1 KB"
|
|
59
51
|
},
|
|
60
52
|
{
|
|
61
53
|
"path": "dist/react-responsive-modal.esm.js",
|
|
62
|
-
"limit": "
|
|
54
|
+
"limit": "4.1 KB"
|
|
63
55
|
}
|
|
64
56
|
],
|
|
65
57
|
"dependencies": {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
58
|
+
"@bedrock-layout/use-forwarded-ref": "^1.1.4",
|
|
59
|
+
"body-scroll-lock": "^3.1.5",
|
|
60
|
+
"classnames": "^2.2.6"
|
|
68
61
|
},
|
|
69
62
|
"peerDependencies": {
|
|
70
63
|
"react": "^16.8.0 || ^17",
|
|
@@ -72,21 +65,19 @@
|
|
|
72
65
|
},
|
|
73
66
|
"devDependencies": {
|
|
74
67
|
"@size-limit/preset-small-lib": "4.7.0",
|
|
75
|
-
"@testing-library/jest-dom": "5.11.
|
|
76
|
-
"@testing-library/react": "11.1.
|
|
68
|
+
"@testing-library/jest-dom": "5.11.6",
|
|
69
|
+
"@testing-library/react": "11.1.2",
|
|
70
|
+
"@types/body-scroll-lock": "2.6.1",
|
|
77
71
|
"@types/classnames": "2.2.11",
|
|
78
|
-
"@types/
|
|
79
|
-
"@types/node": "14.14.6",
|
|
72
|
+
"@types/node": "14.14.7",
|
|
80
73
|
"@types/react": "16.9.56",
|
|
81
74
|
"@types/react-dom": "16.9.9",
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"gatsby": "2.23.11",
|
|
85
|
-
"gatsby-theme-docz": "2.3.1",
|
|
75
|
+
"babel-jest": "26.6.3",
|
|
76
|
+
"cypress": "5.6.0",
|
|
86
77
|
"husky": "4.3.0",
|
|
87
78
|
"prettier": "2.1.2",
|
|
88
|
-
"react": "
|
|
89
|
-
"react-dom": "
|
|
79
|
+
"react": "17.0.1",
|
|
80
|
+
"react-dom": "17.0.1",
|
|
90
81
|
"size-limit": "4.7.0",
|
|
91
82
|
"tsdx": "0.14.1",
|
|
92
83
|
"tslib": "2.0.3",
|
package/src/CloseIcon.tsx
CHANGED
|
@@ -15,7 +15,7 @@ interface CloseIconProps {
|
|
|
15
15
|
classes: {
|
|
16
16
|
closeButton?: string;
|
|
17
17
|
};
|
|
18
|
-
|
|
18
|
+
onClick: () => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const CloseIcon = ({
|
|
@@ -24,13 +24,13 @@ const CloseIcon = ({
|
|
|
24
24
|
styles,
|
|
25
25
|
id,
|
|
26
26
|
closeIcon,
|
|
27
|
-
|
|
27
|
+
onClick,
|
|
28
28
|
}: CloseIconProps) => (
|
|
29
29
|
<button
|
|
30
30
|
id={id}
|
|
31
31
|
className={cx(classes.closeButton, classNames?.closeButton)}
|
|
32
32
|
style={styles?.closeButton}
|
|
33
|
-
onClick={
|
|
33
|
+
onClick={onClick}
|
|
34
34
|
data-testid="close-button"
|
|
35
35
|
>
|
|
36
36
|
{closeIcon ? (
|
|
@@ -39,7 +39,6 @@ const CloseIcon = ({
|
|
|
39
39
|
<svg
|
|
40
40
|
className={classNames?.closeIcon}
|
|
41
41
|
style={styles?.closeIcon}
|
|
42
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
43
42
|
width={28}
|
|
44
43
|
height={28}
|
|
45
44
|
viewBox="0 0 36 36"
|
package/src/FocusTrap.tsx
CHANGED
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
|
|
9
9
|
interface FocusTrapProps {
|
|
10
10
|
container?: React.RefObject<HTMLElement> | null;
|
|
11
|
+
initialFocusRef?: React.RefObject<HTMLElement>;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const FocusTrap = ({ container }: FocusTrapProps) => {
|
|
14
|
+
export const FocusTrap = ({ container, initialFocusRef }: FocusTrapProps) => {
|
|
14
15
|
const refLastFocus = useRef<HTMLElement | null>();
|
|
15
16
|
/**
|
|
16
17
|
* Handle focus lock on the modal
|
|
@@ -27,8 +28,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
|
|
|
27
28
|
}
|
|
28
29
|
// On mount we focus on the first focusable element in the modal if there is one
|
|
29
30
|
if (isBrowser && container?.current) {
|
|
30
|
-
const
|
|
31
|
-
if (allTabbingElements[0]) {
|
|
31
|
+
const savePreviousFocus = () => {
|
|
32
32
|
// First we save the last focused element
|
|
33
33
|
// only if it's a focusable element
|
|
34
34
|
if (
|
|
@@ -38,7 +38,20 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
|
|
|
38
38
|
) {
|
|
39
39
|
refLastFocus.current = document.activeElement as HTMLElement;
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (initialFocusRef) {
|
|
44
|
+
savePreviousFocus();
|
|
45
|
+
// We need to schedule focusing on a next frame - this allows to focus on the modal root
|
|
46
|
+
requestAnimationFrame(() => {
|
|
47
|
+
initialFocusRef.current?.focus();
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
const allTabbingElements = getAllTabbingElements(container.current);
|
|
51
|
+
if (allTabbingElements[0]) {
|
|
52
|
+
savePreviousFocus();
|
|
53
|
+
allTabbingElements[0].focus();
|
|
54
|
+
}
|
|
42
55
|
}
|
|
43
56
|
}
|
|
44
57
|
return () => {
|
|
@@ -48,7 +61,7 @@ export const FocusTrap = ({ container }: FocusTrapProps) => {
|
|
|
48
61
|
refLastFocus.current?.focus();
|
|
49
62
|
}
|
|
50
63
|
};
|
|
51
|
-
}, [container]);
|
|
64
|
+
}, [container, initialFocusRef]);
|
|
52
65
|
|
|
53
66
|
return null;
|
|
54
67
|
};
|
package/src/index.tsx
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import ReactDom from 'react-dom';
|
|
3
3
|
import cx from 'classnames';
|
|
4
4
|
import CloseIcon from './CloseIcon';
|
|
5
5
|
import { FocusTrap } from './FocusTrap';
|
|
6
|
-
import modalManager from './modalManager';
|
|
7
|
-
import {
|
|
6
|
+
import { modalManager, useModalManager } from './modalManager';
|
|
7
|
+
import { useScrollLock } from './useScrollLock';
|
|
8
|
+
import { isBrowser } from './utils';
|
|
9
|
+
import useForwardedRef from '@bedrock-layout/use-forwarded-ref';
|
|
8
10
|
|
|
9
11
|
const classes = {
|
|
12
|
+
root: 'react-responsive-modal-root',
|
|
10
13
|
overlay: 'react-responsive-modal-overlay',
|
|
14
|
+
overlayAnimationIn: 'react-responsive-modal-overlay-in',
|
|
15
|
+
overlayAnimationOut: 'react-responsive-modal-overlay-out',
|
|
16
|
+
modalContainer: 'react-responsive-modal-container',
|
|
17
|
+
modalContainerCenter: 'react-responsive-modal-containerCenter',
|
|
11
18
|
modal: 'react-responsive-modal-modal',
|
|
12
|
-
|
|
19
|
+
modalAnimationIn: 'react-responsive-modal-modal-in',
|
|
20
|
+
modalAnimationOut: 'react-responsive-modal-modal-out',
|
|
13
21
|
closeButton: 'react-responsive-modal-closeButton',
|
|
14
|
-
animationIn: 'react-responsive-modal-fadeIn',
|
|
15
|
-
animationOut: 'react-responsive-modal-fadeOut',
|
|
16
22
|
};
|
|
17
23
|
|
|
18
24
|
export interface ModalProps {
|
|
@@ -46,6 +52,8 @@ export interface ModalProps {
|
|
|
46
52
|
blockScroll?: boolean;
|
|
47
53
|
/**
|
|
48
54
|
* Show the close icon.
|
|
55
|
+
*
|
|
56
|
+
* Default to true.
|
|
49
57
|
*/
|
|
50
58
|
showCloseIcon?: boolean;
|
|
51
59
|
/**
|
|
@@ -62,28 +70,40 @@ export interface ModalProps {
|
|
|
62
70
|
* Default to true.
|
|
63
71
|
*/
|
|
64
72
|
focusTrapped?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Element to focus when focus trap is used.
|
|
75
|
+
*
|
|
76
|
+
* Default to undefined.
|
|
77
|
+
*/
|
|
78
|
+
initialFocusRef?: React.RefObject<HTMLElement>;
|
|
65
79
|
/**
|
|
66
80
|
* You can specify a container prop which should be of type `Element`.
|
|
67
81
|
* The portal will be rendered inside that element.
|
|
68
82
|
* The default behavior will create a div node and render it at the at the end of document.body.
|
|
69
83
|
*/
|
|
70
|
-
container?: Element;
|
|
84
|
+
container?: Element | null;
|
|
71
85
|
/**
|
|
72
86
|
* An object containing classNames to style the modal.
|
|
73
87
|
*/
|
|
74
88
|
classNames?: {
|
|
89
|
+
root?: string;
|
|
75
90
|
overlay?: string;
|
|
91
|
+
overlayAnimationIn?: string;
|
|
92
|
+
overlayAnimationOut?: string;
|
|
93
|
+
modalContainer?: string;
|
|
76
94
|
modal?: string;
|
|
95
|
+
modalAnimationIn?: string;
|
|
96
|
+
modalAnimationOut?: string;
|
|
77
97
|
closeButton?: string;
|
|
78
98
|
closeIcon?: string;
|
|
79
|
-
animationIn?: string;
|
|
80
|
-
animationOut?: string;
|
|
81
99
|
};
|
|
82
100
|
/**
|
|
83
101
|
* An object containing the styles objects to style the modal.
|
|
84
102
|
*/
|
|
85
103
|
styles?: {
|
|
104
|
+
root?: React.CSSProperties;
|
|
86
105
|
overlay?: React.CSSProperties;
|
|
106
|
+
modalContainer?: React.CSSProperties;
|
|
87
107
|
modal?: React.CSSProperties;
|
|
88
108
|
closeButton?: React.CSSProperties;
|
|
89
109
|
closeIcon?: React.CSSProperties;
|
|
@@ -91,7 +111,7 @@ export interface ModalProps {
|
|
|
91
111
|
/**
|
|
92
112
|
* Animation duration in milliseconds.
|
|
93
113
|
*
|
|
94
|
-
* Default to
|
|
114
|
+
* Default to 300.
|
|
95
115
|
*/
|
|
96
116
|
animationDuration?: number;
|
|
97
117
|
/**
|
|
@@ -108,6 +128,10 @@ export interface ModalProps {
|
|
|
108
128
|
* ARIA description for modal
|
|
109
129
|
*/
|
|
110
130
|
ariaDescribedby?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Avoid unpleasant flickering effect when body overflow is hidden. For more information see https://www.npmjs.com/package/body-scroll-lock
|
|
133
|
+
*/
|
|
134
|
+
reserveScrollBarGap?: boolean;
|
|
111
135
|
/**
|
|
112
136
|
* id attribute for modal
|
|
113
137
|
*/
|
|
@@ -133,197 +157,223 @@ export interface ModalProps {
|
|
|
133
157
|
children?: React.ReactNode;
|
|
134
158
|
}
|
|
135
159
|
|
|
136
|
-
export const Modal = (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
export const Modal = React.forwardRef(
|
|
161
|
+
(
|
|
162
|
+
{
|
|
163
|
+
open,
|
|
164
|
+
center,
|
|
165
|
+
blockScroll = true,
|
|
166
|
+
closeOnEsc = true,
|
|
167
|
+
closeOnOverlayClick = true,
|
|
168
|
+
container,
|
|
169
|
+
showCloseIcon = true,
|
|
170
|
+
closeIconId,
|
|
171
|
+
closeIcon,
|
|
172
|
+
focusTrapped = true,
|
|
173
|
+
initialFocusRef = undefined,
|
|
174
|
+
animationDuration = 300,
|
|
175
|
+
classNames,
|
|
176
|
+
styles,
|
|
177
|
+
role = 'dialog',
|
|
178
|
+
ariaDescribedby,
|
|
179
|
+
ariaLabelledby,
|
|
180
|
+
modalId,
|
|
181
|
+
onClose,
|
|
182
|
+
onEscKeyDown,
|
|
183
|
+
onOverlayClick,
|
|
184
|
+
onAnimationEnd,
|
|
185
|
+
children,
|
|
186
|
+
reserveScrollBarGap,
|
|
187
|
+
}: ModalProps,
|
|
188
|
+
ref: React.ForwardedRef<HTMLDivElement>
|
|
189
|
+
) => {
|
|
190
|
+
const refDialog = useForwardedRef(ref);
|
|
191
|
+
const refModal = useRef<HTMLDivElement>(null);
|
|
192
|
+
const refShouldClose = useRef<boolean | null>(null);
|
|
193
|
+
const refContainer = useRef<HTMLDivElement | null>(null);
|
|
194
|
+
// Lazily create the ref instance
|
|
195
|
+
// https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily
|
|
196
|
+
if (refContainer.current === null && isBrowser) {
|
|
197
|
+
refContainer.current = document.createElement('div');
|
|
198
|
+
}
|
|
168
199
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
200
|
+
// The value should be false for srr, that way when the component is hydrated client side,
|
|
201
|
+
// it will match the server rendered content
|
|
202
|
+
const [showPortal, setShowPortal] = useState(false);
|
|
172
203
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (blockScroll) {
|
|
176
|
-
blockNoScroll();
|
|
177
|
-
}
|
|
178
|
-
if (
|
|
179
|
-
refContainer.current &&
|
|
180
|
-
!container &&
|
|
181
|
-
!document.body.contains(refContainer.current)
|
|
182
|
-
) {
|
|
183
|
-
document.body.appendChild(refContainer.current);
|
|
184
|
-
}
|
|
185
|
-
document.addEventListener('keydown', handleKeydown);
|
|
186
|
-
};
|
|
204
|
+
// Hook used to manage multiple modals opened at the same time
|
|
205
|
+
useModalManager(refModal, open);
|
|
187
206
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (blockScroll) {
|
|
191
|
-
unblockNoScroll();
|
|
192
|
-
}
|
|
193
|
-
if (
|
|
194
|
-
refContainer.current &&
|
|
195
|
-
!container &&
|
|
196
|
-
document.body.contains(refContainer.current)
|
|
197
|
-
) {
|
|
198
|
-
document.body.removeChild(refContainer.current);
|
|
199
|
-
}
|
|
200
|
-
document.removeEventListener('keydown', handleKeydown);
|
|
201
|
-
};
|
|
207
|
+
// Hook used to manage the scroll
|
|
208
|
+
useScrollLock(refModal, open, showPortal, blockScroll, reserveScrollBarGap);
|
|
202
209
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
const handleOpen = () => {
|
|
211
|
+
if (
|
|
212
|
+
refContainer.current &&
|
|
213
|
+
!container &&
|
|
214
|
+
!document.body.contains(refContainer.current)
|
|
215
|
+
) {
|
|
216
|
+
document.body.appendChild(refContainer.current);
|
|
217
|
+
}
|
|
211
218
|
|
|
212
|
-
|
|
219
|
+
document.addEventListener('keydown', handleKeydown);
|
|
220
|
+
};
|
|
213
221
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
const handleClose = () => {
|
|
223
|
+
if (
|
|
224
|
+
refContainer.current &&
|
|
225
|
+
!container &&
|
|
226
|
+
document.body.contains(refContainer.current)
|
|
227
|
+
) {
|
|
228
|
+
document.body.removeChild(refContainer.current);
|
|
229
|
+
}
|
|
230
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
234
|
+
// Only the last modal need to be escaped when pressing the esc key
|
|
235
|
+
if (event.keyCode !== 27 || !modalManager.isTopModal(refModal)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onEscKeyDown?.(event);
|
|
218
240
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// When the component is unmounted directly we want to unblock the scroll
|
|
222
|
-
if (showPortal) {
|
|
223
|
-
handleClose();
|
|
241
|
+
if (closeOnEsc) {
|
|
242
|
+
onClose();
|
|
224
243
|
}
|
|
225
244
|
};
|
|
226
|
-
}, [showPortal]);
|
|
227
245
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
return () => {
|
|
248
|
+
if (showPortal) {
|
|
249
|
+
// When the modal is closed or removed directly, cleanup the listeners
|
|
250
|
+
handleClose();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}, [showPortal]);
|
|
236
254
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
// If the open prop is changing, we need to open the modal
|
|
257
|
+
// This is also called on the first render if the open prop is true when the modal is created
|
|
258
|
+
if (open && !showPortal) {
|
|
259
|
+
setShowPortal(true);
|
|
260
|
+
handleOpen();
|
|
261
|
+
}
|
|
262
|
+
}, [open]);
|
|
243
263
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
const handleClickOverlay = (
|
|
265
|
+
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
|
266
|
+
) => {
|
|
267
|
+
if (refShouldClose.current === null) {
|
|
268
|
+
refShouldClose.current = true;
|
|
269
|
+
}
|
|
248
270
|
|
|
249
|
-
|
|
271
|
+
if (!refShouldClose.current) {
|
|
272
|
+
refShouldClose.current = null;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
250
275
|
|
|
251
|
-
|
|
252
|
-
onClose();
|
|
253
|
-
}
|
|
276
|
+
onOverlayClick?.(event);
|
|
254
277
|
|
|
255
|
-
|
|
256
|
-
|
|
278
|
+
if (closeOnOverlayClick) {
|
|
279
|
+
onClose();
|
|
280
|
+
}
|
|
257
281
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
};
|
|
282
|
+
refShouldClose.current = null;
|
|
283
|
+
};
|
|
261
284
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
285
|
+
const handleModalEvent = () => {
|
|
286
|
+
refShouldClose.current = false;
|
|
287
|
+
};
|
|
265
288
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
289
|
+
const handleAnimationEnd = () => {
|
|
290
|
+
if (!open) {
|
|
291
|
+
setShowPortal(false);
|
|
292
|
+
}
|
|
270
293
|
|
|
271
|
-
|
|
272
|
-
|
|
294
|
+
onAnimationEnd?.();
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const containerModal = container || refContainer.current;
|
|
273
298
|
|
|
274
|
-
|
|
299
|
+
const overlayAnimation = open
|
|
300
|
+
? classNames?.overlayAnimationIn ?? classes.overlayAnimationIn
|
|
301
|
+
: classNames?.overlayAnimationOut ?? classes.overlayAnimationOut;
|
|
275
302
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
? classNames?.animationIn ?? classes.animationIn
|
|
283
|
-
: classNames?.animationOut ?? classes.animationOut
|
|
284
|
-
} ${animationDuration}ms`,
|
|
285
|
-
...styles?.overlay,
|
|
286
|
-
}}
|
|
287
|
-
className={cx(classes.overlay, classNames?.overlay)}
|
|
288
|
-
onClick={handleClickOverlay}
|
|
289
|
-
onAnimationEnd={handleAnimationEnd}
|
|
290
|
-
data-testid="overlay"
|
|
291
|
-
>
|
|
303
|
+
const modalAnimation = open
|
|
304
|
+
? classNames?.modalAnimationIn ?? classes.modalAnimationIn
|
|
305
|
+
: classNames?.modalAnimationOut ?? classes.modalAnimationOut;
|
|
306
|
+
|
|
307
|
+
return showPortal && containerModal
|
|
308
|
+
? ReactDom.createPortal(
|
|
292
309
|
<div
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
center && classes.modalCenter,
|
|
297
|
-
classNames?.modal
|
|
298
|
-
)}
|
|
299
|
-
style={styles?.modal}
|
|
300
|
-
onMouseDown={handleModalEvent}
|
|
301
|
-
onMouseUp={handleModalEvent}
|
|
302
|
-
onClick={handleModalEvent}
|
|
303
|
-
id={modalId}
|
|
304
|
-
role={role}
|
|
305
|
-
aria-modal="true"
|
|
306
|
-
aria-labelledby={ariaLabelledby}
|
|
307
|
-
aria-describedby={ariaDescribedby}
|
|
308
|
-
data-testid="modal"
|
|
310
|
+
className={cx(classes.root, classNames?.root)}
|
|
311
|
+
style={styles?.root}
|
|
312
|
+
data-testid="root"
|
|
309
313
|
>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
styles
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
314
|
+
<div
|
|
315
|
+
className={cx(classes.overlay, classNames?.overlay)}
|
|
316
|
+
data-testid="overlay"
|
|
317
|
+
aria-hidden={true}
|
|
318
|
+
style={{
|
|
319
|
+
animation: `${overlayAnimation} ${animationDuration}ms`,
|
|
320
|
+
...styles?.overlay,
|
|
321
|
+
}}
|
|
322
|
+
/>
|
|
323
|
+
<div
|
|
324
|
+
ref={refModal}
|
|
325
|
+
className={cx(
|
|
326
|
+
classes.modalContainer,
|
|
327
|
+
center && classes.modalContainerCenter,
|
|
328
|
+
classNames?.modalContainer
|
|
329
|
+
)}
|
|
330
|
+
style={styles?.modalContainer}
|
|
331
|
+
data-testid="modal-container"
|
|
332
|
+
onClick={handleClickOverlay}
|
|
333
|
+
>
|
|
334
|
+
<div
|
|
335
|
+
ref={refDialog}
|
|
336
|
+
className={cx(classes.modal, classNames?.modal)}
|
|
337
|
+
style={{
|
|
338
|
+
animation: `${modalAnimation} ${animationDuration}ms`,
|
|
339
|
+
...styles?.modal,
|
|
340
|
+
}}
|
|
341
|
+
onMouseDown={handleModalEvent}
|
|
342
|
+
onMouseUp={handleModalEvent}
|
|
343
|
+
onClick={handleModalEvent}
|
|
344
|
+
onAnimationEnd={handleAnimationEnd}
|
|
345
|
+
id={modalId}
|
|
346
|
+
role={role}
|
|
347
|
+
aria-modal="true"
|
|
348
|
+
aria-labelledby={ariaLabelledby}
|
|
349
|
+
aria-describedby={ariaDescribedby}
|
|
350
|
+
data-testid="modal"
|
|
351
|
+
tabIndex={-1}
|
|
352
|
+
>
|
|
353
|
+
{focusTrapped && (
|
|
354
|
+
<FocusTrap
|
|
355
|
+
container={refDialog}
|
|
356
|
+
initialFocusRef={initialFocusRef}
|
|
357
|
+
/>
|
|
358
|
+
)}
|
|
359
|
+
{children}
|
|
360
|
+
{showCloseIcon && (
|
|
361
|
+
<CloseIcon
|
|
362
|
+
classes={classes}
|
|
363
|
+
classNames={classNames}
|
|
364
|
+
styles={styles}
|
|
365
|
+
closeIcon={closeIcon}
|
|
366
|
+
onClick={onClose}
|
|
367
|
+
id={closeIconId}
|
|
368
|
+
/>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
</div>,
|
|
373
|
+
containerModal
|
|
374
|
+
)
|
|
375
|
+
: null;
|
|
376
|
+
}
|
|
377
|
+
);
|
|
328
378
|
|
|
329
379
|
export default Modal;
|