Use TypeScript for modal

[MAILPOET-2777]
This commit is contained in:
Jan Jakeš
2020-05-28 17:31:28 +02:00
committed by Veljko V
parent 73784d588e
commit 8b6f419426
7 changed files with 49 additions and 53 deletions

View File

@@ -1,13 +1,19 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types';
type Props = {
fullScreen?: boolean,
role?: string,
className?: string,
children: React.ReactNode,
};
function ModalFrame({ function ModalFrame({
children, children,
className, className,
role, role,
fullScreen, fullScreen,
}) { }: Props) {
return ( return (
<div <div
className={classnames( className={classnames(
@@ -16,20 +22,13 @@ function ModalFrame({
className className
)} )}
role={role} role={role}
tabIndex="-1" tabIndex={-1}
> >
{children} {children}
</div> </div>
); );
} }
ModalFrame.propTypes = {
fullScreen: PropTypes.bool,
role: PropTypes.string,
className: PropTypes.string,
children: PropTypes.node.isRequired,
};
ModalFrame.defaultProps = { ModalFrame.defaultProps = {
role: 'dialog', role: 'dialog',
fullScreen: false, fullScreen: false,

View File

@@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Heading from 'common/typography/heading/heading'; import Heading from 'common/typography/heading/heading';
const ModalHeader = ({ title }) => ( type Props = {
title: string,
};
const ModalHeader = ({ title }: Props) => (
<div className="mailpoet-modal-header"> <div className="mailpoet-modal-header">
<Heading level={3}> <Heading level={3}>
{ title } { title }
@@ -11,8 +14,4 @@ const ModalHeader = ({ title }) => (
</div> </div>
); );
ModalHeader.propTypes = {
title: PropTypes.string.isRequired,
};
export default ModalHeader; export default ModalHeader;

View File

@@ -1,11 +1,23 @@
import React from 'react'; import React from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';
import ModalFrame from './frame.jsx'; import ModalFrame from './frame';
import ModalHeader from './header.jsx'; import ModalHeader from './header';
import ModalOverlay from './overlay.jsx'; import ModalOverlay from './overlay';
import closeIcon from './close_icon'; import ModalCloseIcon from './close_icon';
type Props = {
isDismissible?: boolean,
contentClassName?: string,
overlayClassName?: string,
title?: string,
onRequestClose?: () => void,
fullScreen?: boolean,
shouldCloseOnEsc?: boolean,
shouldCloseOnClickOutside?: boolean,
role?: string,
children: React.ReactNode,
};
function Modal({ function Modal({
onRequestClose, onRequestClose,
@@ -18,7 +30,7 @@ function Modal({
contentClassName, contentClassName,
overlayClassName, overlayClassName,
fullScreen, fullScreen,
}) { }: Props) {
return createPortal( return createPortal(
<ModalOverlay <ModalOverlay
onRequestClose={onRequestClose} onRequestClose={onRequestClose}
@@ -35,7 +47,7 @@ function Modal({
<ModalHeader title={title} /> <ModalHeader title={title} />
) } ) }
{ isDismissible && ( { isDismissible && (
<button type="button" onClick={onRequestClose} className="mailpoet-modal-close">{closeIcon}</button> <button type="button" onClick={onRequestClose} className="mailpoet-modal-close">{ModalCloseIcon}</button>
) } ) }
<div <div
className="mailpoet-modal-content" className="mailpoet-modal-content"
@@ -49,19 +61,6 @@ function Modal({
); );
} }
Modal.propTypes = {
children: PropTypes.node,
isDismissible: PropTypes.bool,
contentClassName: PropTypes.string,
overlayClassName: PropTypes.string,
title: PropTypes.string,
onRequestClose: PropTypes.func,
fullScreen: PropTypes.bool,
shouldCloseOnEsc: PropTypes.bool,
shouldCloseOnClickOutside: PropTypes.bool,
role: PropTypes.string,
};
Modal.defaultProps = { Modal.defaultProps = {
bodyOpenClassName: 'modal-open', bodyOpenClassName: 'modal-open',
onRequestClose: () => {}, onRequestClose: () => {},

View File

@@ -1,16 +1,23 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types';
const ESCAPE = 27; const ESCAPE = 27;
type Props = {
onRequestClose?: (event: React.SyntheticEvent) => void,
shouldCloseOnEsc?: boolean,
shouldCloseOnClickOutside?: boolean,
className?: string,
children: React.ReactNode,
};
function ModalOverlay({ function ModalOverlay({
onRequestClose, onRequestClose,
shouldCloseOnEsc, shouldCloseOnEsc,
shouldCloseOnClickOutside, shouldCloseOnClickOutside,
className, className,
children, children,
}) { }: Props) {
const overlayRef = useRef(null); const overlayRef = useRef(null);
// get focus on render so keys such as ESC work immediately // get focus on render so keys such as ESC work immediately
@@ -18,27 +25,27 @@ function ModalOverlay({
overlayRef.current.focus(); overlayRef.current.focus();
}, []); }, []);
function onClose(event) { function onClose(event: React.SyntheticEvent) {
if (onRequestClose) { if (onRequestClose) {
onRequestClose(event); onRequestClose(event);
} }
} }
function handleFocusOutside(event) { function handleFocusOutside(event: React.MouseEvent) {
// filter only to clicks on overlay // filter only to clicks on overlay
if (shouldCloseOnClickOutside && overlayRef.current === event.target) { if (shouldCloseOnClickOutside && overlayRef.current === event.target) {
onClose(event); onClose(event);
} }
} }
function handleEscapeKeyDown(event) { function handleEscapeKeyDown(event: React.KeyboardEvent) {
if (shouldCloseOnEsc) { if (shouldCloseOnEsc) {
event.stopPropagation(); event.stopPropagation();
onClose(event); onClose(event);
} }
} }
function handleKeyDown(event) { function handleKeyDown(event: React.KeyboardEvent) {
if (event.keyCode === ESCAPE) { if (event.keyCode === ESCAPE) {
handleEscapeKeyDown(event); handleEscapeKeyDown(event);
} }
@@ -54,21 +61,13 @@ function ModalOverlay({
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onClick={handleFocusOutside} onClick={handleFocusOutside}
role="button" role="button"
tabIndex="0" tabIndex={0}
> >
{children} {children}
</div> </div>
); );
} }
ModalOverlay.propTypes = {
onRequestClose: PropTypes.func,
shouldCloseOnEsc: PropTypes.bool,
shouldCloseOnClickOutside: PropTypes.bool,
className: PropTypes.string,
children: PropTypes.node.isRequired,
};
ModalOverlay.defaultProps = { ModalOverlay.defaultProps = {
onRequestClose: () => {}, onRequestClose: () => {},
shouldCloseOnEsc: true, shouldCloseOnEsc: true,

View File

@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import ReactStringReplace from 'react-string-replace'; import ReactStringReplace from 'react-string-replace';
import jQuery from 'jquery'; import jQuery from 'jquery';
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
import Modal from 'common/modal/modal.jsx'; import Modal from 'common/modal/modal';
import { GlobalContext } from 'context'; import { GlobalContext } from 'context';
const mailPoetApiVersion = (window as any).mailpoet_api_version as string; const mailPoetApiVersion = (window as any).mailpoet_api_version as string;

View File

@@ -8,7 +8,7 @@ import { Spinner, SelectControl } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data'; import { useDispatch, useSelect } from '@wordpress/data';
import Preview from 'common/preview/preview.jsx'; import Preview from 'common/preview/preview.jsx';
import Modal from 'common/modal/modal.jsx'; import Modal from 'common/modal/modal';
import BelowPostsSettings from './below_posts_settings'; import BelowPostsSettings from './below_posts_settings';
import PopUpSettings from './popup_settings'; import PopUpSettings from './popup_settings';
import OtherSettings from './other_settings'; import OtherSettings from './other_settings';