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