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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 2x 1x 1x 1x 1x 2x 2x 2x 2x 2x 19x 19x 19x 19x 19x 19x 19x 2x 2x 2x 2x 2x 2x 22x 22x 2x 2x 19x 19x 19x 19x 19x 19x 19x 19x 19x 2x 2x 22x 22x 22x 22x 22x 22x 22x 2x 2x 22x 6x 6x 18x 18x 18x 18x 6x 6x 6x 6x 18x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 1x | import React, {
createContext,
useContext,
useState,
ReactNode,
FC,
} from 'react';
import { cn } from '../lib/utils';
type TooltipContextType = {
isVisible: boolean;
show: () => void;
hide: () => void;
};
// 1. Create context
const TooltipContext = createContext<TooltipContextType | undefined>(undefined);
// 2. The context provider—manages the hover state
const TooltipProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [isVisible, setIsVisible] = useState(false);
const show = () => setIsVisible(true);
const hide = () => setIsVisible(false);
return (
<TooltipContext.Provider value={{ isVisible, show, hide }}>
{children}
</TooltipContext.Provider>
);
};
// 3. Root wrapper—sets up relative positioning & provider
const Tooltip: FC<{ children: ReactNode }> = ({ children }) => (
<div className='relative inline-block'>
<TooltipProvider>{children}</TooltipProvider>
</div>
);
// 4. Trigger—automatically wires up mouse events
const TooltipTrigger: FC<{ children: ReactNode }> = ({ children }) => {
const ctx = useContext(TooltipContext);
if (!ctx) {
throw new Error('TooltipTrigger must be inside <Tooltip>…</Tooltip>');
}
return (
<div
className='cursor-pointer'
onMouseEnter={ctx.show}
onMouseLeave={ctx.hide}
onFocus={ctx.show}
onBlur={ctx.hide}
>
{children}
</div>
);
};
interface TooltipContentProps {
children: ReactNode;
className?: string;
side?: 'top' | 'bottom' | 'left' | 'right';
sideOffset?: number; // in pixels
}
// 5. Content—reads state from context, no more required prop
const TooltipContent: FC<TooltipContentProps> = ({
children,
className,
side = 'top',
sideOffset = 8,
}) => {
const ctx = useContext(TooltipContext);
if (!ctx) {
throw new Error('TooltipContent must be inside <Tooltip>…</Tooltip>');
}
if (!ctx.isVisible) return null;
// build a little inline style for the offset
const offsetStyle: React.CSSProperties = {};
if (side === 'top') offsetStyle.marginBottom = sideOffset;
if (side === 'bottom') offsetStyle.marginTop = sideOffset;
if (side === 'left') offsetStyle.marginRight = sideOffset;
if (side === 'right') offsetStyle.marginLeft = sideOffset;
return (
<div
className={cn(
'absolute z-50 w-max max-w-xs bg-primary text-primary-foreground text-xs rounded-md px-3 py-1.5 shadow-md animate-fade-in',
side === 'top' && 'bottom-full left-1/2 transform -translate-x-1/2',
side === 'bottom' && 'top-full left-1/2 transform -translate-x-1/2',
side === 'left' && 'right-full top-1/2 transform -translate-y-1/2',
side === 'right' && 'left-full top-1/2 transform -translate-y-1/2',
className
)}
style={offsetStyle}
>
{children}
<div
className={cn(
'absolute h-2 w-2 bg-primary transform rotate-45',
side === 'top' && 'bottom-[-4px] left-1/2 transform -translate-x-1/2',
side === 'bottom' && 'top-[-4px] left-1/2 transform -translate-x-1/2',
side === 'left' && 'right-[-4px] top-1/2 transform -translate-y-1/2',
side === 'right' && 'left-[-4px] top-1/2 transform -translate-y-1/2'
)}
/>
</div>
);
};
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|