109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import { useState, useRef, useEffect, type ReactNode } from 'react';
|
|
|
|
interface Props {
|
|
content: string;
|
|
children: ReactNode;
|
|
}
|
|
|
|
export const Tooltip = ({ content, children }: Props) => {
|
|
const [show, setShow] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
const [positionStyle, setPositionStyle] = useState<React.CSSProperties>({
|
|
left: '50%',
|
|
transform: 'translateX(-50%)',
|
|
});
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
|
setShow(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!show) {
|
|
return;
|
|
}
|
|
|
|
// Measure and adjust positioning to prevent overflow
|
|
const adjustPosition = () => {
|
|
if (tooltipRef.current) {
|
|
const rect = tooltipRef.current.getBoundingClientRect();
|
|
const windowWidth = window.innerWidth;
|
|
const padding = 12; // safety margin from screen edges
|
|
|
|
if (rect.right > windowWidth - padding) {
|
|
setPositionStyle({
|
|
right: '0px',
|
|
left: 'auto',
|
|
transform: 'none',
|
|
});
|
|
} else if (rect.left < padding) {
|
|
setPositionStyle({
|
|
left: '0px',
|
|
transform: 'none',
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// Run adjustment immediately
|
|
adjustPosition();
|
|
|
|
// Also adjust on resize
|
|
window.addEventListener('resize', adjustPosition);
|
|
return () => window.removeEventListener('resize', adjustPosition);
|
|
}, [show]);
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
style={{ position: 'relative', display: 'inline-flex' }}
|
|
onMouseEnter={() => {
|
|
setPositionStyle({ left: '50%', transform: 'translateX(-50%)' });
|
|
setShow(true);
|
|
}}
|
|
onMouseLeave={() => setShow(false)}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (!show) {
|
|
setPositionStyle({ left: '50%', transform: 'translateX(-50%)' });
|
|
}
|
|
setShow(!show);
|
|
}}
|
|
>
|
|
{children}
|
|
{show && (
|
|
<div
|
|
ref={tooltipRef}
|
|
style={{
|
|
position: 'absolute',
|
|
bottom: '100%',
|
|
marginBottom: '8px',
|
|
backgroundColor: 'var(--bg-card)',
|
|
border: '1px solid var(--border-color)',
|
|
padding: '0.5rem 0.75rem',
|
|
borderRadius: '8px',
|
|
width: 'max-content',
|
|
maxWidth: '220px',
|
|
zIndex: 9999,
|
|
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
|
color: 'var(--text-main)',
|
|
fontSize: '0.8rem',
|
|
lineHeight: 1.4,
|
|
textAlign: 'center',
|
|
pointerEvents: 'none',
|
|
...positionStyle
|
|
}}
|
|
>
|
|
{content}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|