UNPKG

wix-style-react

Version:
232 lines (218 loc) • 7.7 kB
import React from 'react'; import PropTypes from 'prop-types'; import ReactModal from 'react-modal'; import X from 'wix-ui-icons-common/X'; import defaultTo from 'lodash/defaultTo'; import { st, classes } from './Modal.st.css'; import { flexPositions } from './constants'; import { ZIndex } from '../ZIndex'; import { FontUpgradeContext } from '../FontUpgrade/context'; import FontUpgrade from '../FontUpgrade'; import { ThemeProviderConsumerBackwardCompatible } from '../ThemeProvider/ThemeProviderConsumerBackwardCompatible'; import uniqueId from 'lodash/uniqueId'; class Modal extends React.PureComponent { constructor(props) { super(props); this.CHILDREN_WRAPPER_DIV_ID = uniqueId('wsr-modal'); } static propTypes = { /** Applied as data-hook HTML attribute that can be used to create driver in testing */ dataHook: PropTypes.string, /** Controls if modal is open or closed */ isOpen: PropTypes.bool.isRequired, /** Border radius of modal */ borderRadius: PropTypes.number, /** a11y: The value of contentLabel is set as an aria-label on the modal element. This helps assistive technology, like screen readers, to add a label to an element that would otherwise be anonymous */ contentLabel: PropTypes.string, /** Renders modal content */ children: PropTypes.any, /** Controls z-index of the modal overlay */ zIndex: PropTypes.number, /** Enables to close modal when mouse clicked on overlay area */ shouldCloseOnOverlayClick: PropTypes.bool, /** Displays a close button on the top right corner of the overlay */ shouldDisplayCloseButton: PropTypes.bool, /** Callback that will be executed when the modal is requested to be closed, prior to actually closing */ onRequestClose: PropTypes.func, /** Callback that will be executed after the modal has been opened */ onAfterOpen: PropTypes.func, /** Horizontal position of the modal */ horizontalPosition: PropTypes.oneOf(['start', 'center', 'end']), /** Vertical position of the modal */ verticalPosition: PropTypes.oneOf(['start', 'center', 'end']), /** Number indicating the milliseconds to wait before closing the modal */ closeTimeoutMS: PropTypes.number, /** Specifies if modal portal supports scroll */ scrollable: PropTypes.bool, /** Specifies if modal content should become scrollable when modal size will fit the window */ scrollableContent: PropTypes.bool, /** Sets the maximum height for a scrollable content */ maxHeight: PropTypes.string, /** Sets the height for modal's content container */ height: PropTypes.string, /** css position of the modal overlay */ overlayPosition: PropTypes.oneOf([ 'static', 'relative', 'absolute', 'fixed', 'sticky', ]), /** A function that returns a DOM element on which the modal should be appended to */ parentSelector: PropTypes.func, /** Selector specifying where to apply the aria-hidden attribute */ appElement: PropTypes.string, /** Specifies minimum spacing between full viewport and modal content */ screen: PropTypes.oneOf(['full', 'desktop', 'mobile']), }; static defaultProps = { borderRadius: 0, shouldCloseOnOverlayClick: false, shouldDisplayCloseButton: false, horizontalPosition: 'center', verticalPosition: 'center', closeTimeoutMS: 500, scrollable: true, scrollableContent: false, height: '100%', maxHeight: 'auto', overlayPosition: 'fixed', screen: 'full', }; render() { const { dataHook, horizontalPosition, verticalPosition, height, scrollableContent, borderRadius, zIndex, scrollable, isOpen, shouldCloseOnOverlayClick, shouldDisplayCloseButton, onRequestClose, onAfterOpen, contentLabel, closeTimeoutMS, children, appElement, overlayPosition, parentSelector, screen, } = this.props; let { maxHeight } = this.props; const justifyContent = flexPositions[horizontalPosition]; const alignItems = flexPositions[verticalPosition]; maxHeight = scrollableContent && maxHeight === 'auto' ? '100vh' : maxHeight; const modalStyles = { overlay: { // Overriding defaults position: overlayPosition, top: 0, left: 0, right: 0, bottom: 0, zIndex: defaultTo(zIndex, ZIndex('Modal')), backgroundColor: null, // null disables the property, use css instead // Overriding defaults - END display: 'flex', justifyContent, alignItems, overflowY: scrollable ? 'auto' : 'hidden', }, content: { // Overriding defaults border: 'none', overflowY: scrollableContent ? 'auto' : 'initial', overflowX: scrollableContent ? 'hidden' : 'initial', height, maxHeight, width: '100%', WebkitOverflowScrolling: 'touch', outline: 'none', borderRadius, padding: '0px', // Overriding defaults - END backgroundColor: 'transparent', marginBottom: '0px', position: 'relative', }, }; if (appElement) { ReactModal.setAppElement(appElement); } else { ReactModal.setAppElement('body'); } return ( <div data-hook={dataHook}> <FontUpgradeContext.Consumer> {({ active }) => { return ( <ThemeProviderConsumerBackwardCompatible> {({ className: themeClassName }) => ( <ReactModal portalClassName={st( classes.root, { scrollable }, `portal portal-${dataHook}`, themeClassName, )} isOpen={isOpen} shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} onRequestClose={onRequestClose} onAfterOpen={onAfterOpen} style={modalStyles} className={classes.modal} contentLabel={contentLabel} closeTimeoutMS={closeTimeoutMS} parentSelector={parentSelector} > <FontUpgrade active={!!active}> {isOpen && shouldDisplayCloseButton && this.renderCloseButton()} <div data-scrollable={scrollable || null} id={this.CHILDREN_WRAPPER_DIV_ID} className={st(classes.childrenContainer, { screen, })} onClick={this.handleOverlayClick} > {children} </div> </FontUpgrade> </ReactModal> )} </ThemeProviderConsumerBackwardCompatible> ); }} </FontUpgradeContext.Consumer> </div> ); } handleOverlayClick = event => { const { shouldCloseOnOverlayClick, onRequestClose } = this.props; if ( shouldCloseOnOverlayClick && event.target.id === this.CHILDREN_WRAPPER_DIV_ID && onRequestClose ) { onRequestClose(); } }; renderCloseButton = () => { return ( <div onClick={this.props.onRequestClose} className={classes.closeButton} data-hook="modal-close-button" > <X size="18px" /> </div> ); }; } export default Modal;