From 306c670727320a79d4c537f4dfd1ad4274b5acb9 Mon Sep 17 00:00:00 2001 From: gearnode Date: Thu, 20 Mar 2025 15:10:57 +0100 Subject: [PATCH] Fix flickering on hover on categories close #56 Signed-off-by: gearnode --- .../src/pages/FrameworkOverviewPage.tsx | 196 +++++++++--------- 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/apps/console/src/pages/FrameworkOverviewPage.tsx b/apps/console/src/pages/FrameworkOverviewPage.tsx index 780d658bc..e234787cc 100644 --- a/apps/console/src/pages/FrameworkOverviewPage.tsx +++ b/apps/console/src/pages/FrameworkOverviewPage.tsx @@ -1,4 +1,4 @@ -import { Suspense, useEffect, useState } from "react"; +import { Suspense, useEffect, useState, useRef } from "react"; import { useParams, useNavigate, Link } from "react-router"; import { graphql, @@ -11,7 +11,6 @@ import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import type { FrameworkOverviewPageQuery as FrameworkOverviewPageQueryType } from "./__generated__/FrameworkOverviewPageQuery.graphql"; import { Helmet } from "react-helmet-async"; -import { createPortal } from "react-dom"; import { PageHeader } from "./PageHeader"; const FrameworkOverviewPageQuery = graphql` @@ -39,6 +38,100 @@ const FrameworkOverviewPageQuery = graphql` } `; +function ControlSquare({ + control, + onClick, +}: { + control: { + id?: string; + name?: string; + description?: string; + state?: string; + category?: string; + importance?: string; + }; + onClick: () => void; +}) { + const [showTooltip, setShowTooltip] = useState(false); + const squareRef = useRef(null); + const tooltipRef = useRef(null); + + const truncateDescription = (text?: string, maxLength = 100) => { + if (!text) return ""; + return text.length > maxLength + ? `${text.substring(0, maxLength)}...` + : text; + }; + + // Calculate tooltip position when it becomes visible + useEffect(() => { + if (showTooltip && squareRef.current && tooltipRef.current) { + const squareRect = squareRef.current.getBoundingClientRect(); + const tooltipRect = tooltipRef.current.getBoundingClientRect(); + + // Position tooltip above the square + const top = squareRect.top - tooltipRect.height - 10; + const left = + squareRect.left + squareRect.width / 2 - tooltipRect.width / 2; + + // Adjust if tooltip would go off screen + const adjustedLeft = Math.max( + 10, + Math.min(left, window.innerWidth - tooltipRect.width - 10) + ); + + tooltipRef.current.style.top = `${top}px`; + tooltipRef.current.style.left = `${adjustedLeft}px`; + } + }, [showTooltip]); + + return ( +
+
setShowTooltip(true)} + onMouseLeave={() => setShowTooltip(false)} + onClick={onClick} + /> + + {showTooltip && ( +
+
+
+
+ 30 min +
+
+ {control?.importance} +
+
+ +
+
{control?.name}
+
+ {truncateDescription(control?.description)} +
+
+
+
+
+ + {control?.state === "IMPLEMENTED" ? "Validated" : "Not validated"} + +
+
+ )} +
+ ); +} + function FrameworkOverviewPageContent({ queryRef, }: { @@ -47,18 +140,9 @@ function FrameworkOverviewPageContent({ const data = usePreloadedQuery(FrameworkOverviewPageQuery, queryRef); const framework = data.node; const controls = framework.controls?.edges.map((edge) => edge?.node) ?? []; - const [hoveredCard, setHoveredCard] = useState(null); - const [hoveredControl, setHoveredControl] = useState(null); - const [tooltipPosition, setTooltipPosition] = useState<{ - x: number; - y: number; - width: number; - } | null>(null); - const [isTooltipHovered, setIsTooltipHovered] = useState(false); const navigate = useNavigate(); const { organizationId } = useParams(); - // Group controls by their category const controlsByCategory = controls.reduce((acc, control) => { if (!control?.category) return acc; if (!acc[control.category]) { @@ -136,25 +220,9 @@ function FrameworkOverviewPageContent({ .map((_, i) => { const control = card.controls[i]; return ( -
{ - setHoveredCard(index); - setHoveredControl(i); - const rect = - e.currentTarget.getBoundingClientRect(); - - setTooltipPosition({ - x: rect.left, - y: rect.top - 10, - width: 400, - }); - }} + control={control} onClick={() => { if (control?.id) { navigate( @@ -162,16 +230,6 @@ function FrameworkOverviewPageContent({ ); } }} - onMouseLeave={() => { - setTimeout(() => { - if (!isTooltipHovered) { - setHoveredCard(null); - setHoveredControl(null); - setTooltipPosition(null); - } - }, 500); - }} - title={control?.name} /> ); })} @@ -187,66 +245,6 @@ function FrameworkOverviewPageContent({
))}
- - {hoveredCard !== null && - hoveredControl !== null && - tooltipPosition && - createPortal( -
setIsTooltipHovered(true)} - onMouseLeave={() => { - setIsTooltipHovered(false); - setHoveredCard(null); - setHoveredControl(null); - setTooltipPosition(null); - }} - style={{ - left: tooltipPosition.x, - transform: `translate(-50%, -100%)`, - top: tooltipPosition.y - 20, - width: tooltipPosition.width + "px", - }} - > -
-
-
-
- 30 min -
-
- { - controlCards[hoveredCard]?.controls[hoveredControl] - ?.importance - } -
-
- -
-
- {controlCards[hoveredCard]?.controls[hoveredControl]?.name} -
-
- { - controlCards[hoveredCard]?.controls[hoveredControl] - ?.description - } -
-
-
-
-
- - {controlCards[hoveredCard]?.controls[hoveredControl] - ?.state === "IMPLEMENTED" - ? "Validated" - : "Not validated"} - -
-
-
, - document.body - )}
); }