2018-03-08 19:28:43 +00:00
|
|
|
import React, { PureComponent } from 'react';
|
2018-04-16 23:30:58 +00:00
|
|
|
import { createPortal } from 'react-dom';
|
2018-03-08 19:28:43 +00:00
|
|
|
import ModalBody from './ModalBody';
|
2018-03-23 16:41:47 +00:00
|
|
|
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
2018-03-08 19:28:43 +00:00
|
|
|
import './index.scss';
|
|
|
|
|
|
|
|
export interface IButton {
|
|
|
|
text: string | React.ReactElement<string>;
|
|
|
|
type?: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'link';
|
|
|
|
disabled?: boolean;
|
|
|
|
onClick?(): void;
|
|
|
|
}
|
|
|
|
interface Props {
|
|
|
|
isOpen?: boolean;
|
2018-05-11 15:15:32 +00:00
|
|
|
title?: React.ReactNode;
|
2018-03-08 19:28:43 +00:00
|
|
|
disableButtons?: boolean;
|
2018-05-11 15:15:32 +00:00
|
|
|
children: React.ReactNode;
|
2018-03-08 19:28:43 +00:00
|
|
|
buttons?: IButton[];
|
|
|
|
maxWidth?: number;
|
|
|
|
handleClose(): void;
|
|
|
|
}
|
|
|
|
interface ModalStyle {
|
|
|
|
width?: string;
|
|
|
|
maxWidth?: string;
|
|
|
|
}
|
|
|
|
|
2018-03-23 16:41:47 +00:00
|
|
|
const Fade = ({ ...props }: any) => (
|
|
|
|
<CSSTransition {...props} timeout={300} classNames="animate-modal" />
|
2018-03-08 19:28:43 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
export default class Modal extends PureComponent<Props, {}> {
|
2018-04-16 23:30:58 +00:00
|
|
|
public modalParent: HTMLElement;
|
2018-03-08 19:28:43 +00:00
|
|
|
public modalBody: ModalBody;
|
|
|
|
|
|
|
|
public componentDidUpdate(prevProps: Props) {
|
|
|
|
if (prevProps.isOpen !== this.props.isOpen) {
|
|
|
|
document.body.classList.toggle('no-scroll', !!this.props.isOpen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-16 23:30:58 +00:00
|
|
|
public componentDidMount() {
|
|
|
|
const modalEl = document.getElementById('ModalContainer');
|
|
|
|
if (modalEl) {
|
|
|
|
this.modalParent = document.createElement('div');
|
|
|
|
modalEl.appendChild(this.modalParent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 19:28:43 +00:00
|
|
|
public componentWillUnmount() {
|
|
|
|
document.body.classList.remove('no-scroll');
|
2018-04-16 23:30:58 +00:00
|
|
|
const modalEl = document.getElementById('ModalContainer');
|
|
|
|
if (modalEl) {
|
|
|
|
modalEl.removeChild(this.modalParent);
|
|
|
|
}
|
2018-03-08 19:28:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public render() {
|
|
|
|
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
|
|
|
|
const hasButtons = buttons && buttons.length;
|
|
|
|
const modalStyle: ModalStyle = {};
|
|
|
|
|
|
|
|
if (maxWidth) {
|
|
|
|
modalStyle.width = '100%';
|
|
|
|
modalStyle.maxWidth = `${maxWidth}px`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const modalBodyProps = { title, children, modalStyle, hasButtons, buttons, handleClose };
|
|
|
|
|
2018-04-16 23:30:58 +00:00
|
|
|
const modal = (
|
2018-03-08 19:28:43 +00:00
|
|
|
<TransitionGroup>
|
|
|
|
{isOpen && (
|
2018-03-23 16:41:47 +00:00
|
|
|
// Trap focus in modal by focusing the first element after the animation is complete
|
|
|
|
<Fade onEntered={() => this.modalBody.firstTabStop.focus()}>
|
2018-03-08 19:28:43 +00:00
|
|
|
<div>
|
|
|
|
<div className="Modal-overlay" onClick={handleClose} />
|
|
|
|
<ModalBody {...modalBodyProps} ref={div => (this.modalBody = div as ModalBody)} />
|
|
|
|
</div>
|
|
|
|
</Fade>
|
|
|
|
)}
|
|
|
|
</TransitionGroup>
|
|
|
|
);
|
2018-04-16 23:30:58 +00:00
|
|
|
|
|
|
|
if (this.modalParent) {
|
|
|
|
return createPortal(modal, this.modalParent);
|
|
|
|
} else {
|
|
|
|
return modal;
|
|
|
|
}
|
2018-03-08 19:28:43 +00:00
|
|
|
}
|
|
|
|
}
|