File size: 1,464 Bytes
d15d7f7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import { useState, useRef, useCallback, useEffect, type ReactNode } from "react";
import { computePosition, offset, flip, shift, type Placement } from "@floating-ui/dom";
interface TooltipProps {
title: string;
placement?: Placement;
children: ReactNode;
}
export function Tooltip({ title, placement = "top", children }: TooltipProps) {
const triggerRef = useRef<HTMLSpanElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
const reposition = useCallback(() => {
const trigger = triggerRef.current;
const tip = tooltipRef.current;
if (!trigger || !tip) return;
computePosition(trigger, tip, {
placement,
strategy: "fixed",
middleware: [offset(6), flip(), shift({ padding: 8 })],
}).then(({ x, y }) => {
tip.style.left = `${x}px`;
tip.style.top = `${y}px`;
});
}, [placement]);
useEffect(() => {
if (open) reposition();
}, [open, reposition]);
if (!title) return <>{children}</>;
return (
<>
<span
ref={triggerRef}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onFocus={() => setOpen(true)}
onBlur={() => setOpen(false)}
style={{ display: "inline-flex" }}
>
{children}
</span>
{open && (
<div ref={tooltipRef} className="ed-tooltip" role="tooltip">
{title}
</div>
)}
</>
);
}
|