Add modal component
[MAILPOET-2602]
This commit is contained in:
committed by
Jack Kitterhing
parent
176ecef4f7
commit
38a62850fc
8
assets/js/src/common/modal/close_icon.jsx
Normal file
8
assets/js/src/common/modal/close_icon.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Path, SVG } from '@wordpress/components';
|
||||
|
||||
export default (
|
||||
<SVG viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
|
||||
<Path d="M21.454 1.546L1.546 21.454M1.546 1.546L21.454 21.454" strokeWidth="3" strokeLinecap="round" />
|
||||
</SVG>
|
||||
);
|
104
assets/js/src/common/modal/frame.jsx
Normal file
104
assets/js/src/common/modal/frame.jsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ESCAPE = 27;
|
||||
|
||||
function ModalFrame({
|
||||
shouldCloseOnClickOutside,
|
||||
onRequestClose,
|
||||
shouldCloseOnEsc,
|
||||
overlayClassName,
|
||||
contentLabel,
|
||||
aria: { describedby, labelledby },
|
||||
children,
|
||||
className,
|
||||
role,
|
||||
style,
|
||||
}) {
|
||||
function onClose(event) {
|
||||
if (onRequestClose) {
|
||||
onRequestClose(event);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFocusOutside(event) {
|
||||
if (shouldCloseOnClickOutside) {
|
||||
onClose(event);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEscapeKeyDown(event) {
|
||||
if (shouldCloseOnEsc) {
|
||||
event.stopPropagation();
|
||||
onClose(event);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.keyCode === ESCAPE) {
|
||||
handleEscapeKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'mailpoet-modal-screen-overlay',
|
||||
overlayClassName
|
||||
)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={handleFocusOutside}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
>
|
||||
<div
|
||||
className={classnames(
|
||||
'mailpoet-modal-frame',
|
||||
className
|
||||
)}
|
||||
style={style}
|
||||
role={role}
|
||||
aria-label={contentLabel}
|
||||
aria-labelledby={contentLabel ? null : labelledby}
|
||||
aria-describedby={describedby}
|
||||
tabIndex="-1"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ModalFrame.propTypes = {
|
||||
onRequestClose: PropTypes.func,
|
||||
shouldCloseOnEsc: PropTypes.bool,
|
||||
shouldCloseOnClickOutside: PropTypes.bool,
|
||||
role: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
contentLabel: PropTypes.string,
|
||||
overlayClassName: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
aria: PropTypes.shape({
|
||||
describedby: PropTypes.string,
|
||||
labelledby: PropTypes.string,
|
||||
}),
|
||||
};
|
||||
|
||||
ModalFrame.defaultProps = {
|
||||
onRequestClose: () => {},
|
||||
role: 'dialog',
|
||||
shouldCloseOnEsc: true,
|
||||
shouldCloseOnClickOutside: true,
|
||||
className: '',
|
||||
style: {},
|
||||
aria: {
|
||||
describedby: '',
|
||||
labelledby: '',
|
||||
},
|
||||
contentLabel: null,
|
||||
overlayClassName: '',
|
||||
};
|
||||
|
||||
export default ModalFrame;
|
58
assets/js/src/common/modal/header.jsx
Normal file
58
assets/js/src/common/modal/header.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from '@wordpress/components';
|
||||
|
||||
import closeIcon from './close_icon.jsx';
|
||||
|
||||
const ModalHeader = ({
|
||||
icon,
|
||||
title,
|
||||
onClose,
|
||||
closeLabel,
|
||||
headingId,
|
||||
isDismissible,
|
||||
}) => (
|
||||
<div className="mailpoet-modal-header">
|
||||
<div className="mailpoet-modal-header-heading-container">
|
||||
{ icon && (
|
||||
<span
|
||||
className="mailpoet-modal-icon-container"
|
||||
aria-hidden
|
||||
>
|
||||
{ icon }
|
||||
</span>
|
||||
) }
|
||||
{ title && (
|
||||
<h1
|
||||
id={headingId}
|
||||
className="mailpoet-modal-header-heading"
|
||||
>
|
||||
{ title }
|
||||
</h1>
|
||||
) }
|
||||
</div>
|
||||
{ isDismissible && (
|
||||
<Button onClick={onClose} icon={closeIcon} label={closeLabel} className="mailpoet-modal-close" />
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
|
||||
ModalHeader.propTypes = {
|
||||
title: PropTypes.string,
|
||||
headingId: PropTypes.string,
|
||||
onClose: PropTypes.func,
|
||||
closeLabel: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
isDismissible: PropTypes.bool,
|
||||
};
|
||||
|
||||
ModalHeader.defaultProps = {
|
||||
title: null,
|
||||
headingId: 'heading-id',
|
||||
onClose: () => {},
|
||||
closeLabel: '',
|
||||
icon: null,
|
||||
isDismissible: true,
|
||||
};
|
||||
|
||||
export default ModalHeader;
|
88
assets/js/src/common/modal/modal.jsx
Normal file
88
assets/js/src/common/modal/modal.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ModalFrame from './frame.jsx';
|
||||
import ModalHeader from './header.jsx';
|
||||
|
||||
function Modal({
|
||||
onRequestClose,
|
||||
title,
|
||||
icon,
|
||||
closeButtonLabel,
|
||||
displayTitle,
|
||||
children,
|
||||
aria,
|
||||
isDismissible,
|
||||
shouldCloseOnEsc,
|
||||
shouldCloseOnClickOutside,
|
||||
role,
|
||||
contentClassName,
|
||||
contentLabel,
|
||||
overlayClassName,
|
||||
}) {
|
||||
const headingId = aria.labelledby || 'components-modal-header';
|
||||
|
||||
return createPortal(
|
||||
<ModalFrame
|
||||
onRequestClose={onRequestClose}
|
||||
aria={{
|
||||
labelledby: title ? headingId : null,
|
||||
describedby: aria.describedby,
|
||||
}}
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
shouldCloseOnClickOutside={shouldCloseOnClickOutside}
|
||||
role={role}
|
||||
className={contentClassName}
|
||||
contentLabel={contentLabel}
|
||||
overlayClassName={overlayClassName}
|
||||
>
|
||||
<div
|
||||
className="mailpoet-modal-content"
|
||||
role="document"
|
||||
>
|
||||
{
|
||||
displayTitle && (
|
||||
<ModalHeader
|
||||
closeLabel={closeButtonLabel}
|
||||
headingId={headingId}
|
||||
icon={icon}
|
||||
isDismissible={isDismissible}
|
||||
onClose={onRequestClose}
|
||||
title={title}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{ children }
|
||||
</div>
|
||||
</ModalFrame>,
|
||||
document.getElementById('mailpoet_modal')
|
||||
);
|
||||
}
|
||||
|
||||
Modal.propTypes = {
|
||||
title: PropTypes.string,
|
||||
onRequestClose: PropTypes.func,
|
||||
displayTitle: PropTypes.bool,
|
||||
focusOnMount: PropTypes.bool,
|
||||
shouldCloseOnEsc: PropTypes.bool,
|
||||
shouldCloseOnClickOutside: PropTypes.bool,
|
||||
role: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
};
|
||||
|
||||
Modal.defaultProps = {
|
||||
bodyOpenClassName: 'modal-open',
|
||||
onRequestClose: () => {},
|
||||
role: 'dialog',
|
||||
title: null,
|
||||
icon: null,
|
||||
aria: {},
|
||||
focusOnMount: true,
|
||||
shouldCloseOnEsc: true,
|
||||
shouldCloseOnClickOutside: true,
|
||||
isDismissible: true,
|
||||
displayTitle: true,
|
||||
};
|
||||
|
||||
export default Modal;
|
Reference in New Issue
Block a user