Add modal component

[MAILPOET-2602]
This commit is contained in:
Pavel Dohnal
2020-02-06 13:14:56 +01:00
committed by Jack Kitterhing
parent 176ecef4f7
commit 38a62850fc
8 changed files with 651 additions and 234 deletions

View 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>
);

View 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;

View 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;

View 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;