turash/bugulma/frontend/components/ui/Tooltip.tsx
Damir Mukimov 673e8d4361
Some checks failed
CI/CD Pipeline / backend-lint (push) Failing after 31s
CI/CD Pipeline / backend-build (push) Has been skipped
CI/CD Pipeline / frontend-lint (push) Failing after 1m37s
CI/CD Pipeline / frontend-build (push) Has been skipped
CI/CD Pipeline / e2e-test (push) Has been skipped
fix: resolve all frontend lint errors (85 issues fixed)
- Replace all 'any' types with proper TypeScript interfaces
- Fix React hooks setState in useEffect issues with lazy initialization
- Remove unused variables and imports across all files
- Fix React Compiler memoization dependency issues
- Add comprehensive i18n translation keys for admin interfaces
- Apply consistent prettier formatting throughout codebase
- Clean up unused bulk editing functionality
- Improve type safety and code quality across frontend

Files changed: 39
- ImpactMetrics.tsx: Fixed any types and interfaces
- AdminVerificationQueuePage.tsx: Added i18n keys, removed unused vars
- LocalizationUIPage.tsx: Fixed memoization, added translations
- LocalizationDataPage.tsx: Added type safety and translations
- And 35+ other files with various lint fixes
2025-12-25 14:14:58 +01:00

125 lines
3.5 KiB
TypeScript

import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { clsx } from 'clsx';
export interface TooltipProps {
content: React.ReactNode;
children: React.ReactNode;
position?: 'top' | 'bottom' | 'left' | 'right';
delay?: number;
className?: string;
disabled?: boolean;
}
const positionClasses = {
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
};
const arrowClasses = {
top: 'top-full left-1/2 -translate-x-1/2 border-t-background border-l-transparent border-r-transparent border-b-transparent',
bottom:
'bottom-full left-1/2 -translate-x-1/2 border-b-background border-l-transparent border-r-transparent border-t-transparent',
left: 'left-full top-1/2 -translate-y-1/2 border-l-background border-t-transparent border-b-transparent border-r-transparent',
right:
'right-full top-1/2 -translate-y-1/2 border-r-background border-t-transparent border-b-transparent border-l-transparent',
};
/**
* Tooltip component
*/
export const Tooltip = ({
content,
children,
position = 'top',
delay = 200,
className,
disabled = false,
}: TooltipProps) => {
const [isVisible, setIsVisible] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout>();
const isVisibleRef = useRef(isVisible);
// Keep ref in sync with state
useEffect(() => {
isVisibleRef.current = isVisible;
}, [isVisible]);
// Handle tooltip visibility with proper cleanup
useLayoutEffect(() => {
// Clear any existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = undefined;
}
if (isVisible && !disabled) {
// Set up delayed show
timeoutRef.current = setTimeout(() => {
// Only show if still visible when timeout fires
if (isVisibleRef.current) {
setShowTooltip(true);
}
}, delay);
}
// Note: We don't call setShowTooltip(false) here to avoid synchronous state updates
// The tooltip will be hidden by the mouse event handlers or when conditions change
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = undefined;
}
};
}, [isVisible, delay, disabled]);
// Handle hiding when conditions change
useLayoutEffect(() => {
if (!isVisible || disabled) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setShowTooltip(false);
}
}, [isVisible, disabled]);
if (disabled) {
return <>{children}</>;
}
return (
<div
className="relative inline-block"
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => {
setIsVisible(false);
setShowTooltip(false);
}}
onFocus={() => setIsVisible(true)}
onBlur={() => {
setIsVisible(false);
setShowTooltip(false);
}}
>
{children}
{showTooltip && (
<div
className={clsx(
'absolute z-50 px-2 py-1',
'text-xs text-foreground bg-background',
'border rounded-md shadow-lg',
'whitespace-nowrap',
'animate-in fade-in-0 zoom-in-95 duration-200',
positionClasses[position],
className
)}
role="tooltip"
>
{content}
<div className={clsx('absolute border-4', arrowClasses[position])} />
</div>
)}
</div>
);
};