import React, { useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { closePopup, openPopup, togglePopup, } from "../../slices/localVariablesSlice"; const HoverPopup = ({ id, title, content, children, className = "", Mode = "hover", align = "auto", // <-- dynamic placement minWidth = "250px", maxWidth = "350px", boundaryRef = null, }) => { const dispatch = useDispatch(); const visible = useSelector((s) => s.localVariables.popups[id] || false); const triggerRef = useRef(null); const popupRef = useRef(null); const handleMouseEnter = () => Mode === "hover" && dispatch(openPopup(id)); const handleMouseLeave = () => Mode === "hover" && dispatch(closePopup(id)); const handleClick = (e) => { if (Mode !== "click") return; e.stopPropagation(); dispatch(togglePopup(id)); }; // Close popup when clicking outside (click mode) useEffect(() => { if (Mode !== "click" || !visible) return; const handler = (e) => { if ( popupRef.current && !popupRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target) ) { dispatch(closePopup(id)); } }; document.addEventListener("click", handler); return () => document.removeEventListener("click", handler); }, [visible, Mode, id, dispatch]); // ---------- DYNAMIC POSITIONING LOGIC ---------- useEffect(() => { if (!visible || !popupRef.current || !triggerRef.current) return; requestAnimationFrame(() => { const popup = popupRef.current; const trigger = triggerRef.current; const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement; const boundaryRect = boundaryEl.getBoundingClientRect(); const triggerRect = trigger.getBoundingClientRect(); const popupRect = popup.getBoundingClientRect(); let left; // AUTO ALIGN (smart) if (align === "auto") { const center = triggerRect.left + triggerRect.width / 2 - boundaryRect.left - popupRect.width / 2; left = Math.max( 0, Math.min(center, boundaryRect.width - popupRect.width) ); } // LEFT ALIGN else if (align === "left") { left = triggerRect.left - boundaryRect.left; if (left + popupRect.width > boundaryRect.width) { left = boundaryRect.width - popupRect.width; // clamp right } } // RIGHT ALIGN else if (align === "right") { left = triggerRect.left + triggerRect.width - boundaryRect.left - popupRect.width; if (left < 0) left = 0; // clamp left } popup.style.left = `${left}px`; popup.style.top = `100%`; }); }, [visible, align, boundaryRef]); // ------------------------------------------------ return (
{children}
{visible && (
e.stopPropagation()} > {title &&
{title}
} {content}
)}
); }; export default HoverPopup;