import React, { DetailedHTMLProps, HTMLAttributes } from "react"; import ModalComponent from "../(partials)/ModalComponent"; import PopoverComponent from "../(partials)/PopoverComponent"; import { twMerge } from "tailwind-merge"; export const TWUIPopoverStyles = [ "top", "bottom", "left", "right", "transform", "bottom-left", "bottom-right", ] as const; export const TWUIPopoverTriggers = ["hover", "click"] as const; export type TWUI_MODAL_PROPS = DetailedHTMLProps< HTMLAttributes, HTMLDivElement > & { target?: React.ReactNode; targetRef?: React.RefObject; popoverReferenceRef?: React.RefObject; targetWrapperProps?: React.DetailedHTMLProps< React.HTMLAttributes, HTMLDivElement >; setOpen?: React.Dispatch>; open?: boolean; isPopover?: boolean; position?: (typeof TWUIPopoverStyles)[number]; trigger?: (typeof TWUIPopoverTriggers)[number]; debounce?: number; onClose?: () => any; }; /** * # Modal Component * @ID twui-modal-root * @className twui-modal-content * @className twui-modal * @ID twui-popover-root * @className twui-popover-content * @className twui-popover-target */ export default function Modal(props: TWUI_MODAL_PROPS) { const { target, targetRef, targetWrapperProps, open: existingOpen, setOpen: existingSetOpen, isPopover, popoverReferenceRef, trigger = "hover", debounce = 500, onClose, } = props; const [ready, setReady] = React.useState(false); const [open, setOpen] = React.useState(existingOpen || false); React.useEffect(() => { const IDName = isPopover ? "twui-popover-root" : "twui-modal-root"; const modalRoot = document.getElementById(IDName); if (modalRoot) { setReady(true); } else { const newModalRootEl = document.createElement("div"); newModalRootEl.id = IDName; document.body.appendChild(newModalRootEl); setReady(true); } }, []); React.useEffect(() => { existingSetOpen?.(open); if (open == false) onClose?.(); }, [open]); React.useEffect(() => { setOpen(existingOpen || false); }, [existingOpen]); const finalTargetRef = targetRef || React.useRef(null); const finalPopoverReferenceRef = popoverReferenceRef || finalTargetRef; const popoverTargetActiveRef = React.useRef(false); const popoverContentActiveRef = React.useRef(false); let closeTimeout: any; const popoverEnterFn = React.useCallback((e: any) => { popoverTargetActiveRef.current = true; popoverContentActiveRef.current = false; setOpen(true); props.onMouseEnter?.(e); }, []); const popoverLeaveFn = React.useCallback((e: any) => { window.clearTimeout(closeTimeout); closeTimeout = setTimeout(() => { // if (popoverTargetActiveRef.current) { // popoverTargetActiveRef.current = false; // return; // } if (popoverContentActiveRef.current) { popoverContentActiveRef.current = false; return; } setOpen(false); }, debounce); props.onMouseLeave?.(e); }, []); const handleClickOutside = React.useCallback((e: MouseEvent) => { const targetEl = e.target as HTMLElement; const closestWrapper = targetEl.closest(".twui-popover-content"); const closestTarget = targetEl.closest(".twui-popover-target"); if (closestTarget) return; if (!closestWrapper) { return setOpen(false); } }, []); React.useEffect(() => { if (!isPopover) return; document.addEventListener("click", handleClickOutside); return () => { document.removeEventListener("click", handleClickOutside); }; }, []); return ( {target ? (
setOpen(!open)} ref={finalTargetRef} onMouseEnter={ isPopover && trigger === "hover" ? popoverEnterFn : targetWrapperProps?.onMouseEnter } onMouseLeave={ isPopover && trigger === "hover" ? popoverLeaveFn : targetWrapperProps?.onMouseLeave } className={twMerge( "twui-popover-target", targetWrapperProps?.className )} > {target}
) : null} {ready ? ( isPopover ? ( ) : ( ) ) : null}
); }