import { useState, useRef, useEffect, type ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface TooltipProps {
content: ReactNode
children: ReactNode
position?: 'top' | 'bottom' | 'left' | 'right'
className?: string
}
const positionStyles: Record<NonNullable<TooltipProps['position']>, 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',
}
/** 悬浮提示 —— 200ms 延迟显示,反转色气泡 */
export function Tooltip({
content,
children,
position = 'top',
className,
}: TooltipProps) {
const [visible, setVisible] = useState(false)
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
const show = () => {
timerRef.current = setTimeout(() => setVisible(true), 200)
}
const hide = () => {
if (timerRef.current) {
clearTimeout(timerRef.current)
timerRef.current = undefined
}
setVisible(false)
}
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current)
}
}, [])
return (
<div
className="relative inline-flex"
onMouseEnter={show}
onMouseLeave={hide}
onFocus={show}
onBlur={hide}
>
{children}
{visible && (
<div
role="tooltip"
className={cn(
'absolute z-50 px-2.5 py-1.5 rounded-lg text-xs whitespace-nowrap',
'bg-gray-800 dark:bg-gray-200',
'text-white dark:text-gray-800',
'shadow-[var(--shadow-tooltip)]',
'animate-fade-in pointer-events-none',
positionStyles[position],
className,
)}
>
{content}
</div>
)}
</div>
)
}
|