import React, { MouseEvent, ReactNode, useEffect, useMemo, useRef } from 'react'; import { createPortal } from 'react-dom'; import { TStyle, useClassnames } from '../../../hooks/use-classnames'; import { useBodyScrollLock } from '../../../hooks/use-body-scroll-lock'; import { Button, IProps as IButtonProps } from '../../button/v1'; import { Title } from '../../typography/v1/title'; import { IconClear } from '../../icons/clear'; import { consoleFormat } from '../../../tools/console-format'; import style from './index.module.pcss'; export interface IProps { readonly className?: string | TStyle, readonly header?: ReactNode, readonly children: ReactNode, readonly placement?: 'center' | 'bottom', readonly onClose?: () => void, readonly primaryButton?: IButtonProps, readonly secondaryButton?: IButtonProps, readonly anchorElement?: HTMLElement, readonly bodyPaddingPreset?: 'default' | 'reduced', readonly isCloseIconShow?: boolean, readonly isOpen?: boolean, readonly widthPreset?: 's' | 'm' | 'l' | 'xl' } export const Modal = ({ anchorElement = document.body, placement = 'center', bodyPaddingPreset = 'default', isCloseIconShow = true, widthPreset = 'm', isOpen = false, ...props }: IProps) => { const cn = useClassnames(style, props.className); const { enableBodyScroll, disableBodyScroll } = useBodyScrollLock(); const $root = useRef(null); const $body = useRef(null); useEffect(() => { const tempRef = $root.current; if(tempRef && placement !== 'bottom') { disableBodyScroll(tempRef); } return () => { if(tempRef && placement !== 'bottom') { enableBodyScroll(tempRef); } }; }, [isOpen]); useEffect(() => { if(placement === 'bottom' && (widthPreset === 'l' || widthPreset === 'xl')) { consoleFormat('Modal: Позиционирование `placement={bottom}` несовместимо с пресетами `widthPreset={L}` и `widthPreset={XL}`'); } }, [widthPreset, placement]); const escClick = (e: KeyboardEvent) => { if(e.key === 'Escape') { props.onClose?.(); } }; // необходимо, чтобы при событии mousedown внутри модалки и mouseup вне модалки окно не закрывалось const onClickOutside = (e: MouseEvent) => { if(e.type === 'mousedown') { props.onClose?.(); } }; const onClickLayout = (e: MouseEvent) => { e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); }; useEffect(() => { document.addEventListener('keydown', escClick, false); return () => { document.removeEventListener('keydown', escClick, false); }; }, []); const elHeader = useMemo(() => { if(typeof props.header === 'string') { return ( {props.header} ); } return props.header; }, [props.header]); const elCloseIcon = useMemo(() => { if(!!props.onClose && !!isCloseIconShow) { return ( ); } }, [bodyPaddingPreset, placement, isCloseIconShow, props.onClose]); const elModalHeader = useMemo(() => { if(props.header) { return (
{elHeader} {elCloseIcon}
); } return elCloseIcon; }, [elHeader, isCloseIconShow, props.header, elCloseIcon]); if(isOpen) { return createPortal( (
{elModalHeader}
{props.children}
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */} {props.primaryButton || props.secondaryButton ? (
{props.primaryButton ? (
) : null}
), anchorElement ); } return null; }; export default Modal;