File size: 2,914 Bytes
9b72f0d
 
 
 
 
97ce2e0
 
9b72f0d
 
 
 
 
97ce2e0
9b72f0d
 
 
 
 
 
 
97ce2e0
9b72f0d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97ce2e0
 
 
 
 
 
 
 
9b72f0d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3624cee
9b72f0d
3624cee
9b72f0d
 
 
 
 
 
 
 
 
 
 
97ce2e0
9b72f0d
 
 
 
 
 
 
 
 
 
 
 
 
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import cn from "@utils/classnames.ts";
import { type ReactNode, useRef, useState } from "react";

type TooltipPosition = "top" | "bottom" | "left" | "right";

type TextSize = "xs" | "sm" | "md" | "lg" | "xl";

export interface TooltipProps {
  children: ReactNode;
  text: ReactNode;
  position?: TooltipPosition;
  className?: string;
  textSize?: TextSize;
}

export default function Tooltip({
  children,
  text,
  position = "top",
  className = "",
  textSize = "md",
}: TooltipProps) {
  const [isVisible, setIsVisible] = useState(false);
  const timeoutRef = useRef<number>(null);

  const positionClasses: Record<TooltipPosition, string> = {
    top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
    bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
    left: "right-full top-1/2 -translate-y-1/2 mr-2",
    right: "left-full top-1/2 -translate-y-1/2 ml-2",
  };

  const arrowClasses: Record<TooltipPosition, string> = {
    top: "top-full left-1/2 -translate-x-1/2 border-l-transparent border-r-transparent border-b-transparent border-t-gray-900 dark:border-t-gray-100",
    bottom:
      "bottom-full left-1/2 -translate-x-1/2 border-l-transparent border-r-transparent border-t-transparent border-b-gray-900 dark:border-b-gray-100",
    left: "left-full top-1/2 -translate-y-1/2 border-t-transparent border-b-transparent border-r-transparent border-l-gray-900 dark:border-l-gray-100",
    right:
      "right-full top-1/2 -translate-y-1/2 border-t-transparent border-b-transparent border-l-transparent border-r-gray-900 dark:border-r-gray-100",
  };

  const textSizeClass: Record<TextSize, string> = {
    xs: "text-xs",
    sm: "text-sm",
    md: "text-base",
    lg: "text-lg",
    xl: "text-xl",
  };

  const showTooltip = () => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
    setIsVisible(true);
  };

  const hideTooltip = () => {
    timeoutRef.current = window.setTimeout(() => {
      setIsVisible(false);
    }, 100);
  };

  return (
    <div
      className={cn("relative inline-block", className)}
      onMouseEnter={showTooltip}
      onMouseLeave={hideTooltip}
      onFocus={showTooltip}
      onBlur={hideTooltip}
    >
      <span tabIndex={0} className="block">
        {children}
      </span>
      {isVisible && (
        <div
          className={cn(
            "absolute z-50",
            positionClasses[position],
            "animate-fadeIn"
          )}
          role="tooltip"
        >
          <div
            className={cn(
              textSizeClass[textSize],
              "rounded bg-gray-900 px-3 py-2 text-sm whitespace-nowrap text-white dark:bg-gray-100 dark:text-gray-900"
            )}
          >
            {text}
          </div>
          <div
            className={cn("absolute h-0 w-0 border-4", arrowClasses[position])}
          />
        </div>
      )}
    </div>
  );
}