Branch Name:
@@ -149,7 +149,8 @@ const ManageJobTicket = ({ Job }) => {
}
>
diff --git a/src/components/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx
index 0afb63e0..9c5f8e13 100644
--- a/src/components/common/HoverPopup.jsx
+++ b/src/components/common/HoverPopup.jsx
@@ -1,15 +1,11 @@
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";
-/**
- * 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,
title,
@@ -17,29 +13,26 @@ const HoverPopup = ({
children,
className = "",
Mode = "hover",
- align = "auto",
+ align = "auto", // <-- dynamic placement
+ minWidth = "250px",
+ maxWidth = "350px",
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);
- const handleMouseEnter = () => {
- if (Mode === "hover") dispatch(openPopup(id));
- };
- const handleMouseLeave = () => {
- if (Mode === "hover") dispatch(closePopup(id));
- };
+ const handleMouseEnter = () => Mode === "hover" && dispatch(openPopup(id));
+ const handleMouseLeave = () => Mode === "hover" && dispatch(closePopup(id));
const handleClick = (e) => {
- if (Mode === "click") {
- e.stopPropagation();
- dispatch(togglePopup(id));
- }
+ if (Mode !== "click") return;
+ e.stopPropagation();
+ dispatch(togglePopup(id));
};
- // Close on outside click when in click mode
+ // Close popup when clicking outside (click mode)
useEffect(() => {
if (Mode !== "click" || !visible) return;
@@ -56,9 +49,9 @@ const HoverPopup = ({
document.addEventListener("click", handler);
return () => document.removeEventListener("click", handler);
- }, [Mode, visible, dispatch, id]);
+ }, [visible, Mode, id, dispatch]);
- // Positioning: compute left/top relative to popup.offsetParent, but clamp using boundaryRef (if provided)
+ // ---------- DYNAMIC POSITIONING LOGIC ----------
useEffect(() => {
if (!visible || !popupRef.current || !triggerRef.current) return;
@@ -66,50 +59,55 @@ const HoverPopup = ({
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();
- // Find offsetParent (element the absolute popup is positioned relative to)
- const offsetParent = popup.offsetParent || document.documentElement;
- const offsetParentRect = offsetParent.getBoundingClientRect();
+ let left;
- // 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;
+ // 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)
+ );
}
- // Compute boundaries in global coordinates (use boundaryRef if provided, else offsetParent)
- const boundaryEl = (boundaryRef && boundaryRef.current) || offsetParent;
- const boundaryRect = boundaryEl.getBoundingClientRect();
+ // LEFT ALIGN
+ else if (align === "left") {
+ left = triggerRect.left - boundaryRect.left;
+ if (left + popupRect.width > boundaryRect.width) {
+ left = boundaryRect.width - popupRect.width; // clamp right
+ }
+ }
- // Clamp desiredLeftGlobal to boundaryRect
- const minLeftGlobal = boundaryRect.left;
- const maxLeftGlobal = boundaryRect.right - popupRect.width;
- let clampedLeftGlobal = Math.min(Math.max(desiredLeftGlobal, minLeftGlobal), maxLeftGlobal);
+ // RIGHT ALIGN
+ else if (align === "right") {
+ left =
+ triggerRect.left +
+ triggerRect.width -
+ boundaryRect.left -
+ popupRect.width;
- // Convert to coordinates relative to offsetParent
- const leftRelativeToOffsetParent = clampedLeftGlobal - offsetParentRect.left;
+ if (left < 0) left = 0; // clamp 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.left = `${left}px`;
+ popup.style.top = `100%`;
});
}, [visible, align, boundaryRef]);
+ // ------------------------------------------------
+
return (
e.stopPropagation()}
>
{title &&
{title}
}
-
{content}
+ {content}
)}