diff --git a/src/frontend/src/features/pip/components/DocumentPiPPortal.tsx b/src/frontend/src/features/pip/components/DocumentPiPPortal.tsx index fd0e21ac..246ba2a2 100644 --- a/src/frontend/src/features/pip/components/DocumentPiPPortal.tsx +++ b/src/frontend/src/features/pip/components/DocumentPiPPortal.tsx @@ -5,6 +5,7 @@ import { useDocumentPiP } from '../hooks/useDocumentPiP' import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce' import { useRestoreFocus } from '@/hooks/useRestoreFocus' import { UNSAFE_PortalProvider } from '@react-aria/overlays' +import { VisualOnlyTooltipsContext } from '@/primitives/VisualOnlyTooltipsContext' // Minimal base styles so the PiP window renders correctly on first paint. const ensureBaseStyles = (target: Document) => { @@ -224,7 +225,9 @@ export const DocumentPiPPortal = ({ if (!container) return null return createPortal( container}> - {children} + + {children} + , container ) diff --git a/src/frontend/src/primitives/Menu.tsx b/src/frontend/src/primitives/Menu.tsx index f0fe5f59..873782ee 100644 --- a/src/frontend/src/primitives/Menu.tsx +++ b/src/frontend/src/primitives/Menu.tsx @@ -1,11 +1,8 @@ -import { ReactNode, useMemo } from 'react' +import { ReactNode } from 'react' import { MenuTrigger } from 'react-aria-components' import { StyledPopover } from './Popover' import { Box } from './Box' -import { - useOverlayBoundaryElement, - useOverlayPortalContainer, -} from './useOverlayPortalContainer' +import { useOverlayBoundaryElement } from './useOverlayPortalContainer' /** * a Menu is a tuple of a trigger component (most usually a Button) that toggles menu items in a tooltip around the trigger @@ -15,33 +12,22 @@ import { export const Menu = ({ children, variant = 'light', - placement, + placement = 'top', + shouldFlip, }: { children: [trigger: ReactNode, menu: ReactNode] variant?: 'dark' | 'light' placement?: 'bottom' | 'top' | 'left' | 'right' + shouldFlip?: boolean }) => { const [trigger, menu] = children const boundaryElement = useOverlayBoundaryElement() - const portalContainer = useOverlayPortalContainer() - - // Detect if we're in PiP: portal container is in a different document than the main window - const isInPiP = useMemo( - () => - !!portalContainer?.ownerDocument && - portalContainer.ownerDocument !== document, - [portalContainer] - ) - - // Default placement: 'bottom' in PiP, 'top' elsewhere (to match existing behavior) - const defaultPlacement = isInPiP ? 'bottom' : 'top' - const shouldFlip = isInPiP ? false : undefined return ( {trigger} diff --git a/src/frontend/src/primitives/TooltipWrapper.tsx b/src/frontend/src/primitives/TooltipWrapper.tsx index 0e4f252b..a8143070 100644 --- a/src/frontend/src/primitives/TooltipWrapper.tsx +++ b/src/frontend/src/primitives/TooltipWrapper.tsx @@ -6,8 +6,8 @@ import { type TooltipProps, } from 'react-aria-components' import { styled } from '@/styled-system/jsx' -import { useOverlayPortalContainer } from './useOverlayPortalContainer' import { VisualOnlyTooltip } from './VisualOnlyTooltip' +import { useVisualOnlyTooltips } from './VisualOnlyTooltipsContext' export type TooltipWrapperProps = { tooltip?: string @@ -29,28 +29,23 @@ export const TooltipWrapper = ({ }: { children: ReactElement } & TooltipWrapperProps) => { - const portalContainer = useOverlayPortalContainer() - const isExternalDocument = - !!portalContainer?.ownerDocument && - portalContainer.ownerDocument !== document + const visualOnly = useVisualOnlyTooltips() + const tooltipDelay = + tooltipType === 'instant' + ? INSTANT_TOOLTIP_DELAY_MS + : DELAYED_TOOLTIP_DELAY_MS - return tooltip ? ( - isExternalDocument ? ( - {children} - ) : ( - - {children} - {tooltip} - - ) - ) : ( - children + if (!tooltip) return children + + if (visualOnly) { + return {children} + } + + return ( + + {children} + {tooltip} + ) } diff --git a/src/frontend/src/primitives/VisualOnlyTooltipsContext.tsx b/src/frontend/src/primitives/VisualOnlyTooltipsContext.tsx new file mode 100644 index 00000000..579c7638 --- /dev/null +++ b/src/frontend/src/primitives/VisualOnlyTooltipsContext.tsx @@ -0,0 +1,11 @@ +import { createContext, useContext } from 'react' + +/** + * When true, tooltips render as visual-only (no aria-describedby). + * Provided by surfaces where React Aria TooltipTrigger doesn't work + * correctly (e.g. cross-document portals). + */ +export const VisualOnlyTooltipsContext = createContext(false) + +export const useVisualOnlyTooltips = () => + useContext(VisualOnlyTooltipsContext)