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,272 @@
$modal_title_color: #cfcfcf;
$modal_highlight_background_color: #f1f1f1;
$modal_background_color: #fff;
$modal_popup_margin: 30px;
$modal_popup_margin_mobile: 10px;
$modal_popup_padding: 30px;
$modal_popup_padding_mobile: 12px;
$modal_close_button_size: 23px;
$overlay_background_color: rgba(0, 0, 0, .6);
body.mailpoet_modal_opened {
overflow: hidden;
}
.mailpoet_modal_overlay {
align-items: center;
background-color: $overlay_background_color;
box-sizing: border-box;
display: flex;
height: 100%;
justify-content: center;
left: 0;
overflow-x: hidden;
overflow-y: auto;
padding: $modal_popup_margin;
position: fixed;
top: 0;
width: 100%;
z-index: 100000;
}
.mailpoet_modal_highlight {
background-color: $modal_highlight_background_color;
box-shadow: 0 0 20px 2px rgba(#fff, 75%);
pointer-events: none;
position: relative;
z-index: 100001 !important;
}
.mailpoet_modal_overlay.mailpoet_overlay_transparent {
background-color: transparent;
}
.mailpoet_modal_overlay.mailpoet_overlay_loading {
background-color: $overlay_background_color !important;
display: flex !important;
}
.mailpoet_popup {
animation: mailpoet_popup_fadein .5s;
margin: auto;
max-width: 100%;
z-index: 25;
}
@keyframes mailpoet_popup_fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.mailpoet_popup_wrapper {
background-color: $modal_background_color;
border-radius: 4px;
box-shadow: 1px 2px 4px #343434;
box-sizing: border-box;
display: flex;
flex-flow: column;
height: 100%;
overflow: hidden;
padding: $modal_popup_padding;
position: relative;
width: 100%;
z-index: 0;
}
.mailpoet_overlay_transparent .mailpoet_popup_wrapper {
border: 1px solid #333;
}
.mailpoet_popup_title h2 {
font-size: 23px;
font-weight: 600;
line-height: 29px;
margin: 0 ($modal_close_button_size + 20) 0 0;
}
.mailpoet_popup_body {
flex-grow: 1;
margin-top: 20px;
position: relative;
.button + .button {
margin-left: 10px;
}
}
.mailpoet_popup_has_title .mailpoet_popup_body {
margin-top: 30px;
}
.mailpoet_modal_overlay.mailpoet_panel_overlay {
overflow: hidden;
top: 32px;
}
.mailpoet_panel {
bottom: 0;
display: none;
margin: 0;
padding: 0;
position: fixed;
top: 0;
transition: margin 350ms ease-out;
width: 100%;
z-index: 100002;
}
.mailpoet_panel_wrapper {
background-color: #f1f1f1;
border: 1px solid #e1e1e1;
border-top: 0 none;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
top: 0;
width: 100%;
z-index: 0;
}
.mailpoet_panel_title {
height: 0;
margin: 0;
padding: 0;
position: relative;
}
.mailpoet_panel_title h2 {
border-left: 1px solid #444;
border-right: 1px solid #444;
color: $modal_title_color;
font-family: 'Lucida Grande', Verdana, Arial, sans-serif;
font-size: 1em;
font-weight: normal;
line-height: 32px;
margin: 0;
padding: 0 30px 0 10px;
}
.mailpoet_panel_body {
padding: 10px 10px 36px;
}
.mailpoet_modal_close {
cursor: pointer;
outline: 0 none;
overflow: hidden;
padding: 0;
position: absolute;
z-index: 2;
svg {
opacity: .5;
stroke: #979797;
&:hover {
stroke: #636363;
}
}
}
.mailpoet_popup .mailpoet_modal_close {
height: $modal_close_button_size;
padding: 3px 0;
right: $modal_popup_padding;
top: $modal_popup_padding;
width: $modal_close_button_size;
}
.mailpoet_panel .mailpoet_modal_close {
height: 16px;
padding: 2px 0;
right: 20px;
top: 20px;
width: 16px;
}
.mailpoet_modal_close:focus {
outline: 0 none;
}
.mailpoet_align_left {
margin: 0;
text-align: left;
}
.mailpoet_align_center {
margin: 0;
text-align: center;
}
.mailpoet_align_right {
margin: 0;
text-align: right;
}
@media screen and (max-width: 782px) {
.mailpoet_modal_overlay {
padding: $modal_popup_margin_mobile;
}
.mailpoet_popup {
min-width: auto !important;
width: 100%;
}
.mailpoet_popup_wrapper {
padding: $modal_popup_padding_mobile;
}
.mailpoet_popup_title h2 {
margin-right: $modal_close_button_size + 10;
}
.mailpoet_popup .mailpoet_modal_close {
right: $modal_popup_padding_mobile;
top: $modal_popup_padding_mobile;
}
.mailpoet_modal_overlay.mailpoet_panel_overlay {
top: 46px;
}
.mailpoet_panel_body {
padding-bottom: 52px;
}
}
.mailpoet_loading {
display: flex;
flex-direction: row;
height: 32px;
width: 150px;
}
.mailpoet_modal_loading {
animation-direction: linear;
animation-duration: 1.9500000000000002s;
animation-iteration-count: infinite;
animation-name: bounce_mailpoet_modal_loading;
background-color: #e01d4e;
border-radius: 21px;
height: 32px;
margin-left: 17px;
width: 32px;
}
.mailpoet_modal_loading_1 {
animation-delay: .39s;
}
.mailpoet_modal_loading_2 {
animation-delay: .9099999999999999s;
}
.mailpoet_modal_loading_3 {
animation-delay: 1.1700000000000002s;
}
@keyframes bounce_mailpoet_modal_loading {
0%,
50% { background-color: #064e6d; }
}

View File

@@ -1,272 +1,157 @@
$modal_title_color: #cfcfcf; $modal-min-width: 360px;
$modal_highlight_background_color: #f1f1f1; $modal-screen-overlay-z-index: 100000;
$modal_background_color: #fff; $modal-header-z-index: 10;
$modal_popup_margin: 30px; $border-width: 1px;
$modal_popup_margin_mobile: 10px; $light-gray-500: #e2e4e7;
$modal_popup_padding: 30px; $dark-gray-900: #191e23;
$modal_popup_padding_mobile: 12px; $shadow-modal: 0 3px 30px rgba($dark-gray-900, .2);
$modal_close_button_size: 23px;
$overlay_background_color: rgba(0, 0, 0, .6);
body.mailpoet_modal_opened { $grid-size: 8px;
overflow: hidden; $grid-size-large: 16px;
$grid-size-xlarge: 24px;
$header-height: 56px;
$icon-button-size: 36px;
#mailpoet_modal {
display: block !important;
} }
.mailpoet_modal_overlay { .mailpoet-modal-screen-overlay {
align-items: center; animation: edit-post__fade-in-animation .2s ease-out 0s;
background-color: $overlay_background_color; animation-duration: 100ms;
box-sizing: border-box; animation-fill-mode: forwards;
display: flex; background-color: rgba(#000, .7);
height: 100%;
justify-content: center;
left: 0;
overflow-x: hidden;
overflow-y: auto;
padding: $modal_popup_margin;
position: fixed;
top: 0;
width: 100%;
z-index: 100000;
}
.mailpoet_modal_highlight {
background-color: $modal_highlight_background_color;
box-shadow: 0 0 20px 2px rgba(#fff, 75%);
pointer-events: none;
position: relative;
z-index: 100001 !important;
}
.mailpoet_modal_overlay.mailpoet_overlay_transparent {
background-color: transparent;
}
.mailpoet_modal_overlay.mailpoet_overlay_loading {
background-color: $overlay_background_color !important;
display: flex !important;
}
.mailpoet_popup {
animation: mailpoet_popup_fadein .5s;
margin: auto;
max-width: 100%;
z-index: 25;
}
@keyframes mailpoet_popup_fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.mailpoet_popup_wrapper {
background-color: $modal_background_color;
border-radius: 4px;
box-shadow: 1px 2px 4px #343434;
box-sizing: border-box;
display: flex;
flex-flow: column;
height: 100%;
overflow: hidden;
padding: $modal_popup_padding;
position: relative;
width: 100%;
z-index: 0;
}
.mailpoet_overlay_transparent .mailpoet_popup_wrapper {
border: 1px solid #333;
}
.mailpoet_popup_title h2 {
font-size: 23px;
font-weight: 600;
line-height: 29px;
margin: 0 ($modal_close_button_size + 20) 0 0;
}
.mailpoet_popup_body {
flex-grow: 1;
margin-top: 20px;
position: relative;
.button + .button {
margin-left: 10px;
}
}
.mailpoet_popup_has_title .mailpoet_popup_body {
margin-top: 30px;
}
.mailpoet_modal_overlay.mailpoet_panel_overlay {
overflow: hidden;
top: 32px;
}
.mailpoet_panel {
bottom: 0; bottom: 0;
display: none; left: 0;
margin: 0;
padding: 0;
position: fixed; position: fixed;
right: 0;
top: 0; top: 0;
transition: margin 350ms ease-out; z-index: $modal-screen-overlay-z-index;
width: 100%;
z-index: 100002;
} }
.mailpoet_panel_wrapper { .mailpoet-modal-frame {
background-color: #f1f1f1; background: white;
border: 1px solid #e1e1e1; border: $border-width solid $light-gray-500;
border-top: 0 none; bottom: 0;
height: 100%; box-shadow: $shadow-modal;
overflow-x: hidden; box-sizing: border-box;
overflow-y: auto; left: 0;
top: 0;
width: 100%;
z-index: 0;
}
.mailpoet_panel_title {
height: 0;
margin: 0; margin: 0;
padding: 0; overflow: auto;
position: relative; // On small screens the content needs to be full width because of limited
} // space.
.mailpoet_panel_title h2 {
border-left: 1px solid #444;
border-right: 1px solid #444;
color: $modal_title_color;
font-family: 'Lucida Grande', Verdana, Arial, sans-serif;
font-size: 1em;
font-weight: normal;
line-height: 32px;
margin: 0;
padding: 0 30px 0 10px;
}
.mailpoet_panel_body {
padding: 10px 10px 36px;
}
.mailpoet_modal_close {
cursor: pointer;
outline: 0 none;
overflow: hidden;
padding: 0;
position: absolute; position: absolute;
z-index: 2; right: 0;
top: 0;
svg { // Show a centered modal on bigger screens.
opacity: .5;
stroke: #979797;
&:hover { @media screen and (min-width: 600px) {
stroke: #636363; // Animate the modal frame/contents appearing on the page.
} animation: mailpoet-modal-appear-animation .1s ease-out;
animation-duration: 100ms;
animation-fill-mode: forwards;
bottom: auto;
left: 50%;
max-height: calc(100% - #{ $header-height } - #{ $header-height });
max-width: calc(100% - #{ $grid-size-large } - #{ $grid-size-large });
min-width: $modal-min-width;
right: auto;
top: 50%;
transform: translate(-50%, -50%);
} }
} }
.mailpoet_popup .mailpoet_modal_close { @keyframes mailpoet-modal-appear-animation {
height: $modal_close_button_size; from {
padding: 3px 0; margin-top: $grid-size * 4;
right: $modal_popup_padding;
top: $modal_popup_padding;
width: $modal_close_button_size;
}
.mailpoet_panel .mailpoet_modal_close {
height: 16px;
padding: 2px 0;
right: 20px;
top: 20px;
width: 16px;
}
.mailpoet_modal_close:focus {
outline: 0 none;
}
.mailpoet_align_left {
margin: 0;
text-align: left;
}
.mailpoet_align_center {
margin: 0;
text-align: center;
}
.mailpoet_align_right {
margin: 0;
text-align: right;
}
@media screen and (max-width: 782px) {
.mailpoet_modal_overlay {
padding: $modal_popup_margin_mobile;
} }
.mailpoet_popup { to {
min-width: auto !important; margin-top: 0;
}
}
// Fix header to the top so it is always there to provide context to the modal
// if the content needs to be scrolled (for example, on the keyboard shortcuts
// modal screen).
.mailpoet-modal-header {
align-items: center;
background: white;
box-sizing: border-box;
display: flex;
flex-direction: row;
height: $header-height;
justify-content: space-between;
margin: 0 -#{$grid-size-xlarge} $grid-size-xlarge;
padding: 0 $grid-size-xlarge;
// For z-index to take effect, the element must be positioned. A "sticky"
// element is positioned, but since this is not supported in IE11,
// "relative" is used as a fallback.
position: relative;
position: sticky;
top: 0;
z-index: $modal-header-z-index;
// Rules inside this query are only run by Microsoft Edge.
// Edge has bugs around position: sticky;, so it needs a separate top rule.
// See also https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/17555420/.
@supports (-ms-ime-align:auto) {
position: fixed;
width: 100%; width: 100%;
} }
.mailpoet_popup_wrapper { .mailpoet-modal-header-heading {
padding: $modal_popup_padding_mobile; font-size: 1rem;
font-weight: 600;
} }
.mailpoet_popup_title h2 { h1 {
margin-right: $modal_close_button_size + 10; line-height: 1;
margin: 0;
} }
.mailpoet_popup .mailpoet_modal_close { .components-button {
right: $modal_popup_padding_mobile; left: $grid-size;
top: $modal_popup_padding_mobile; position: relative;
} }
.mailpoet_modal_overlay.mailpoet_panel_overlay { .mailpoet-modal-close svg {
top: 46px; opacity: .5;
} stroke: $dark-gray-900;
.mailpoet_panel_body {
padding-bottom: 52px;
} }
} }
.mailpoet_loading { .mailpoet-modal-header-heading-container {
align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 32px; flex-grow: 1;
width: 150px; justify-content: left;
} }
.mailpoet_modal_loading { .mailpoet-modal-header-icon-container {
animation-direction: linear; display: inline-block;
animation-duration: 1.9500000000000002s;
animation-iteration-count: infinite; svg {
animation-name: bounce_mailpoet_modal_loading; max-height: $icon-button-size;
background-color: #e01d4e; max-width: $icon-button-size;
border-radius: 21px; padding: $grid-size;
height: 32px; }
margin-left: 17px;
width: 32px;
} }
.mailpoet_modal_loading_1 { // Modal contents.
animation-delay: .39s; .mailpoet-modal-content {
} box-sizing: border-box;
height: 100%;
padding: 0 $grid-size-xlarge $grid-size-xlarge;
.mailpoet_modal_loading_2 { // Rules inside this query are only run by Microsoft Edge.
animation-delay: .9099999999999999s; // This is a companion top padding to the fixed rule in line 77.
}
.mailpoet_modal_loading_3 { @supports (-ms-ime-align:auto) {
animation-delay: 1.1700000000000002s; padding-top: $header-height;
} }
@keyframes bounce_mailpoet_modal_loading {
0%,
50% { background-color: #064e6d; }
} }

View File

@@ -5,6 +5,7 @@
@import 'components/datepicker/datepicker'; @import 'components/datepicker/datepicker';
@import 'components/dynamicSegments'; @import 'components/dynamicSegments';
@import 'components/common'; @import 'components/common';
@import 'components/legacyModal';
@import 'components/modal'; @import 'components/modal';
@import 'components/notice'; @import 'components/notice';
@import 'components/listing'; @import 'components/listing';

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;

View File

@@ -167,3 +167,4 @@ jQuery('.toplevel_page_mailpoet-newsletters.menu-top-last')
Parsley.setLocale('mailpoet'); Parsley.setLocale('mailpoet');
</script> </script>
<% block after_javascript %><% endblock %> <% block after_javascript %><% endblock %>
<div id="mailpoet_modal"></div>