/** * Modal window * * @author: Ilya Braujer * @date: 2019-12-26 */ import * as React from 'react'; import {Overlay} from './core/overlay/Overlay'; import {joinClassNames} from '../../utils/joinClassNames'; import {filterProps} from '../../utils/filterProps'; import {getSizeThemeKey} from '../../utils/getSizeThemeKey'; import {Icon, SIZE} from '../../index'; import {DefaultProps, IProps, ModalAnimation, PropsWithDefault, SizeClassNames} from './Modal.types'; import * as theme from './modal.m.scss'; const DEFAULT_BODY_PADDING = 24; // Default padding for the modal body is 24px (see model.m.scss) interface IState { isOpenModal: boolean; } export { IProps as IModalProps, ModalAnimation }; export class Modal extends React.PureComponent { constructor (props: IProps) { super(props); this.updateIsOpenModalAfterAnimation = this.updateIsOpenModalAfterAnimation.bind(this); } override state: IState = { isOpenModal: false }; static defaultProps: DefaultProps = { shouldCloseOnClickOutside: true, shouldCloseOnEsc: true, hasCloseIcon: true, 'data-qaid': 'modal', animation: ModalAnimation.Fade }; static excludingProps: Array = [ 'isOpen', 'size', 'header', 'footer', 'noBodyPadding', 'shouldCloseOnClickOutside', 'shouldCloseOnEsc', 'hasCloseIcon', 'onClose', 'styles', 'animation' ]; headerRef = React.createRef(); bodyRef = React.createRef(); override componentDidMount (): void { this.updateIsOpenModal(); this.setHeaderWidth(); } override componentDidUpdate (prevProps: Readonly, prevState: Readonly<{}>, snapshot?: any): void { this.updateIsOpenModal(); this.setHeaderWidth(); } setHeaderWidth = () => { if (this.bodyRef.current && this.headerRef.current) { const bodyChildren = this.bodyRef.current.children.item(0); const headerElement = this.headerRef.current; if (bodyChildren) { const bodyWidth = bodyChildren.clientWidth; const bodyPadding = this.props.noBodyPadding === true ? 0 : DEFAULT_BODY_PADDING; if (bodyWidth !== headerElement.offsetWidth) { headerElement.style.width = `${bodyWidth + (bodyPadding * 2)}px`; } } } }; updateIsOpenModal () { if (this.props.animation !== ModalAnimation.None) { if (this.props.isOpen !== this.state.isOpenModal && !this.state.isOpenModal) { this.setState({ isOpenModal: true }); } } else { this.setState({ isOpenModal: this.props.isOpen }); } } updateIsOpenModalAfterAnimation () { if (this.props.animation !== ModalAnimation.None && this.props.isOpen !== this.state.isOpenModal && this.state.isOpenModal ) { this.setState({ isOpenModal: false }); } } override render () { const { isOpen, header, size, styles, shouldCloseOnClickOutside, shouldCloseOnEsc, hasCloseIcon, onClose, animation } = this.props as PropsWithDefault; const style = styles && styles.modal; const headerElement = this.renderHeader(); const footerElement = this.renderFooter(); let sizeClassName; if (size) { const sizeThemeKey = getSizeThemeKey('modal', size); sizeClassName = theme[sizeThemeKey]; } const className = joinClassNames( theme.modal, sizeClassName, [theme.modalHasHeaderContent, Boolean(header)], [theme.modalHasCloseIcon, Boolean(hasCloseIcon)], [theme.modalHasHeader, Boolean(headerElement)], [theme.modalHasFooter, Boolean(footerElement)], ); const htmlProps = { ...filterProps>(this.props, Modal.excludingProps), style, className }; const containerClass = joinClassNames( theme.container, [theme.animationFlipTopOpen, (animation === ModalAnimation.FlipTop && isOpen)], [theme.animationFlipTopClose, (animation === ModalAnimation.FlipTop && !isOpen)], [theme.animationSlitOpen, (animation === ModalAnimation.Slit && isOpen)], [theme.animationSlitClose, (animation === ModalAnimation.Slit && !isOpen)], [theme.animationOpen, animation === ModalAnimation.Fade && isOpen], [theme.animationClose, animation === ModalAnimation.Fade && !isOpen] ); return (
{headerElement} {this.renderBody()} {footerElement}
); } private renderCloseIcon () { if (!this.props.hasCloseIcon) { return null; } const {onClose, styles} = this.props; const iconElement: React.ReactNode = ; const style = styles && styles.closeIconContainer; return (
{iconElement}
); } private renderHeader () { const {header, styles} = this.props; let headerContentElement = null; if (header) { const className = joinClassNames(theme.headerContent, [ theme.headerContentHasText, typeof header === 'string' ]); const style = styles && styles.headerContent; headerContentElement = (
{header}
); } const closeIconElement = this.renderCloseIcon(); if (headerContentElement || closeIconElement) { const style = styles && styles.header; return (
{headerContentElement} {closeIconElement}
); } return null; } private renderBody () { const {styles, noBodyPadding} = this.props; const style = styles && styles.body; const className = joinClassNames( theme.body, [theme.noPadding, Boolean(noBodyPadding)] ); return (
{this.props.children}
); } private renderFooter () { const {styles, footer} = this.props; if (footer) { const style = styles && styles.footer; return (
{footer}
); } return null; } }