espace-paie-odentas/components/ui/tooltip.tsx

116 lines
No EOL
3.6 KiB
TypeScript

"use client"
import * as React from "react"
import { createPortal } from "react-dom"
import { cn } from "@/lib/utils"
interface TooltipProps {
children: React.ReactNode
content: string
className?: string
side?: 'top' | 'bottom' | 'left' | 'right'
asChild?: boolean
}
export function Tooltip({ children, content, className, side = 'top', asChild = false }: TooltipProps) {
const [isVisible, setIsVisible] = React.useState(false)
const [triggerRect, setTriggerRect] = React.useState<DOMRect | null>(null)
const triggerRef = React.useRef<HTMLDivElement>(null)
const updatePosition = () => {
if (triggerRef.current) {
setTriggerRect(triggerRef.current.getBoundingClientRect())
}
}
const handleMouseEnter = () => {
updatePosition()
setIsVisible(true)
}
const handleMouseLeave = () => {
setIsVisible(false)
}
const getTooltipStyle = (): React.CSSProperties => {
if (!triggerRect) return {}
const offset = 8
let left = 0
let top = 0
switch (side) {
case 'top':
left = triggerRect.left + triggerRect.width / 2
top = triggerRect.top - offset
break
case 'bottom':
left = triggerRect.left + triggerRect.width / 2
top = triggerRect.bottom + offset
break
case 'left':
left = triggerRect.left - offset
top = triggerRect.top + triggerRect.height / 2
break
case 'right':
left = triggerRect.right + offset
top = triggerRect.top + triggerRect.height / 2
break
}
// Position the tooltip relative to the computed anchor point
// For 'top', anchor is at the bottom-center of the tooltip (so translateY(-100%))
// For 'bottom', anchor is at the top-center (no vertical translation)
// For 'left'/'right', anchor centered vertically.
let transform = ''
if (side === 'top') transform = 'translate(-50%, -100%)'
else if (side === 'bottom') transform = 'translate(-50%, 0)'
else if (side === 'left') transform = 'translate(-100%, -50%)'
else transform = 'translate(0, -50%)'
return {
position: 'fixed',
left: `${left}px`,
top: `${top}px`,
transform,
zIndex: 9999
}
}
const arrowClasses = {
top: 'top-full left-1/2 transform -translate-x-1/2 border-t-gray-900 border-t-4 border-x-transparent border-x-4 border-b-0',
bottom: 'bottom-full left-1/2 transform -translate-x-1/2 border-b-gray-900 border-b-4 border-x-transparent border-x-4 border-t-0',
left: 'left-full top-1/2 transform -translate-y-1/2 border-l-gray-900 border-l-4 border-y-transparent border-y-4 border-r-0',
right: 'right-full top-1/2 transform -translate-y-1/2 border-r-gray-900 border-r-4 border-y-transparent border-y-4 border-l-0'
}
return (
<>
<div
ref={triggerRef}
className={asChild ? "block w-full" : "inline-block"}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onFocus={handleMouseEnter}
onBlur={handleMouseLeave}
>
{children}
</div>
{isVisible && triggerRect && typeof window !== 'undefined' && createPortal(
<div
style={getTooltipStyle()}
className={cn(
"px-3 py-2 text-sm text-white bg-gray-900 rounded-lg shadow-xl whitespace-nowrap pointer-events-none relative",
"animate-in fade-in-0 zoom-in-95 duration-150",
className
)}
>
{content}
<div className={cn("absolute w-0 h-0", arrowClasses[side])} />
</div>,
document.body
)}
</>
)
}