"use client"; import { useState, useRef, useEffect } from "react"; import { createPortal } from "react-dom"; interface InfoTooltipProps { content: React.ReactNode; /** Position relative to the trigger icon. Default: "top" */ position?: "top" | "bottom"; /** Extra width class, e.g. "w-72". Default: "w-60" */ width?: string; } /** * Small ℹ icon that shows a tooltip on hover / focus. * Rendered via a portal into document.body so it's never clipped by * ancestor overflow:hidden containers (table cells, widget cards, etc.). */ export function InfoTooltip({ content, position = "top", width = "w-60" }: InfoTooltipProps) { const [show, setShow] = useState(false); const [coords, setCoords] = useState({ top: 0, left: 0 }); const btnRef = useRef(null); function computeCoords() { if (!btnRef.current) return; const rect = btnRef.current.getBoundingClientRect(); if (position === "top") { setCoords({ top: rect.top + window.scrollY - 8, // 8px gap + arrow left: rect.left + window.scrollX + rect.width / 2, }); } else { setCoords({ top: rect.bottom + window.scrollY + 8, left: rect.left + window.scrollX + rect.width / 2, }); } } function handleShow() { computeCoords(); setShow(true); } // Recompute on scroll/resize while shown so tooltip follows the trigger useEffect(() => { if (!show) return; const update = () => computeCoords(); window.addEventListener("scroll", update, true); window.addEventListener("resize", update); return () => { window.removeEventListener("scroll", update, true); window.removeEventListener("resize", update); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [show]); const tooltipStyle: React.CSSProperties = position === "top" ? { position: "fixed", top: coords.top, left: coords.left, transform: "translate(-50%, -100%)" } : { position: "fixed", top: coords.top, left: coords.left, transform: "translateX(-50%)" }; const arrowClass = position === "top" ? "top-full border-t-gray-900 border-l-transparent border-r-transparent border-b-transparent border-l-4 border-r-4 border-t-4 border-b-0" : "bottom-full border-b-gray-900 border-l-transparent border-r-transparent border-t-transparent border-l-4 border-r-4 border-b-4 border-t-0"; return ( {show && createPortal(
{content}
, document.body, )}
); }