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