import React, { type DetailedHTMLProps, type 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; hoverOpen?: boolean; no_cancel_button?: boolean; }; /** * # 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 * @className twui-modal-content * @className twui-modal-target */ export default function Modal(props: TWUI_MODAL_PROPS) { const { target, targetRef, targetWrapperProps, open: existingOpen, setOpen: existingSetOpen, isPopover, popoverReferenceRef, trigger = "hover", debounce = 500, onClose, hoverOpen, no_cancel_button, } = 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) { if (isPopover) { modalRoot.style.zIndex = "1000"; } 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); const closeTimeoutRef = React.useRef(null); const popoverEnterFn = React.useCallback((e: any) => { popoverTargetActiveRef.current = true; popoverContentActiveRef.current = false; window.clearTimeout(closeTimeoutRef.current); setOpen(true); props.onMouseEnter?.(e); }, []); const popoverLeaveFn = React.useCallback((e: any) => { window.clearTimeout(closeTimeoutRef.current); closeTimeoutRef.current = setTimeout(() => { if (popoverContentActiveRef.current) { popoverContentActiveRef.current = false; return; } setOpen(false); }, debounce); props.onMouseLeave?.(e); }, []); const target_class_name = isPopover ? "twui-popover-target" : "twui-modal-target"; const content_class_name = isPopover ? "twui-popover-content" : "twui-modal-content"; const handleClickOutside = React.useCallback((e: MouseEvent) => { const targetEl = e.target as HTMLElement; if (!isPopover) { return; } const closestWrapper = targetEl.closest(`.${content_class_name}`); const closestTarget = targetEl.closest(`.${target_class_name}`); if (closestTarget) return; if (!closestWrapper) { return setOpen(false); } }, []); React.useEffect(() => { if (!isPopover) return; document.addEventListener("click", handleClickOutside); return () => { document.removeEventListener("click", handleClickOutside); }; }, []); return ( {target ? (
{ e.preventDefault(); e.stopPropagation(); setOpen(!open); }} ref={finalTargetRef} onMouseEnter={ isPopover && (trigger === "hover" || hoverOpen) ? popoverEnterFn : targetWrapperProps?.onMouseEnter } onMouseLeave={ isPopover && (trigger === "hover" || hoverOpen) ? popoverLeaveFn : targetWrapperProps?.onMouseLeave } className={twMerge( target_class_name, targetWrapperProps?.className, )} > {target}
) : null} {ready ? ( isPopover ? ( ) : ( ) ) : null}
); }