From 64671ae7e5bfe31f6866615c8b62332cd4dd0964 Mon Sep 17 00:00:00 2001 From: Marvell69 Date: Thu, 23 Apr 2026 22:23:16 +0100 Subject: [PATCH] style: improve mobile responsiveness of Tooltip Update tooltip responsive layout and add a Playwright regression test for mobile/desktop visibility. --- frontend/src/components/InfoTooltip.tsx | 52 +++++++++++++---- .../tests/e2e/tooltip-responsiveness.spec.ts | 56 +++++++++++++++++++ 2 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 frontend/tests/e2e/tooltip-responsiveness.spec.ts diff --git a/frontend/src/components/InfoTooltip.tsx b/frontend/src/components/InfoTooltip.tsx index aa8aa355..27cee6f0 100644 --- a/frontend/src/components/InfoTooltip.tsx +++ b/frontend/src/components/InfoTooltip.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useRef, useEffect } from "react"; +import React, { useId, useState, useRef, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; interface InfoTooltipProps { @@ -16,30 +16,57 @@ export function InfoTooltip({ }: InfoTooltipProps) { const [isVisible, setIsVisible] = useState(false); const triggerRef = useRef(null); + const contentRef = useRef(null); + const tooltipId = useId(); // Close on escape useEffect(() => { + if (!isVisible) return; + const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") setIsVisible(false); }; - if (isVisible) { - window.addEventListener("keydown", handleKeyDown); - } - return () => window.removeEventListener("keydown", handleKeyDown); + + const handleOutsidePointer = (event: MouseEvent | TouchEvent) => { + if ( + triggerRef.current && + contentRef.current && + !triggerRef.current.contains(event.target as Node) && + !contentRef.current.contains(event.target as Node) + ) { + setIsVisible(false); + } + }; + + window.addEventListener("keydown", handleKeyDown); + document.addEventListener("mousedown", handleOutsidePointer); + document.addEventListener("touchstart", handleOutsidePointer); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("mousedown", handleOutsidePointer); + document.removeEventListener("touchstart", handleOutsidePointer); + }; }, [isVisible]); return ( -
+
setIsVisible(true)} + onMouseLeave={() => setIsVisible(false)} + onFocusCapture={() => setIsVisible(true)} + onBlurCapture={() => setIsVisible(false)} + > @@ -47,13 +74,14 @@ export function InfoTooltip({ {isVisible && (
{content}
{/* Arrow */} diff --git a/frontend/tests/e2e/tooltip-responsiveness.spec.ts b/frontend/tests/e2e/tooltip-responsiveness.spec.ts new file mode 100644 index 00000000..f9838fe4 --- /dev/null +++ b/frontend/tests/e2e/tooltip-responsiveness.spec.ts @@ -0,0 +1,56 @@ +import { expect, test, type Page } from "@playwright/test"; + +const MERCHANT_API_KEY = "sk_test_tooltip_responsiveness_key"; + +async function seedMerchantSession(page: Page) { + await page.addInitScript( + ({ apiKey, token }) => { + window.localStorage.setItem("merchant_api_key", apiKey); + window.localStorage.setItem("merchant_token", token); + document.cookie = "NEXT_LOCALE=en; path=/"; + }, + { + apiKey: MERCHANT_API_KEY, + token: "eyJhbGciOiJub25lIn0.eyJpZCI6InRlc3QtaWQiLCJlbWFpbCI6InRlc3RAdGx1dG8uY2MiLCJleHAiOjE3NzY5ODI2ODl9.", + }, + ); +} + +async function mockHealthApi(page: Page) { + await page.route("**/api/health", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ status: "ok" }), + }); + }); +} + +test.describe("Tooltip responsiveness", () => { + test.beforeEach(async ({ page }) => { + await seedMerchantSession(page); + await mockHealthApi(page); + }); + + test("shows tooltip content and keeps it inside the viewport", async ({ page }) => { + await page.goto("/dashboard/create"); + + const tooltipButton = page.getByRole("button", { name: "More information" }).first(); + await tooltipButton.click(); + + const tooltip = page.getByRole("tooltip"); + await expect(tooltip).toBeVisible({ timeout: 10000 }); + + const box = await tooltip.boundingBox(); + const viewport = page.viewportSize(); + + expect(box).not.toBeNull(); + expect(viewport).not.toBeNull(); + expect(box!.x).toBeGreaterThanOrEqual(0); + expect(box!.y).toBeGreaterThanOrEqual(0); + expect(box!.x + box!.width).toBeLessThanOrEqual(viewport!.width); + expect(box!.y + box!.height).toBeLessThanOrEqual(viewport!.height); + + await expect(tooltip).toHaveCSS("background-color", "rgba(255, 255, 255, 0.95)"); + }); +});