import React, { useEffect, useState, useContext } from "react"; import Modal from "react-modal"; import { useThemeUI } from "theme-ui"; import MapInteractionContext from "../../contexts/MapInteractionContext"; import usePrevious from "../../helpers/usePrevious"; function MapMenu({ isOpen, onRequestClose, onModalContent, top, left, bottom, right, children, style, // A node to exclude from the pointer event for closing excludeNode, }) { // Save modal node in state to ensure that the pointer listeners // are removed if the open state changed not from the onRequestClose // callback const [modalContentNode, setModalContentNode] = useState(null); // Toggle map interaction when menu is opened const wasOpen = usePrevious(isOpen); const { setPreventMapInteraction } = useContext(MapInteractionContext); useEffect(() => { if (isOpen && !wasOpen) { setPreventMapInteraction(true); } else if (wasOpen && !isOpen) { setPreventMapInteraction(false); } }, [isOpen, setPreventMapInteraction, wasOpen]); useEffect(() => { // Close modal if interacting with any other element function handlePointerDown(event) { const path = event.composedPath(); if ( !path.includes(modalContentNode) && !(excludeNode && path.includes(excludeNode)) ) { onRequestClose(); document.body.removeEventListener("pointerdown", handlePointerDown); } } if (modalContentNode) { document.body.addEventListener("pointerdown", handlePointerDown); // Check for wheel event to close modal as well document.body.addEventListener( "wheel", () => { onRequestClose(); }, { once: true } ); } return () => { if (modalContentNode) { document.body.removeEventListener("pointerdown", handlePointerDown); } }; }, [modalContentNode, excludeNode, onRequestClose]); function handleModalContent(node) { setModalContentNode(node); onModalContent(node); } const { theme } = useThemeUI(); return ( {children} ); } MapMenu.defaultProps = { onModalContent: () => {}, top: "initial", left: "initial", right: "initial", bottom: "initial", style: {}, excludeNode: null, }; export default MapMenu;