Correction in HoverPopup.

This commit is contained in:
Kartik Sharma 2025-11-29 10:26:08 +05:30 committed by pramod.mahajan
parent c3720df294
commit 57bbcd4b45

View File

@ -1,14 +1,14 @@
import React, { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
closePopup,
openPopup,
togglePopup,
} from "../../slices/localVariablesSlice";
import { closePopup, openPopup, togglePopup } from "../../slices/localVariablesSlice";
/**
* align: "auto" | "left" | "right"
* boundaryRef: optional ref to the drawer/container element to use as boundary
* Props:
* id, title, content, children
* className = ""
* Mode = "hover" | "click"
* align = "auto" | "left" | "right"
* boundaryRef = optional ref to DOM element to constrain popup within (getBoundingClientRect used)
*/
const HoverPopup = ({
id,
@ -21,7 +21,7 @@ const HoverPopup = ({
boundaryRef = null,
}) => {
const dispatch = useDispatch();
const visible = useSelector((s) => s.localVariables.popups[id] || false);
const visible = useSelector((s) => s.localVariables.popups?.[id] || false);
const triggerRef = useRef(null);
const popupRef = useRef(null);
@ -39,7 +39,7 @@ const HoverPopup = ({
}
};
// Close on outside click when using click mode
// Close on outside click when in click mode
useEffect(() => {
if (Mode !== "click" || !visible) return;
@ -58,38 +58,61 @@ const HoverPopup = ({
return () => document.removeEventListener("click", handler);
}, [Mode, visible, dispatch, id]);
// Positioning: compute left/top relative to popup.offsetParent, but clamp using boundaryRef (if provided)
useEffect(() => {
if (!visible || !popupRef.current || !triggerRef.current) return;
requestAnimationFrame(() => {
const popup = popupRef.current;
const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement;
if (!boundaryEl) return;
const trigger = triggerRef.current;
const triggerRect = trigger.getBoundingClientRect();
const popupRect = popup.getBoundingClientRect();
// Find offsetParent (element the absolute popup is positioned relative to)
const offsetParent = popup.offsetParent || document.documentElement;
const offsetParentRect = offsetParent.getBoundingClientRect();
// Global positions we want to clamp against (in viewport coordinates)
let desiredLeftGlobal;
if (align === "left") {
desiredLeftGlobal = triggerRect.left;
} else if (align === "right") {
desiredLeftGlobal = triggerRect.right - popupRect.width;
} else {
// auto / center: center popup under trigger
desiredLeftGlobal = triggerRect.left + triggerRect.width / 2 - popupRect.width / 2;
}
// Compute boundaries in global coordinates (use boundaryRef if provided, else offsetParent)
const boundaryEl = (boundaryRef && boundaryRef.current) || offsetParent;
const boundaryRect = boundaryEl.getBoundingClientRect();
const triggerRect = triggerRef.current.getBoundingClientRect();
popup.style.left = "";
// Clamp desiredLeftGlobal to boundaryRect
const minLeftGlobal = boundaryRect.left;
const maxLeftGlobal = boundaryRect.right - popupRect.width;
let clampedLeftGlobal = Math.min(Math.max(desiredLeftGlobal, minLeftGlobal), maxLeftGlobal);
// Convert to coordinates relative to offsetParent
const leftRelativeToOffsetParent = clampedLeftGlobal - offsetParentRect.left;
// Compute top: place popup just below trigger with small gap
const gap = 8;
const desiredTopGlobal = triggerRect.bottom + gap;
// Convert to offsetParent-relative top
const topRelativeToOffsetParent = desiredTopGlobal - offsetParentRect.top;
// Apply styles
popup.style.left = `${Math.round(leftRelativeToOffsetParent)}px`;
popup.style.top = `${Math.round(topRelativeToOffsetParent)}px`;
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" style={{ overflow: "visible" }}>
<div
className="d-inline-block position-relative" // <-- ADD THIS !!
style={{ overflow: "visible" }}
>
<div
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
@ -105,10 +128,10 @@ const HoverPopup = ({
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",
// we set left/top from JS; ensure positionAbsolute context exists via offsetParent
}}
onClick={(e) => e.stopPropagation()}
>
@ -118,7 +141,6 @@ const HoverPopup = ({
)}
</div>
);
};
export default HoverPopup;