keep tooltip inside the viewport

This commit is contained in:
Gavin McDonald
2025-04-15 17:22:27 -04:00
parent 8f1430d12b
commit 4c5eb541bc

View File

@@ -1,11 +1,14 @@
'use client'; 'use client';
import React, { useRef, useState } from 'react'; import { useRef, useState, ReactNode } from 'react';
type TooltipProps = { type TooltipProps = {
children: React.ReactNode; children: React.ReactNode;
content: React.ReactNode; content: React.ReactNode;
delay?: number; delay?: number;
mobileDelay?: number; mobileDelay?: number;
offsetX?: number;
offsetY?: number;
edgeBuffer?: number;
}; };
export default function Tooltip({ export default function Tooltip({
@@ -13,7 +16,11 @@ export default function Tooltip({
content, content,
delay = 500, delay = 500,
mobileDelay = 500, mobileDelay = 500,
offsetX = 20,
offsetY = 20,
edgeBuffer = 10,
}: TooltipProps) { }: TooltipProps) {
const ttRef = useRef<HTMLDivElement | null>(null);
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const [pos, setPos] = useState({ x: 0, y: 0 }); const [pos, setPos] = useState({ x: 0, y: 0 });
const delayTimeout = useRef<NodeJS.Timeout | null>(null); const delayTimeout = useRef<NodeJS.Timeout | null>(null);
@@ -29,7 +36,14 @@ export default function Tooltip({
}; };
const handleMouseMove = (e: React.MouseEvent) => { const handleMouseMove = (e: React.MouseEvent) => {
setPos({ x: e.clientX, y: e.clientY }); const { clientX: x, clientY: y } = e;
const ttHeight = ttRef.current?.offsetHeight || 0;
const viewportHeight = window.innerHeight - edgeBuffer;
const ttBottom = ttHeight + offsetY + y;
const adjustment = ttBottom > viewportHeight ? ttBottom - viewportHeight : 0;
setPos({ x, y: y - adjustment });
}; };
const handleTouchStart = () => { const handleTouchStart = () => {
@@ -53,10 +67,11 @@ export default function Tooltip({
{children} {children}
</div> </div>
<div <div
ref={ttRef}
className={`fixed w-[25vh] pointer-events-none z-50 text-xs bg-black text-white rounded border border-gray-300 px-2 py-1 transition-opacity duration-250 ${show ? 'opacity-100' : 'opacity-0'}`} className={`fixed w-[25vh] pointer-events-none z-50 text-xs bg-black text-white rounded border border-gray-300 px-2 py-1 transition-opacity duration-250 ${show ? 'opacity-100' : 'opacity-0'}`}
style={{ style={{
top: `${pos.y + 20}px`, top: `${pos.y + offsetY}px`,
left: `${pos.x + 20}px`, left: `${pos.x + offsetX}px`,
}} }}
> >
{content} {content}