requestFullscreen() on the voice container puts only its descendants in the browser's top layer; overlays portaled to document.body were rendered outside that layer and stayed invisible — most visibly the right-click context menu on stream tiles and voice user panels. Add usePortalContainer() hook returning document.fullscreenElement ?? document.body and re-rendering on fullscreenchange. Migrate every overlay reachable during a call: ContextMenuRenderer (desktop, submenu, mobile sheet), Tooltip, ConfirmDialog, ConnectionInfoPopover, ScreenShareSettingsPopover, and ScreenSharePicker (which previously rendered inline at App root).
62 lines
1.8 KiB
TypeScript
62 lines
1.8 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { useFloatingPosition } from '../../hooks/useFloatingPosition';
|
|
import { usePortalContainer } from '../../hooks/usePortalContainer';
|
|
import { useUIStore } from '../../stores/uiStore';
|
|
|
|
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 isMobile = useUIStore((s) => s.isMobile);
|
|
|
|
// No tooltips on touch devices — return children unwrapped
|
|
if (isMobile) return <>{children}</>;
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
const anchorRef = useRef<HTMLDivElement>(null);
|
|
const floatingRef = useRef<HTMLDivElement>(null);
|
|
const portalContainer = usePortalContainer();
|
|
|
|
const { style } = useFloatingPosition(anchorRef, floatingRef, {
|
|
placement: position,
|
|
offset: 8,
|
|
enabled: isVisible,
|
|
});
|
|
|
|
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);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div ref={anchorRef} className="relative inline-flex" onMouseEnter={show} onMouseLeave={hide}>
|
|
{children}
|
|
{isVisible && createPortal(
|
|
<div
|
|
ref={floatingRef}
|
|
style={style}
|
|
className="px-3 py-1.5 text-sm font-medium text-txt-primary glass rounded-md whitespace-nowrap pointer-events-none"
|
|
>
|
|
{content}
|
|
</div>,
|
|
portalContainer,
|
|
)}
|
|
</div>
|
|
);
|
|
}
|