import * as React from 'react'; import * as TooltipPrimitive from '@radix-ui/react-tooltip'; import { useControllableState } from '@radix-ui/react-use-controllable-state'; import { tv, type VariantProps } from 'tailwind-variants'; import { type VisibleState } from '../../types'; import { cn } from '../../utils/cn'; const TooltipProvider = TooltipPrimitive.Provider; const TooltipRoot = TooltipPrimitive.Root; const TooltipPortal = TooltipPrimitive.Portal; const TooltipTrigger = TooltipPrimitive.Trigger; export const tooltipContentVariants = tv({ base: [ 'max-w-xxs flex flex-col items-start self-stretch rounded-md px-3 py-2', 'z-100 overflow-hidden text-xs font-semibold shadow-lg', 'animate-in fade-in-0 zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', ], variants: { border: { default: '', outlined: 'border-border-secondary border', }, theme: { default: 'bg-background text-foreground', light: 'bg-white text-gray-900', dark: 'bg-gray-900 text-white', }, }, defaultVariants: { theme: 'default', border: 'default', }, }); export interface TooltipContentProps extends Omit, 'title' | 'content'>, VariantProps {} const TooltipContent = React.forwardRef, TooltipContentProps>( ({ className, sideOffset = 4, border = 'default', theme = 'light', ...props }, ref) => ( ) ); TooltipContent.displayName = TooltipPrimitive.Content.displayName; export interface TooltipProps extends TooltipContentProps, VisibleState { title?: React.ReactNode; content?: React.ReactNode; contentClassName?: string; outlined?: string; /** * The duration from when the pointer enters the trigger until the tooltip gets opened. This will * override the prop with the same name passed to Provider. * @defaultValue 300 */ delayDuration?: number; /** * When `true`, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger. * @defaultValue false */ disableHoverableContent?: boolean; arrow?: boolean; } const Tooltip = React.forwardRef, TooltipProps>( ( { title, content, children, open: openProp, defaultOpen, onOpenChange, theme = 'default', delayDuration = 200, disableHoverableContent, arrow, contentClassName, ...contentProps }, ref ) => { const openByClickRef = React.useRef(false); const [open = false, setOpen] = useControllableState({ prop: openProp, defaultProp: defaultOpen, onChange: onOpenChange, }); const handleOpenByClick = () => { setOpen(true); openByClickRef.current = true; setTimeout(() => { openByClickRef.current = false; }, delayDuration); }; const handleOpenChange = (isOpen?: boolean) => { if (openByClickRef.current) { openByClickRef.current = false; return; } setOpen(isOpen); }; return ( {children} {title} {content && (
{content}
)} {arrow && ( )}
); } ); Tooltip.displayName = 'Tooltip'; export { Tooltip, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger };