All files / src/components/ui tooltip.tsx

100% Statements 15/15
100% Branches 8/8
100% Functions 7/7
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                      17x 17x   17x 6x 6x     17x 3x     17x 9x 9x       17x                                                                   3x            
import { useState, useRef, useEffect, type ReactNode } from "react";
import { Info } from "lucide-react";
import { cn } from "../../lib/utils";
 
interface TooltipProps {
	content: string;
	children?: ReactNode;
	className?: string;
}
 
export function Tooltip({ content, children, className }: TooltipProps) {
	const [isVisible, setIsVisible] = useState(false);
	const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
 
	const show = () => {
		if (timeoutRef.current) clearTimeout(timeoutRef.current);
		setIsVisible(true);
	};
 
	const hide = () => {
		timeoutRef.current = setTimeout(() => setIsVisible(false), 150);
	};
 
	useEffect(() => {
		return () => {
			if (timeoutRef.current) clearTimeout(timeoutRef.current);
		};
	}, []);
 
	return (
		<span className={cn("relative inline-flex items-center", className)}>
			<button
				type="button"
				onMouseEnter={show}
				onMouseLeave={hide}
				onFocus={show}
				onBlur={hide}
				aria-label="More info"
				className="inline-flex cursor-help"
			>
				{children || (
					<Info className="h-4 w-4 text-foreground-subtle hover:text-foreground-muted transition-colors" />
				)}
			</button>
			{isVisible && (
				<span
					role="tooltip"
					className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 text-xs text-foreground-inverse bg-foreground-default rounded-md shadow-lg whitespace-normal max-w-[240px] z-50 pointer-events-none"
				>
					{content}
					<span className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-foreground-default" />
				</span>
			)}
		</span>
	);
}
 
interface HelpTextProps {
	children: ReactNode;
	className?: string;
}
 
export function HelpText({ children, className }: HelpTextProps) {
	return (
		<p className={cn("mt-1 text-xs text-foreground-subtle", className)}>
			{children}
		</p>
	);
}