| 1 |
import { useState, useRef, useEffect } from 'react'; |
| 2 |
import { arrayToClassName } from 'elementor-app/utils/utils.js'; |
| 3 |
|
| 4 |
import Button from 'elementor-app/ui/molecules/button'; |
| 5 |
import Grid from 'elementor-app/ui/grid/grid'; |
| 6 |
import Icon from 'elementor-app/ui/atoms/icon'; |
| 7 |
import Text from 'elementor-app/ui/atoms/text'; |
| 8 |
|
| 9 |
import ModalSection from './modal-section'; |
| 10 |
import ModalTip from './modal-tip'; |
| 11 |
|
| 12 |
import './modal.scss'; |
| 13 |
|
| 14 |
export default function ModalProvider( props ) { |
| 15 |
const [ show, setShow ] = useState( props.show ), |
| 16 |
hideModal = () => { |
| 17 |
setShow( false ); |
| 18 |
|
| 19 |
// The purpose of the props.setShow is to sync an external state with the component inner state. |
| 20 |
if ( props.setShow ) { |
| 21 |
props.setShow( false ); |
| 22 |
} |
| 23 |
}, |
| 24 |
showModal = () => { |
| 25 |
setShow( true ); |
| 26 |
|
| 27 |
// The purpose of the props.setShow is to sync an external state with the component inner state. |
| 28 |
if ( props.setShow ) { |
| 29 |
props.setShow( true ); |
| 30 |
} |
| 31 |
}, |
| 32 |
modalAttrs = { |
| 33 |
...props, |
| 34 |
show, |
| 35 |
hideModal, |
| 36 |
showModal, |
| 37 |
}; |
| 38 |
|
| 39 |
useEffect( () => { |
| 40 |
// Sync with external state. |
| 41 |
setShow( props.show ); |
| 42 |
}, [ props.show ] ); |
| 43 |
|
| 44 |
return ( |
| 45 |
<> |
| 46 |
{ |
| 47 |
props.toggleButtonProps && |
| 48 |
<Button { ...props.toggleButtonProps } onClick={ showModal } /> |
| 49 |
} |
| 50 |
|
| 51 |
<Modal { ...modalAttrs }> |
| 52 |
{ props.children } |
| 53 |
</Modal> |
| 54 |
</> |
| 55 |
); |
| 56 |
} |
| 57 |
|
| 58 |
ModalProvider.propTypes = { |
| 59 |
children: PropTypes.node.isRequired, |
| 60 |
toggleButtonProps: PropTypes.object, |
| 61 |
title: PropTypes.string, |
| 62 |
icon: PropTypes.string, |
| 63 |
show: PropTypes.bool, |
| 64 |
setShow: PropTypes.func, |
| 65 |
onOpen: PropTypes.func, |
| 66 |
onClose: PropTypes.func, |
| 67 |
}; |
| 68 |
|
| 69 |
ModalProvider.defaultProps = { |
| 70 |
show: false, |
| 71 |
}; |
| 72 |
|
| 73 |
ModalProvider.Section = ModalSection; |
| 74 |
ModalProvider.Tip = ModalTip; |
| 75 |
|
| 76 |
export const Modal = ( props ) => { |
| 77 |
const modalRef = useRef( null ), |
| 78 |
closeRef = useRef( null ), |
| 79 |
closeModal = ( e ) => { |
| 80 |
const node = modalRef.current, |
| 81 |
closeNode = closeRef.current, |
| 82 |
isInCloseNode = closeNode && closeNode.contains( e.target ); |
| 83 |
|
| 84 |
// Ignore if click is inside the modal |
| 85 |
if ( node && node.contains( e.target ) && ! isInCloseNode ) { |
| 86 |
return; |
| 87 |
} |
| 88 |
|
| 89 |
props.hideModal(); |
| 90 |
|
| 91 |
if ( props.onClose ) { |
| 92 |
props.onClose( e ); |
| 93 |
} |
| 94 |
}; |
| 95 |
|
| 96 |
useEffect( () => { |
| 97 |
if ( props.show ) { |
| 98 |
document.addEventListener( 'mousedown', closeModal, false ); |
| 99 |
props.onOpen?.(); |
| 100 |
} |
| 101 |
|
| 102 |
return () => document.removeEventListener( 'mousedown', closeModal, false ); |
| 103 |
}, [ props.show ] ); |
| 104 |
|
| 105 |
if ( ! props.show ) { |
| 106 |
return null; |
| 107 |
} |
| 108 |
|
| 109 |
return ( |
| 110 |
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions |
| 111 |
<div className="eps-modal__overlay" onClick={ closeModal }> |
| 112 |
<div className={ arrayToClassName( [ 'eps-modal', props.className ] ) } ref={ modalRef } > |
| 113 |
<Grid container className="eps-modal__header" justify="space-between" alignItems="center"> |
| 114 |
<Grid item> |
| 115 |
<Icon className={ `eps-modal__icon ${ props.icon }` } /> |
| 116 |
<Text className="title" tag="span">{ props.title }</Text> |
| 117 |
</Grid> |
| 118 |
<Grid item> |
| 119 |
<div className="eps-modal__close-wrapper" ref={ closeRef }> |
| 120 |
<Button text={ __( 'Close', 'elementor' ) } hideText icon="eicon-close" onClick={ props.closeModal } /> |
| 121 |
</div> |
| 122 |
</Grid> |
| 123 |
</Grid> |
| 124 |
<div className="eps-modal__body"> |
| 125 |
{ props.children } |
| 126 |
</div> |
| 127 |
</div> |
| 128 |
</div> |
| 129 |
); |
| 130 |
}; |
| 131 |
|
| 132 |
Modal.propTypes = { |
| 133 |
className: PropTypes.string, |
| 134 |
children: PropTypes.any.isRequired, |
| 135 |
title: PropTypes.string.isRequired, |
| 136 |
icon: PropTypes.string, |
| 137 |
show: PropTypes.bool, |
| 138 |
setShow: PropTypes.func, |
| 139 |
hideModal: PropTypes.func, |
| 140 |
showModal: PropTypes.func, |
| 141 |
closeModal: PropTypes.func, |
| 142 |
onOpen: PropTypes.func, |
| 143 |
onClose: PropTypes.func, |
| 144 |
}; |
| 145 |
|
| 146 |
Modal.defaultProps = { |
| 147 |
className: '', |
| 148 |
}; |
| 149 |
|