1
1
import PropTypes from 'prop-types' ;
2
- import React , { useRef } from 'react' ;
2
+ import React , {
3
+ useCallback ,
4
+ useEffect ,
5
+ useImperativeHandle ,
6
+ useRef ,
7
+ } from 'react' ;
3
8
import { createPortal } from 'react-dom' ;
4
9
import { withGlobalProps } from '../../providers/globalProps' ;
5
10
import { classNames } from '../../utils/classNames' ;
6
11
import { transferProps } from '../../utils/transferProps' ;
12
+ import { dialogOnCancelHandler } from './_helpers/dialogOnCancelHandler' ;
13
+ import { dialogOnClickHandler } from './_helpers/dialogOnClickHandler' ;
14
+ import { dialogOnCloseHandler } from './_helpers/dialogOnCloseHandler' ;
15
+ import { dialogOnKeyDownHandler } from './_helpers/dialogOnKeyDownHandler' ;
7
16
import { getPositionClassName } from './_helpers/getPositionClassName' ;
8
17
import { getSizeClassName } from './_helpers/getSizeClassName' ;
9
18
import { useModalFocus } from './_hooks/useModalFocus' ;
@@ -12,90 +21,121 @@ import styles from './Modal.module.scss';
12
21
13
22
const preRender = (
14
23
children ,
15
- childrenWrapperRef ,
16
- closeButtonRef ,
24
+ dialogRef ,
17
25
position ,
18
- restProps ,
19
26
size ,
27
+ events ,
28
+ restProps ,
20
29
) => (
21
- < div
22
- className = { styles . backdrop }
23
- onClick = { ( e ) => {
24
- e . preventDefault ( ) ;
25
- if ( closeButtonRef ?. current != null ) {
26
- closeButtonRef . current . click ( ) ;
27
- }
28
- } }
29
- role = "presentation"
30
+ < dialog
31
+ { ... transferProps ( restProps ) }
32
+ { ... transferProps ( events ) }
33
+ className = { classNames (
34
+ styles . root ,
35
+ getSizeClassName ( size , styles ) ,
36
+ getPositionClassName ( position , styles ) ,
37
+ ) }
38
+ ref = { dialogRef }
30
39
>
31
- < div
32
- { ...transferProps ( restProps ) }
33
- className = { classNames (
34
- styles . root ,
35
- getSizeClassName ( size , styles ) ,
36
- getPositionClassName ( position , styles ) ,
37
- ) }
38
- onClick = { ( e ) => {
39
- e . stopPropagation ( ) ;
40
- } }
41
- ref = { childrenWrapperRef }
42
- role = "presentation"
43
- >
44
- { children }
45
- </ div >
46
- </ div >
40
+ { children }
41
+ </ dialog >
47
42
) ;
48
43
49
44
export const Modal = ( {
45
+ allowCloseOnBackdropClick,
46
+ allowCloseOnEscapeKey,
47
+ allowPrimaryActionOnEnterKey,
50
48
autoFocus,
51
49
children,
52
50
closeButtonRef,
51
+ dialogRef,
53
52
portalId,
54
53
position,
55
54
preventScrollUnderneath,
56
55
primaryButtonRef,
57
56
size,
58
57
...restProps
59
58
} ) => {
60
- const childrenWrapperRef = useRef ( ) ;
59
+ const internalDialogRef = useRef ( ) ;
61
60
62
- useModalFocus (
63
- autoFocus ,
64
- childrenWrapperRef ,
65
- primaryButtonRef ,
66
- closeButtonRef ,
67
- ) ;
61
+ useEffect ( ( ) => {
62
+ internalDialogRef . current . showModal ( ) ;
63
+ } , [ ] ) ;
68
64
65
+ // We need to have a reference to the dialog element to be able to call its methods,
66
+ // but at the same time we want to expose this reference to the parent component for
67
+ // case someone wants to call dialog methods from outside the component.
68
+ useImperativeHandle ( dialogRef , ( ) => internalDialogRef . current ) ;
69
+
70
+ useModalFocus ( autoFocus , internalDialogRef , primaryButtonRef ) ;
69
71
useModalScrollPrevention ( preventScrollUnderneath ) ;
70
72
73
+ const onCancel = useCallback (
74
+ ( e ) => dialogOnCancelHandler ( e , closeButtonRef , restProps . onCancel ) ,
75
+ [ closeButtonRef , restProps . onCancel ] ,
76
+ ) ;
77
+ const onClick = useCallback (
78
+ ( e ) => dialogOnClickHandler ( e , closeButtonRef , internalDialogRef , allowCloseOnBackdropClick ) ,
79
+ [ allowCloseOnBackdropClick , closeButtonRef , internalDialogRef ] ,
80
+ ) ;
81
+ const onClose = useCallback (
82
+ ( e ) => dialogOnCloseHandler ( e , closeButtonRef , restProps . onClose ) ,
83
+ [ closeButtonRef , restProps . onClose ] ,
84
+ ) ;
85
+ const onKeyDown = useCallback (
86
+ ( e ) => dialogOnKeyDownHandler (
87
+ e ,
88
+ closeButtonRef ,
89
+ primaryButtonRef ,
90
+ allowCloseOnEscapeKey ,
91
+ allowPrimaryActionOnEnterKey ,
92
+ ) ,
93
+ [
94
+ allowCloseOnEscapeKey ,
95
+ allowPrimaryActionOnEnterKey ,
96
+ closeButtonRef ,
97
+ primaryButtonRef ,
98
+ ] ,
99
+ ) ;
100
+ const events = {
101
+ onCancel,
102
+ onClick,
103
+ onClose,
104
+ onKeyDown,
105
+ } ;
106
+
71
107
if ( portalId === null ) {
72
108
return preRender (
73
109
children ,
74
- childrenWrapperRef ,
75
- closeButtonRef ,
110
+ internalDialogRef ,
76
111
position ,
77
- restProps ,
78
112
size ,
113
+ events ,
114
+ restProps ,
79
115
) ;
80
116
}
81
117
82
118
return createPortal (
83
119
preRender (
84
120
children ,
85
- childrenWrapperRef ,
86
- closeButtonRef ,
121
+ internalDialogRef ,
87
122
position ,
88
- restProps ,
89
123
size ,
124
+ events ,
125
+ restProps ,
90
126
) ,
91
127
document . getElementById ( portalId ) ,
92
128
) ;
93
129
} ;
94
130
95
131
Modal . defaultProps = {
132
+ allowCloseOnBackdropClick : true ,
133
+ allowCloseOnEscapeKey : true ,
134
+ allowPrimaryActionOnEnterKey : true ,
96
135
autoFocus : true ,
97
136
children : null ,
98
137
closeButtonRef : null ,
138
+ dialogRef : null ,
99
139
portalId : null ,
100
140
position : 'center' ,
101
141
preventScrollUnderneath : window . document . body ,
@@ -104,6 +144,18 @@ Modal.defaultProps = {
104
144
} ;
105
145
106
146
Modal . propTypes = {
147
+ /**
148
+ * If `true`, the `Modal` can be closed by clicking on the backdrop.
149
+ */
150
+ allowCloseOnBackdropClick : PropTypes . bool ,
151
+ /**
152
+ * If `true`, the `Modal` can be closed by pressing the Escape key.
153
+ */
154
+ allowCloseOnEscapeKey : PropTypes . bool ,
155
+ /**
156
+ * If `true`, the `Modal` can be submitted by pressing the Enter key.
157
+ */
158
+ allowPrimaryActionOnEnterKey : PropTypes . bool ,
107
159
/**
108
160
* If `true`, focus the first input element in the `Modal`, or primary button (referenced by the `primaryButtonRef`
109
161
* prop), or other focusable element when the `Modal` is opened. If there are none or `autoFocus` is set to `false`,
@@ -121,12 +173,20 @@ Modal.propTypes = {
121
173
*/
122
174
children : PropTypes . node ,
123
175
/**
124
- * Reference to close button element. It is used to close modal when Escape key is pressed or the backdrop is clicked.
176
+ * Reference to close button element. It is used to close modal when Escape key is pressed
177
+ * or the backdrop is clicked.
125
178
*/
126
179
closeButtonRef : PropTypes . shape ( {
127
180
// eslint-disable-next-line react/forbid-prop-types
128
181
current : PropTypes . any ,
129
182
} ) ,
183
+ /**
184
+ * Reference to dialog element
185
+ */
186
+ dialogRef : PropTypes . shape ( {
187
+ // eslint-disable-next-line react/forbid-prop-types
188
+ current : PropTypes . any ,
189
+ } ) ,
130
190
/**
131
191
* If set, modal is rendered in the React Portal with that ID.
132
192
*/
0 commit comments