Files
panel/src/components/ui/Tooltip.js
RKWojs fd87b66b06 feat: Implement dark mode support across components and UI elements
- Added dark mode styles to TaskStatusDropdown, TaskStatusDropdownDebug, and TaskStatusDropdownSimple components.
- Introduced ThemeProvider and useTheme hook for managing theme state.
- Updated Button, Card, Input, Loading, Navigation, PageContainer, PageHeader, ProjectCalendarWidget, ProjectMap, SearchBar, States, Tooltip, and other UI components to support dark mode.
- Created ThemeToggle component for switching between light and dark modes.
- Enhanced i18n translations for settings related to theme and language preferences.
- Configured Tailwind CSS to support dark mode with class-based toggling.
2025-09-25 08:58:03 +02:00

96 lines
2.6 KiB
JavaScript

"use client";
import { useState, useRef, useEffect } from "react";
import { createPortal } from "react-dom";
export default function Tooltip({ children, content, className = "" }) {
const [isVisible, setIsVisible] = useState(false);
const [position, setPosition] = useState({ top: 0, left: 0 });
const triggerRef = useRef(null);
const tooltipRef = useRef(null);
const updatePosition = () => {
if (triggerRef.current && tooltipRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
const scrollY = window.scrollY;
const scrollX = window.scrollX;
// Calculate position (above the trigger by default)
let top = triggerRect.top + scrollY - tooltipRect.height - 8;
let left = triggerRect.left + scrollX + (triggerRect.width / 2) - (tooltipRect.width / 2);
// Keep tooltip within viewport
if (left < 10) left = 10;
if (left + tooltipRect.width > window.innerWidth - 10) {
left = window.innerWidth - tooltipRect.width - 10;
}
// If tooltip would go above viewport, show below instead
if (top < scrollY + 10) {
top = triggerRect.bottom + scrollY + 8;
}
setPosition({ top, left });
}
};
const handleMouseEnter = () => {
setIsVisible(true);
};
const handleMouseLeave = () => {
setIsVisible(false);
};
useEffect(() => {
if (isVisible) {
// Small delay to ensure tooltip is rendered before positioning
const timer = setTimeout(() => {
updatePosition();
}, 10);
const handleScroll = () => updatePosition();
const handleResize = () => updatePosition();
window.addEventListener("scroll", handleScroll, true);
window.addEventListener("resize", handleResize);
return () => {
clearTimeout(timer);
window.removeEventListener("scroll", handleScroll, true);
window.removeEventListener("resize", handleResize);
};
}
}, [isVisible]);
const tooltip = isVisible && (
<div
ref={tooltipRef}
className={`fixed z-50 px-3 py-2 text-sm bg-gray-900 dark:bg-gray-700 text-white rounded-lg shadow-lg border border-gray-700 dark:border-gray-600 max-w-sm ${className}`}
style={{
top: position.top,
left: position.left,
}}
>
{content}
{/* Arrow pointing down */}
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
</div>
);
return (
<>
<span
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className="inline-block"
>
{children}
</span>
{typeof document !== "undefined" && createPortal(tooltip, document.body)}
</>
);
}