Files
grungnet/src/components/map/MapMenu.tsx

111 lines
2.7 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useState } from "react";
import Modal from "react-modal";
import { useThemeUI } from "theme-ui";
2021-07-16 18:59:29 +10:00
import CSS from "csstype";
import { RequestCloseEventHandler } from "../../types/Events";
type MapMenuProps = {
isOpen: boolean;
onRequestClose: RequestCloseEventHandler;
onModalContent: (instance: HTMLDivElement) => void;
top: number;
left: number;
bottom: number;
right: number;
children: React.ReactNode;
style: React.CSSProperties;
excludeNode: Node | null;
};
function MapMenu({
isOpen,
onRequestClose,
onModalContent,
top,
left,
bottom,
right,
children,
style,
// A node to exclude from the pointer event for closing
excludeNode,
2021-07-16 18:59:29 +10:00
}: MapMenuProps) {
// Save modal node in state to ensure that the pointer listeners
// are removed if the open state changed not from the onRequestClose
// callback
2021-07-16 18:59:29 +10:00
const [modalContentNode, setModalContentNode] = useState<Node | null>(null);
useEffect(() => {
// Close modal if interacting with any other element
2021-07-16 18:59:29 +10:00
function handleInteraction(event: Event) {
const path = event.composedPath();
if (
2021-07-16 18:59:29 +10:00
modalContentNode &&
!path.includes(modalContentNode) &&
!(excludeNode && path.includes(excludeNode)) &&
!(event.target instanceof HTMLTextAreaElement)
) {
onRequestClose();
document.body.removeEventListener("pointerdown", handleInteraction);
document.body.removeEventListener("wheel", handleInteraction);
}
}
if (modalContentNode) {
document.body.addEventListener("pointerdown", handleInteraction);
// Check for wheel event to close modal as well
document.body.addEventListener("wheel", handleInteraction);
}
2020-05-22 13:46:52 +10:00
return () => {
if (modalContentNode) {
document.body.removeEventListener("pointerdown", handleInteraction);
}
};
2020-05-22 13:46:52 +10:00
}, [modalContentNode, excludeNode, onRequestClose]);
2021-07-16 18:59:29 +10:00
function handleModalContent(node: HTMLDivElement) {
setModalContentNode(node);
onModalContent(node);
}
const { theme } = useThemeUI();
return (
<Modal
isOpen={isOpen}
onRequestClose={onRequestClose}
style={{
overlay: { top: "0", bottom: "initial" },
content: {
2021-07-16 18:59:29 +10:00
backgroundColor: theme.colors?.overlay as CSS.Property.Color,
top,
left,
right,
bottom,
padding: 0,
borderRadius: "4px",
border: "none",
...style,
},
}}
contentRef={handleModalContent}
>
{children}
</Modal>
);
}
MapMenu.defaultProps = {
onModalContent: () => {},
top: "initial",
left: "initial",
right: "initial",
bottom: "initial",
style: {},
excludeNode: null,
};
export default MapMenu;