1
1
import React , { Component } from 'react' ;
2
+ import { PropTypes } from 'prop-types' ;
2
3
import * as focusManager from '../helpers/focusManager' ;
3
- import { scopeTab } from '../helpers/scopeTab' ;
4
+ import scopeTab from '../helpers/scopeTab' ;
4
5
5
6
// so that our CSS is statically analyzable
6
7
const CLASS_NAMES = {
@@ -19,6 +20,33 @@ export default class ModalPortal extends Component {
19
20
}
20
21
} ;
21
22
23
+ static propTypes = {
24
+ isOpen : PropTypes . bool . isRequired ,
25
+ defaultStyles : PropTypes . shape ( {
26
+ content : PropTypes . object ,
27
+ overlay : PropTypes . object
28
+ } ) ,
29
+ style : PropTypes . shape ( {
30
+ content : PropTypes . object ,
31
+ overlay : PropTypes . object
32
+ } ) ,
33
+ className : PropTypes . oneOfType ( [
34
+ PropTypes . String ,
35
+ PropTypes . object
36
+ ] ) ,
37
+ overlayClassName : PropTypes . oneOfType ( [
38
+ PropTypes . String ,
39
+ PropTypes . object
40
+ ] ) ,
41
+ onAfterOpen : PropTypes . func ,
42
+ onRequestClose : PropTypes . func ,
43
+ closeTimeoutMS : PropTypes . number ,
44
+ shouldCloseOnOverlayClick : PropTypes . bool ,
45
+ role : PropTypes . string ,
46
+ contentLabel : PropTypes . string ,
47
+ children : PropTypes . node
48
+ } ;
49
+
22
50
constructor ( props ) {
23
51
super ( props ) ;
24
52
@@ -38,10 +66,6 @@ export default class ModalPortal extends Component {
38
66
}
39
67
}
40
68
41
- componentWillUnmount ( ) {
42
- clearTimeout ( this . closeTimer ) ;
43
- }
44
-
45
69
componentWillReceiveProps ( newProps ) {
46
70
// Focus only needs to be set once when the modal is being opened
47
71
if ( ! this . props . isOpen && newProps . isOpen ) {
@@ -59,6 +83,10 @@ export default class ModalPortal extends Component {
59
83
}
60
84
}
61
85
86
+ componentWillUnmount ( ) {
87
+ clearTimeout ( this . closeTimer ) ;
88
+ }
89
+
62
90
setFocusAfterRender = focus => {
63
91
this . focusAfterRender = focus ;
64
92
}
@@ -93,17 +121,16 @@ export default class ModalPortal extends Component {
93
121
}
94
122
}
95
123
96
- focusContent = ( ) => {
97
- // Don't steal focus from inner elements
98
- if ( ! this . contentHasFocus ( ) ) {
99
- this . refs . content . focus ( ) ;
100
- }
101
- }
124
+ // Don't steal focus from inner elements
125
+ focusContent = ( ) => ( ! this . contentHasFocus ( ) ) && this . content . focus ( ) ;
102
126
103
127
closeWithTimeout = ( ) => {
104
128
const closesAt = Date . now ( ) + this . props . closeTimeoutMS ;
105
- this . setState ( { beforeClose : true , closesAt : closesAt } , ( ) => {
106
- this . closeTimer = setTimeout ( this . closeWithoutTimeout , this . state . closesAt - Date . now ( ) ) ;
129
+ this . setState ( { beforeClose : true , closesAt } , ( ) => {
130
+ this . closeTimer = setTimeout (
131
+ this . closeWithoutTimeout ,
132
+ this . state . closesAt - Date . now ( )
133
+ ) ;
107
134
} ) ;
108
135
}
109
136
@@ -117,10 +144,10 @@ export default class ModalPortal extends Component {
117
144
}
118
145
119
146
handleKeyDown = event => {
120
- if ( event . keyCode == TAB_KEY ) {
121
- scopeTab ( this . refs . content , event ) ;
147
+ if ( event . keyCode === TAB_KEY ) {
148
+ scopeTab ( this . content , event ) ;
122
149
}
123
- if ( event . keyCode == ESC_KEY ) {
150
+ if ( event . keyCode === ESC_KEY ) {
124
151
event . preventDefault ( ) ;
125
152
this . requestClose ( event ) ;
126
153
}
@@ -145,53 +172,56 @@ export default class ModalPortal extends Component {
145
172
this . shouldClose = false ;
146
173
}
147
174
148
- requestClose = event => {
149
- if ( this . ownerHandlesClose ( ) ) {
150
- this . props . onRequestClose ( event ) ;
151
- }
152
- }
175
+ requestClose = event =>
176
+ this . ownerHandlesClose ( ) && this . props . onRequestClose ( event ) ;
153
177
154
- ownerHandlesClose = ( ) => {
155
- return this . props . onRequestClose ;
156
- }
178
+ ownerHandlesClose = ( ) =>
179
+ this . props . onRequestClose ;
157
180
158
- shouldBeClosed = ( ) => {
159
- return ! this . state . isOpen && ! this . state . beforeClose ;
160
- }
181
+ shouldBeClosed = ( ) =>
182
+ ! this . state . isOpen && ! this . state . beforeClose ;
161
183
162
- contentHasFocus = ( ) => {
163
- return document . activeElement === this . refs . content || this . refs . content . contains ( document . activeElement ) ;
164
- }
184
+ contentHasFocus = ( ) =>
185
+ document . activeElement === this . content ||
186
+ this . content . contains ( document . activeElement ) ;
165
187
166
188
buildClassName = ( which , additional ) => {
167
189
const classNames = ( typeof additional === 'object' ) ? additional : {
168
190
base : CLASS_NAMES [ which ] ,
169
- afterOpen : CLASS_NAMES [ which ] + " --after-open" ,
170
- beforeClose : CLASS_NAMES [ which ] + " --before-close"
191
+ afterOpen : ` ${ CLASS_NAMES [ which ] } --after-open` ,
192
+ beforeClose : ` ${ CLASS_NAMES [ which ] } --before-close`
171
193
} ;
172
194
let className = classNames . base ;
173
- if ( this . state . afterOpen ) { className += " " + classNames . afterOpen ; }
174
- if ( this . state . beforeClose ) { className += " " + classNames . beforeClose ; }
175
- return ( typeof additional === 'string' && additional ) ? [ className , additional ] . join ( " " ) : className ;
195
+ if ( this . state . afterOpen ) {
196
+ className = `${ className } ${ classNames . afterOpen } ` ;
197
+ }
198
+ if ( this . state . beforeClose ) {
199
+ className = `${ className } ${ classNames . beforeClose } ` ;
200
+ }
201
+ return ( typeof additional === 'string' && additional ) ?
202
+ `${ className } ${ additional } ` : className ;
176
203
}
177
204
178
205
render ( ) {
179
- const contentStyles = this . props . className ? { } : this . props . defaultStyles . content ;
180
- const overlayStyles = this . props . overlayClassName ? { } : this . props . defaultStyles . overlay ;
206
+ const { className, overlayClassName, defaultStyles } = this . props ;
207
+ const contentStyles = className ? { } : defaultStyles . content ;
208
+ const overlayStyles = overlayClassName ? { } : defaultStyles . overlay ;
181
209
182
210
return this . shouldBeClosed ( ) ? < div /> : (
183
- < div ref = "overlay"
184
- className = { this . buildClassName ( 'overlay' , this . props . overlayClassName ) }
185
- style = { { ...overlayStyles , ...this . props . style . overlay } }
186
- onClick = { this . handleOverlayOnClick } >
187
- < div ref = "content"
188
- style = { { ...contentStyles , ...this . props . style . content } }
189
- className = { this . buildClassName ( 'content' , this . props . className ) }
190
- tabIndex = "-1"
191
- onKeyDown = { this . handleKeyDown }
192
- onClick = { this . handleContentOnClick }
193
- role = { this . props . role }
194
- aria-label = { this . props . contentLabel } >
211
+ < div
212
+ ref = { overlay => { this . overlay = overlay ; } }
213
+ className = { this . buildClassName ( 'overlay' , overlayClassName ) }
214
+ style = { { ...overlayStyles , ...this . props . style . overlay } }
215
+ onClick = { this . handleOverlayOnClick } >
216
+ < div
217
+ ref = { content => { this . content = content ; } }
218
+ style = { { ...contentStyles , ...this . props . style . content } }
219
+ className = { this . buildClassName ( 'content' , className ) }
220
+ tabIndex = "-1"
221
+ onKeyDown = { this . handleKeyDown }
222
+ onClick = { this . handleContentOnClick }
223
+ role = { this . props . role }
224
+ aria-label = { this . props . contentLabel } >
195
225
{ this . props . children }
196
226
</ div >
197
227
</ div >
0 commit comments