// import { useState, useEffect, useRef } from 'react'; // interface MenuState { // isOpen: boolean; // anchorEl: null | HTMLElement; // } // function useMenu(): { // isOpen: boolean; // anchorEl: null | HTMLElement; // openMenu: (event: React.MouseEvent) => void; // closeMenu: () => void; // toggleMenu: (event: React.MouseEvent) => void; // menuRef: React.RefObject; // } { // const [menuState, setMenuState] = useState({ // isOpen: false, // anchorEl: null, // }); // const menuRef = useRef(null); // useEffect(() => { // const handleOutsideClick = (event: MouseEvent) => { // if ( // menuState.anchorEl && // !menuState.anchorEl.contains(event.target as Node) // ) { // closeMenu(); // } // }; // if (menuState.isOpen) { // const listener = (event: MouseEvent) => handleOutsideClick(event); // document.addEventListener('mousedown', listener); // return () => { // document.removeEventListener('mousedown', listener); // }; // } // }, [menuState.isOpen, menuState.anchorEl]); // const openMenu = (event: React.MouseEvent) => { // if (!menuState.isOpen) { // setMenuState({ // isOpen: true, // anchorEl: event.currentTarget, // }); // } // }; // const closeMenu = () => { // setMenuState({ // isOpen: false, // anchorEl: null, // }); // }; // const toggleMenu = (event: React.MouseEvent) => { // if (menuState.isOpen) { // closeMenu(); // } else { // openMenu(event); // } // }; // return { // isOpen: menuState.isOpen, // anchorEl: menuState.anchorEl, // openMenu, // closeMenu, // toggleMenu, // menuRef, // }; // } // export default useMenu; import { useState, useEffect, useRef } from 'react'; import { getAnchorPointByTarget } from '../../util/position'; import { Dimensions, Position } from '../../../util'; import ReactDOM from 'react-dom'; interface MenuState { isOpen: boolean; anchorEl: null | HTMLElement; position: { y: number; x: number }; } type Orientation = | 'top-left' | 'top-right' | 'top' | 'right' | 'left' | 'pointer' | 'bottom-left' | 'bottom-right' | 'bottom'; export function useMenu( orientation: Orientation = 'bottom-left', menuDimensions: Dimensions, offset?: Position, closableIds?: string[], closableClasses?: string[] ): { isOpen: boolean; anchorEl: null | HTMLElement; position: { y: number; x: number }; openMenu: (event: React.MouseEvent) => void; closeMenu: () => void; toggleMenu: (event: React.MouseEvent) => void; menuRef: React.RefObject; } { const [menuState, setMenuState] = useState({ isOpen: false, anchorEl: null, position: { y: 0, x: 0 }, }); const menuRef = useRef(null); useEffect(() => { const handleOutsideClick = (event: MouseEvent) => { const domNode = ReactDOM.findDOMNode(menuRef.current); if (domNode && domNode.contains(event.target as HTMLElement)) { // check if the click is on a closable element if (closableIds) { const id = (event.target as HTMLElement).id; if (closableIds.includes(id)) { closeMenu(); return; } } if (closableClasses) { const classes = (event.target as HTMLElement).classList; if (closableClasses.some((c) => classes.contains(c))) { closeMenu(); return; } } event.stopPropagation(); } else { if ( menuState.anchorEl && !menuState.anchorEl.contains(event.target as Node) ) { closeMenu(); event.stopPropagation(); } } }; if (menuState.isOpen) { const listener = (event: MouseEvent) => handleOutsideClick(event); document.addEventListener('mousedown', listener); return () => { document.removeEventListener('mousedown', listener); }; } return; }, [menuState.isOpen, menuState.anchorEl, closableIds, closableClasses]); const calculatePosition = ( anchorEvent: React.MouseEvent, orientation: Orientation, menuDimensions: { width: number; height: number }, offset?: Position ): Position => { let position: Position = getAnchorPointByTarget( anchorEvent.nativeEvent, menuDimensions, orientation, offset ); return position; }; const openMenu = (event: React.MouseEvent) => { const { currentTarget } = event; const position = calculatePosition( event, orientation, menuDimensions, offset ); setMenuState({ isOpen: true, anchorEl: currentTarget as HTMLElement, position, }); }; const closeMenu = () => { setMenuState({ isOpen: false, anchorEl: null, position: { y: 0, x: 0 }, }); }; const toggleMenu = (event: React.MouseEvent) => { if (menuState.isOpen) { closeMenu(); } else { openMenu(event); } }; return { ...menuState, openMenu, closeMenu, toggleMenu, menuRef }; }