import type { ElementRef } from '../../lib/teact/teact'; import React, { beginHeavyAnimation, type FC, memo, useEffect, useRef, } from '../../lib/teact/teact'; import type { MenuPositionOptions } from '../../hooks/useMenuPosition'; import { IS_BACKDROP_BLUR_SUPPORTED } from '../../util/browser/windowEnvironment'; import buildClassName from '../../util/buildClassName'; import captureEscKeyListener from '../../util/captureEscKeyListener'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import useAppLayout from '../../hooks/useAppLayout'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useHistoryBack from '../../hooks/useHistoryBack'; import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation'; import useLastCallback from '../../hooks/useLastCallback'; import useMenuPosition from '../../hooks/useMenuPosition'; import useShowTransition from '../../hooks/useShowTransition'; import useVirtualBackdrop from '../../hooks/useVirtualBackdrop'; import Portal from './Portal'; import './Menu.scss'; export type { MenuPositionOptions } from '../../hooks/useMenuPosition'; type OwnProps = { ref?: ElementRef; isOpen: boolean; shouldCloseFast?: boolean; id?: string; className?: string; bubbleClassName?: string; ariaLabelledBy?: string; autoClose?: boolean; footer?: string; noCloseOnBackdrop?: boolean; backdropExcludedSelector?: string; noCompact?: boolean; onKeyDown?: (e: React.KeyboardEvent) => void; onCloseAnimationEnd?: () => void; onClose: () => void; onMouseEnter?: (e: React.MouseEvent) => void; onMouseEnterBackdrop?: (e: React.MouseEvent) => void; onMouseLeave?: (e: React.MouseEvent) => void; withPortal?: boolean; children?: React.ReactNode; } & MenuPositionOptions; const ANIMATION_DURATION = 200; const Menu: FC = ({ ref: externalRef, shouldCloseFast, isOpen, id, className, bubbleClassName, ariaLabelledBy, children, autoClose = false, footer, noCloseOnBackdrop = false, backdropExcludedSelector, noCompact, onCloseAnimationEnd, onClose, onMouseEnter, onMouseLeave, withPortal, onMouseEnterBackdrop, ...positionOptions }) => { const { isTouchScreen } = useAppLayout(); const containerRef = useRef(); const { ref: bubbleRef } = useShowTransition({ isOpen, ref: externalRef, onCloseAnimationEnd, }); useMenuPosition(isOpen, containerRef, bubbleRef, positionOptions); useEffect( () => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose], ); useHistoryBack({ isActive: isOpen, onBack: onClose, shouldBeReplaced: true, }); useEffectWithPrevDeps(([prevIsOpen]) => { if (isOpen || (!isOpen && prevIsOpen === true)) { beginHeavyAnimation(ANIMATION_DURATION); } }, [isOpen]); const handleKeyDown = useKeyboardListNavigation(bubbleRef, isOpen, autoClose ? onClose : undefined, undefined, true); useVirtualBackdrop( isOpen, containerRef, noCloseOnBackdrop ? undefined : onClose, undefined, backdropExcludedSelector, ); const bubbleFullClassName = buildClassName( 'bubble menu-container custom-scroll', footer && 'with-footer', bubbleClassName, shouldCloseFast && 'close-fast', ); const handleClick = useLastCallback((e: React.MouseEvent) => { e.stopPropagation(); if (autoClose) { onClose(); } }); const menu = (
{isOpen && ( // This only prevents click events triggering on underlying elements
)}
{children} {footer &&
{footer}
}
); if (withPortal) { return {menu}; } return menu; }; export default memo(Menu);