From 57bbcd4b451c947e3fbe2791096931d19941cd3a Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 29 Nov 2025 10:26:08 +0530 Subject: [PATCH 1/2] Correction in HoverPopup. --- src/components/common/HoverPopup.jsx | 82 ++++++++++++++++++---------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/components/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx index 232ced5d..0afb63e0 100644 --- a/src/components/common/HoverPopup.jsx +++ b/src/components/common/HoverPopup.jsx @@ -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 ( -
+
e.stopPropagation()} > @@ -118,7 +141,6 @@ const HoverPopup = ({ )}
); - }; export default HoverPopup; From 8dbdd230af36a345979cf7fa5b6d1642cffcbdc9 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 29 Nov 2025 12:47:07 +0530 Subject: [PATCH 2/2] Adding HoverPopup effect. --- .../DailyProgressRport/TaskReportList.jsx | 2 +- .../ServiceProjectJob/ManageJobTicket.jsx | 7 +- src/components/common/HoverPopup.jsx | 117 +++++++++--------- 3 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/components/DailyProgressRport/TaskReportList.jsx b/src/components/DailyProgressRport/TaskReportList.jsx index bcb4232b..f812937c 100644 --- a/src/components/DailyProgressRport/TaskReportList.jsx +++ b/src/components/DailyProgressRport/TaskReportList.jsx @@ -205,7 +205,7 @@ const TaskReportList = () => { id="total_pending_task" title="Total Pending Task" content={ -
+
This shows the total pending tasks for each activity on that date.
} diff --git a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx index d6bff3e3..d2647649 100644 --- a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx +++ b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx @@ -69,7 +69,7 @@ const ManageJobTicket = ({ Job }) => { id="STATUS_CHANEG" Mode="click" className="" - align="right" + align="left" content={ { })()}
{data?.projectBranch && ( -
+
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}
)}