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>
      )}
    </>
  );
}