Files
davisfe.cz/src/components/Tooltip.tsx
T

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>
);
};