import React, { Children, cloneElement, useContext, useEffect, useCallback, useState, } from "react"; import { createPortal } from "react-dom"; import { DropdownMenuProps } from "./types"; import { DropdownContext } from "../context/DropdownContext"; import { useKeyboard } from "../../../hooks/components/Dropdown/useKeyboard"; import { usePopper } from "react-popper"; import { useTransition } from "../../../hooks/useTransition"; import { Placement, flip } from "@popperjs/core"; import clsx from "clsx"; import DropdownMenuTheme from "./DrodpownMenuTheme"; const TEDropdownMenu: React.FC = ({ className, tag: Tag = "ul", children, appendToBody = false, theme: customTheme, responsive, position, alignment = "start", popperConfig, display = "dynamic", offset = [0, 0], ...props }) => { const [windowWidth, setWindowWidth] = useState(window.innerWidth); const [newAlignment, setNewAlignment] = useState( alignment ); const [placement, setPlacement] = useState( "bottom-start" ); const [isFaded, setIsFaded] = useState(false); const [show, setShow] = useState(false); const { activeIndex, isOpenState, setPopperElement, animation, referenceElement, popperElement, } = useContext(DropdownContext); const theme = { ...customTheme, ...DropdownMenuTheme, }; const { onTransitionShow, onTransitionHide } = useTransition(popperElement); useKeyboard(children); const classes = clsx( theme.menu, animation && theme.fade, isFaded ? "opacity-100" : "opacity-0", className ); const handleResize = useCallback(() => { setWindowWidth(window.innerWidth); }, []); useEffect(() => { if (isOpenState) { setShow(true); onTransitionShow(() => { setIsFaded(true); }); return; } setIsFaded(false); onTransitionHide(() => { setShow(false); }); }, [isOpenState]); useEffect(() => { window.addEventListener("resize", handleResize); handleResize(); return () => window.removeEventListener("resize", handleResize); }, [handleResize]); useEffect(() => { const responsiveAlignment = () => { const breakpoints = { "sm-start": 640, "md-start": 768, "lg-start": 1024, "xl-start": 1280, "2xl-start": 1536, "sm-end": 640, "md-end": 768, "lg-end": 1024, "xl-end": 1280, "2xl-end": 1536, }; const matchingBreakpoint = Object.entries(breakpoints).find( ([key, value]) => responsive === key && windowWidth >= value ); return matchingBreakpoint ? matchingBreakpoint?.[0].split("-")[1] : alignment; }; setNewAlignment(responsiveAlignment()); }, [responsive, alignment, windowWidth, newAlignment]); useEffect(() => { const calculatePlacement = () => { if (position === "dropright") { return "right-start"; } if (position === "dropleft") { return "left-start"; } const isEnd = popperElement && newAlignment === "end"; if (position === "dropup") { return isEnd ? "top-end" : "top-start"; } return isEnd ? "bottom-end" : "bottom-start"; }; setPlacement(calculatePlacement()); }, [position, alignment, newAlignment, popperElement]); const { styles } = usePopper( referenceElement, popperElement, display === "dynamic" ? { placement: placement, modifiers: [ flip, { name: "offset", options: { offset, }, }, ], ...popperConfig, } : { modifiers: [ { name: "applyStyles", enabled: false, }, ], } ); const menu = ( {Children.map(children, (child, idx) => cloneElement(child, { tabIndex: idx, "data-te-active": activeIndex === idx && true, }) )} ); return ( <>{show && (appendToBody ? createPortal(menu, document.body) : menu)} ); }; export default TEDropdownMenu;