125 lines
3.3 KiB
JavaScript

import React, { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
closePopup,
openPopup,
togglePopup,
} from "../../slices/localVariablesSlice";
/**
* align: "auto" | "left" | "right"
* boundaryRef: optional ref to the drawer/container element to use as boundary
*/
const HoverPopup = ({
id,
title,
content,
children,
className = "",
Mode = "hover",
align = "auto",
boundaryRef = null,
}) => {
const dispatch = useDispatch();
const visible = useSelector((s) => s.localVariables.popups[id] || false);
const triggerRef = useRef(null);
const popupRef = useRef(null);
const handleMouseEnter = () => {
if (Mode === "hover") dispatch(openPopup(id));
};
const handleMouseLeave = () => {
if (Mode === "hover") dispatch(closePopup(id));
};
const handleClick = (e) => {
if (Mode === "click") {
e.stopPropagation();
dispatch(togglePopup(id));
}
};
// Close on outside click when using 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);
}, [Mode, visible, dispatch, id]);
useEffect(() => {
if (!visible || !popupRef.current || !triggerRef.current) return;
requestAnimationFrame(() => {
const popup = popupRef.current;
const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement;
if (!boundaryEl) return;
const boundaryRect = boundaryEl.getBoundingClientRect();
const triggerRect = triggerRef.current.getBoundingClientRect();
popup.style.left = "";
popup.style.right = "";
popup.style.transform = "";
popup.style.top = "";
const popupRect = popup.getBoundingClientRect();
const triggerCenterX = triggerRect.left + triggerRect.width / 2 - boundaryRect.left;
let left = triggerCenterX - popupRect.width / 2;
// Clamp to boundaries
left = Math.max(0, Math.min(left, boundaryRect.width - popupRect.width));
popup.style.left = `${left}px`;
});
}, [visible, align, boundaryRef]);
return (
<div
className="d-inline-block position-relative" // <-- ADD THIS !!
style={{ overflow: "visible" }}
>
<div
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
style={{ cursor: "pointer", display: "inline-block" }}
>
{children}
</div>
{visible && (
<div
ref={popupRef}
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`}
style={{
zIndex: 2000,
top: "100%",
minWidth: "200px",
maxWidth: "300px",
wordWrap: "break-word",
}}
onClick={(e) => e.stopPropagation()}
>
{title && <h6 className="fw-semibold mb-2">{title}</h6>}
<div>{content}</div>
</div>
)}
</div>
);
};
export default HoverPopup;