import React, { useState, useEffect } from "react"; import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { ClickableElementContainer } from "./styles"; import { BaseMenuProps } from "./types"; import getPortalRoot from "../../util/getPortalRoot"; const BaseMenu: React.FC = ({ component, options, children, isOpen = false }) => { const [targetElement, setTargetElement] = useState(null); const [menuElement, setMenuElement] = useState(null); const placement = options?.placement ?? "bottom"; const offset = options?.offset ?? [0, 10]; const padding = options?.padding ?? { left: 16, right: 16 }; const [isMenuOpen, setIsMenuOpen] = useState(isOpen); const toggle = () => { setIsMenuOpen((prev) => !prev); }; const open = () => { setIsMenuOpen(true); }; const close = () => { setIsMenuOpen(false); }; // Allow for component to be controlled useEffect(() => { setIsMenuOpen(isOpen); }, [isOpen, setIsMenuOpen]); useEffect(() => { if (menuElement !== null && targetElement !== null) { const handleClickOutside = ({ target }: Event) => { if (target instanceof Node && !menuElement.contains(target) && !targetElement.contains(target)) { setIsMenuOpen(false); } }; document.addEventListener("click", handleClickOutside); return () => { document.removeEventListener("click", handleClickOutside); }; } return undefined; }, [menuElement, targetElement]); const { styles, attributes } = usePopper(targetElement, menuElement, { placement, modifiers: [ { name: "offset", options: { offset } }, { name: "preventOverflow", options: { padding } }, ], }); const menu = (
{typeof children === "function" ? children({ toggle, open, close }) : children}
); const portal = getPortalRoot(); const renderMenu = portal ? createPortal(menu, portal) : menu; return ( <> {component} {isMenuOpen && renderMenu} ); }; export default BaseMenu;