11import React , { Component } from 'react' ;
2+ import { PropTypes } from 'prop-types' ;
23import * as focusManager from '../helpers/focusManager' ;
3- import { scopeTab } from '../helpers/scopeTab' ;
4+ import scopeTab from '../helpers/scopeTab' ;
45
56// so that our CSS is statically analyzable
67const CLASS_NAMES = {
@@ -19,6 +20,33 @@ export default class ModalPortal extends Component {
1920 }
2021 } ;
2122
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+
2250 constructor ( props ) {
2351 super ( props ) ;
2452
@@ -38,10 +66,6 @@ export default class ModalPortal extends Component {
3866 }
3967 }
4068
41- componentWillUnmount ( ) {
42- clearTimeout ( this . closeTimer ) ;
43- }
44-
4569 componentWillReceiveProps ( newProps ) {
4670 // Focus only needs to be set once when the modal is being opened
4771 if ( ! this . props . isOpen && newProps . isOpen ) {
@@ -59,6 +83,10 @@ export default class ModalPortal extends Component {
5983 }
6084 }
6185
86+ componentWillUnmount ( ) {
87+ clearTimeout ( this . closeTimer ) ;
88+ }
89+
6290 setFocusAfterRender = focus => {
6391 this . focusAfterRender = focus ;
6492 }
@@ -93,17 +121,16 @@ export default class ModalPortal extends Component {
93121 }
94122 }
95123
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 ( ) ;
102126
103127 closeWithTimeout = ( ) => {
104128 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+ ) ;
107134 } ) ;
108135 }
109136
@@ -117,10 +144,10 @@ export default class ModalPortal extends Component {
117144 }
118145
119146 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 ) ;
122149 }
123- if ( event . keyCode == ESC_KEY ) {
150+ if ( event . keyCode === ESC_KEY ) {
124151 event . preventDefault ( ) ;
125152 this . requestClose ( event ) ;
126153 }
@@ -145,53 +172,56 @@ export default class ModalPortal extends Component {
145172 this . shouldClose = false ;
146173 }
147174
148- requestClose = event => {
149- if ( this . ownerHandlesClose ( ) ) {
150- this . props . onRequestClose ( event ) ;
151- }
152- }
175+ requestClose = event =>
176+ this . ownerHandlesClose ( ) && this . props . onRequestClose ( event ) ;
153177
154- ownerHandlesClose = ( ) => {
155- return this . props . onRequestClose ;
156- }
178+ ownerHandlesClose = ( ) =>
179+ this . props . onRequestClose ;
157180
158- shouldBeClosed = ( ) => {
159- return ! this . state . isOpen && ! this . state . beforeClose ;
160- }
181+ shouldBeClosed = ( ) =>
182+ ! this . state . isOpen && ! this . state . beforeClose ;
161183
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 ) ;
165187
166188 buildClassName = ( which , additional ) => {
167189 const classNames = ( typeof additional === 'object' ) ? additional : {
168190 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`
171193 } ;
172194 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 ;
176203 }
177204
178205 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 ;
181209
182210 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 } >
195225 { this . props . children }
196226 </ div >
197227 </ div >
0 commit comments