| 1 |
import {useEffect, useCallback} from 'react'; |
| 2 |
import PropTypes from 'prop-types'; |
| 3 |
import classNames from 'classnames'; |
| 4 |
import LoadingOverlay from '@givewp/components/LoadingOverlay'; |
| 5 |
|
| 6 |
import styles from './styles.module.scss'; |
| 7 |
|
| 8 |
import {__} from '@wordpress/i18n'; |
| 9 |
|
| 10 |
const Modal = ({visible = true, type = 'notice', children = {}, isLoading = false, handleClose = () => {}, ...rest}) => { |
| 11 |
const closeModal = useCallback((event) => { |
| 12 |
if (event.keyCode === 27 && typeof handleClose === 'function') { |
| 13 |
handleClose(); |
| 14 |
} |
| 15 |
}, []); |
| 16 |
|
| 17 |
useEffect(() => { |
| 18 |
document.addEventListener('keydown', closeModal, false); |
| 19 |
|
| 20 |
return () => { |
| 21 |
document.removeEventListener('keydown', closeModal, false); |
| 22 |
}; |
| 23 |
}, []); |
| 24 |
|
| 25 |
const handleOverlayClick = (e) => { |
| 26 |
if (e.target.classList.contains(styles.overlay) && typeof handleClose === 'function') { |
| 27 |
handleClose(); |
| 28 |
} |
| 29 |
}; |
| 30 |
|
| 31 |
const modalStyles = classNames({ |
| 32 |
[styles.modal]: true, |
| 33 |
[styles.error]: type === 'error' || type === 'failed', |
| 34 |
[styles.warning]: type === 'warning', |
| 35 |
[styles.success]: type === 'success', |
| 36 |
[styles.http]: type === 'http', |
| 37 |
}); |
| 38 |
|
| 39 |
return ( |
| 40 |
<div className={classNames({[styles.overlay]: visible})} onClick={handleOverlayClick} {...rest}> |
| 41 |
<div className={styles.container}> |
| 42 |
<div className={modalStyles}> |
| 43 |
{isLoading && <LoadingOverlay spinnerSize="small" />} |
| 44 |
<div className={styles.content}>{children}</div> |
| 45 |
</div> |
| 46 |
</div> |
| 47 |
</div> |
| 48 |
); |
| 49 |
}; |
| 50 |
|
| 51 |
Modal.Title = ({children, ...rest}) => { |
| 52 |
return ( |
| 53 |
<div className={styles.title} {...rest}> |
| 54 |
{children} |
| 55 |
</div> |
| 56 |
); |
| 57 |
}; |
| 58 |
|
| 59 |
Modal.CloseIcon = ({onClick, ...rest}) => { |
| 60 |
return ( |
| 61 |
<div className={styles.closeIconContainer}> |
| 62 |
<div className={styles.close} onClick={onClick} {...rest}> |
| 63 |
<span className="dashicons dashicons-no" /> |
| 64 |
</div> |
| 65 |
</div> |
| 66 |
); |
| 67 |
}; |
| 68 |
|
| 69 |
Modal.Section = ({title, content, ...rest}) => { |
| 70 |
return ( |
| 71 |
<div className={styles.section} {...rest}> |
| 72 |
<strong>{title}:</strong> |
| 73 |
{content} |
| 74 |
</div> |
| 75 |
); |
| 76 |
}; |
| 77 |
|
| 78 |
Modal.Content = ({children, align, ...rest}) => { |
| 79 |
const contentClasses = classNames({ |
| 80 |
[styles.innerContent]: true, |
| 81 |
[styles.textCenter]: align === 'center', |
| 82 |
[styles.textRight]: align === 'right', |
| 83 |
[styles.textLeft]: !align || align === 'left', |
| 84 |
}); |
| 85 |
|
| 86 |
return ( |
| 87 |
<div className={contentClasses} {...rest}> |
| 88 |
{children} |
| 89 |
</div> |
| 90 |
); |
| 91 |
}; |
| 92 |
|
| 93 |
Modal.AdditionalContext = ({type, context, ...rest}) => { |
| 94 |
const title = ['error', 'failed'].includes(type) ? __('Error details', 'give') : __('Additional context', 'give'); |
| 95 |
|
| 96 |
return ( |
| 97 |
<div className={styles.section} {...rest}> |
| 98 |
<strong>{title}:</strong> |
| 99 |
<div className={styles.errorDetailsContainer}> |
| 100 |
<pre> |
| 101 |
{context instanceof Object |
| 102 |
? Object.entries(context).map(([key, value]) => { |
| 103 |
return ( |
| 104 |
<div key={key}> |
| 105 |
<span>{key}:</span> |
| 106 |
{value} |
| 107 |
</div> |
| 108 |
); |
| 109 |
}) |
| 110 |
: context} |
| 111 |
</pre> |
| 112 |
</div> |
| 113 |
</div> |
| 114 |
); |
| 115 |
}; |
| 116 |
|
| 117 |
Modal.propTypes = { |
| 118 |
// Is visible |
| 119 |
visible: PropTypes.bool.isRequired, |
| 120 |
// Is loading |
| 121 |
isLoading: PropTypes.bool, |
| 122 |
// Modal type |
| 123 |
type: PropTypes.string, |
| 124 |
// Collection of react DOM elements |
| 125 |
children: PropTypes.arrayOf(PropTypes.object), |
| 126 |
// Handle close callback |
| 127 |
handleClose: PropTypes.func, |
| 128 |
}; |
| 129 |
|
| 130 |
Modal.Title.propTypes = { |
| 131 |
// Collection of react DOM elements |
| 132 |
children: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.object)]), |
| 133 |
}; |
| 134 |
|
| 135 |
Modal.CloseIcon.propTypes = { |
| 136 |
// On click callback |
| 137 |
onClick: PropTypes.func.isRequired, |
| 138 |
}; |
| 139 |
|
| 140 |
Modal.Section.propTypes = { |
| 141 |
// Section title |
| 142 |
title: PropTypes.string.isRequired, |
| 143 |
// Section content |
| 144 |
content: PropTypes.string.isRequired, |
| 145 |
}; |
| 146 |
|
| 147 |
Modal.Content.propTypes = { |
| 148 |
// Collection of react DOM elements |
| 149 |
children: PropTypes.object, |
| 150 |
}; |
| 151 |
|
| 152 |
Modal.AdditionalContext.propTypes = { |
| 153 |
// Log type |
| 154 |
type: PropTypes.string.isRequired, |
| 155 |
// String or Array of objects |
| 156 |
context: PropTypes.any.isRequired, |
| 157 |
}; |
| 158 |
|
| 159 |
export default Modal; |
| 160 |
|