Adding HoverPopup effect.
This commit is contained in:
parent
57bbcd4b45
commit
8dbdd230af
@ -205,7 +205,7 @@ const TaskReportList = () => {
|
||||
id="total_pending_task"
|
||||
title="Total Pending Task"
|
||||
content={
|
||||
<div className="text-wrap" style={{ maxWidth: "200px" }}>
|
||||
<div className="text-wrap" style={{ minWidth: "200px" }}>
|
||||
This shows the total pending tasks for each activity on that date.
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ const ManageJobTicket = ({ Job }) => {
|
||||
id="STATUS_CHANEG"
|
||||
Mode="click"
|
||||
className=""
|
||||
align="right"
|
||||
align="left"
|
||||
content={
|
||||
<ChangeStatus
|
||||
statusId={data?.status?.id}
|
||||
@ -141,7 +141,7 @@ const ManageJobTicket = ({ Job }) => {
|
||||
})()}
|
||||
</div>
|
||||
{data?.projectBranch && (
|
||||
<div className="d-flex gap-3 my-2 position-relative" ref={drawerRef} >
|
||||
<div className="d-flex gap-3 my-2 position-relative" ref={drawerRef} >
|
||||
<span className="text-secondary">
|
||||
<i className="bx bx-buildings"></i> Branch Name:
|
||||
</span>
|
||||
@ -149,7 +149,8 @@ const ManageJobTicket = ({ Job }) => {
|
||||
<HoverPopup
|
||||
id="BRANCH_DETAILS"
|
||||
Mode="click"
|
||||
align="auto"
|
||||
align="right"
|
||||
minWidth="340px"
|
||||
boundaryRef={drawerRef}
|
||||
content={<BranchDetails branch={data?.projectBranch?.id} />}
|
||||
>
|
||||
|
||||
@ -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 (
|
||||
<div className="d-inline-block position-relative" style={{ overflow: "visible" }}>
|
||||
<div
|
||||
@ -128,15 +126,14 @@ const HoverPopup = ({
|
||||
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`}
|
||||
style={{
|
||||
zIndex: 2000,
|
||||
minWidth: "200px",
|
||||
maxWidth: "300px",
|
||||
minWidth,
|
||||
maxWidth,
|
||||
wordWrap: "break-word",
|
||||
// we set left/top from JS; ensure positionAbsolute context exists via offsetParent
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{title && <h6 className="fw-semibold mb-2">{title}</h6>}
|
||||
<div>{content}</div>
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user