Files
backspace/packages/web/src/components/ui/Tooltip.tsx
T
Jannis Braun 435d12e5b8 feat: unread indicators + DM bug fixes + data-driven isDmChannel
- Fix stale message cache: add force param to loadMessages, clearAllMessages action
- Fix reload race condition: URL-based isDmChannel fallback before WS ready
- Add read_states DB table for persistent unread tracking
- Add channel_ack WS event (client→server→echo) with BigInt comparison
- Wire up unread state in chatStore (readStates, unreadChannels, ackChannel)
- Auto-ack channels on MessageList view (200ms debounced)
- Unread pill indicators on server icons in ServerSidebar
- Bold text + white dot on unread channels/DMs in ChannelSidebar
- Replace all showDms reads with data-driven isDmChannel() across 8 files
- Design system, UI polish, and component fixes from previous sessions
2026-02-18 20:48:48 +01:00

49 lines
1.4 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
interface TooltipProps {
content: string;
children: React.ReactNode;
position?: 'top' | 'right' | 'bottom' | 'left';
delay?: number;
}
export function Tooltip({ content, children, position = 'right', delay = 200 }: TooltipProps) {
const [isVisible, setIsVisible] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
const show = () => {
timeoutRef.current = setTimeout(() => setIsVisible(true), delay);
};
const hide = () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
setIsVisible(false);
};
useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, []);
const positionClasses: Record<string, string> = {
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
right: 'left-full top-1/2 -translate-y-1/2 ml-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',
};
return (
<div className="relative inline-flex" onMouseEnter={show} onMouseLeave={hide}>
{children}
{isVisible && (
<div
className={`absolute z-50 px-3 py-1.5 text-sm font-medium text-discord-text-primary bg-discord-bg-floating rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none ${positionClasses[position]}`}
>
{content}
</div>
)}
</div>
);
}