import type { FC, TeactNode } from '../../lib/teact/teact'; import React, { useEffect, useRef } from '../../lib/teact/teact'; import type { TextPart } from '../../types'; import buildClassName from '../../util/buildClassName'; import captureKeyboardListeners from '../../util/captureKeyboardListeners'; import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager'; import freezeWhenClosed from '../../util/hoc/freezeWhenClosed'; import trapFocus from '../../util/trapFocus'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLastCallback from '../../hooks/useLastCallback'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useOldLang from '../../hooks/useOldLang'; import useShowTransition from '../../hooks/useShowTransition'; import Icon from '../common/icons/Icon'; import Button from './Button'; import Portal from './Portal'; import './Modal.scss'; export const ANIMATION_DURATION = 200; export type OwnProps = { title?: string | TextPart[]; className?: string; contentClassName?: string; headerClassName?: string; isOpen?: boolean; header?: TeactNode; isSlim?: boolean; hasCloseButton?: boolean; hasAbsoluteCloseButton?: boolean; noBackdrop?: boolean; noBackdropClose?: boolean; children: React.ReactNode; style?: string; dialogRef?: React.RefObject; onClose: () => void; onCloseAnimationEnd?: () => void; onEnter?: () => void; }; type StateProps = { shouldSkipHistoryAnimations?: boolean; }; const Modal: FC = ({ dialogRef, title, className, contentClassName, headerClassName, isOpen, isSlim, header, hasCloseButton, hasAbsoluteCloseButton, noBackdrop, noBackdropClose, children, style, shouldSkipHistoryAnimations, onClose, onCloseAnimationEnd, onEnter, }) => { const { shouldRender, transitionClassNames, } = useShowTransition( isOpen, onCloseAnimationEnd, shouldSkipHistoryAnimations, undefined, shouldSkipHistoryAnimations, ); // eslint-disable-next-line no-null/no-null const modalRef = useRef(null); const withCloseButton = hasCloseButton || hasAbsoluteCloseButton; useEffect(() => { if (!isOpen) { return undefined; } disableDirectTextInput(); return enableDirectTextInput; }, [isOpen]); const handleEnter = useLastCallback((e: KeyboardEvent) => { if (!onEnter) { return false; } e.preventDefault(); onEnter(); return true; }); useEffect(() => ( isOpen ? captureKeyboardListeners({ onEsc: onClose, onEnter: handleEnter }) : undefined ), [isOpen, onClose, handleEnter]); useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen]); useHistoryBack({ isActive: isOpen, onBack: onClose, }); useLayoutEffectWithPrevDeps(([prevIsOpen]) => { document.body.classList.toggle('has-open-dialog', Boolean(isOpen)); if (isOpen || (!isOpen && prevIsOpen !== undefined)) { dispatchHeavyAnimationEvent(ANIMATION_DURATION); } return () => { document.body.classList.remove('has-open-dialog'); }; }, [isOpen]); const lang = useOldLang(); if (!shouldRender) { return undefined; } function renderHeader() { if (header) { return header; } if (!title && !withCloseButton) return undefined; return (
{withCloseButton && ( )}
{title}
); } const fullClassName = buildClassName( 'Modal', className, transitionClassNames, noBackdrop && 'transparent-backdrop', isSlim && 'slim', ); return (
{renderHeader()}
{children}
); }; export default freezeWhenClosed(Modal);