import React from 'react'; import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; /** * TooltipProvider keeps the Radix-era prop names (`delayDuration`, * `skipDelayDuration`, `disableHoverableContent`) as its public API and maps * them onto Base UI's Provider (`delay`, `timeout`) — call sites in the apps * stay unchanged. `disableHoverableContent` has no provider-level equivalent * in Base UI (it moved to per-root `disableHoverablePopup`), so it is carried * via context and applied to every Tooltip root under the provider. */ const DisableHoverablePopupContext = React.createContext(false); interface TooltipProviderProps { delayDuration?: number; skipDelayDuration?: number; disableHoverableContent?: boolean; children?: React.ReactNode; } export const TooltipProvider: React.FC = ({ delayDuration, skipDelayDuration, disableHoverableContent = false, children, }) => ( {children} ); interface TooltipProps { content: React.ReactNode; children: React.ReactElement; side?: 'top' | 'right' | 'bottom' | 'left'; align?: 'start' | 'center' | 'end'; delayDuration?: number; sideOffset?: number; /** * When true, allow the tooltip to wrap onto multiple lines with a reasonable * max width. Default is single-line (nowrap) — matches the original callsites * that use it for short button labels. */ wide?: boolean; /** * Asked each time the tooltip would open, with the trigger element; `false` * keeps it closed this time (e.g. open only while the trigger's text is cut * off). Unset: it always opens, as before. */ shouldOpen?: (trigger: Element) => boolean; /** * Where the popup is portaled. Unset: the document body. A trigger inside a * native modal `` needs the dialog here, or the popup is drawn * under the dialog's top layer. */ container?: HTMLElement | null; /** * When true, Escape closes the tooltip without using the key up: it still * reaches the page (not default-prevented, not stopped), so the page's own * Escape (closing a pane) works while a tooltip is open. Unset: Base UI's * default, where the Escape that closes a tooltip goes no further. */ escapePassesThrough?: boolean; } export const Tooltip: React.FC = ({ content, children, side = 'top', align = 'center', delayDuration, sideOffset = 8, wide = false, shouldOpen, container, escapePassesThrough = false, }) => { const disableHoverablePopup = React.useContext(DisableHoverablePopupContext); // Controlled only for escapePassesThrough: the Escape close is canceled (so Base UI neither prevents nor stops // the key) and the tooltip is closed here instead. const [controlledOpen, setControlledOpen] = React.useState(false); const controlled = escapePassesThrough ? { open: controlledOpen } : {}; return ( { if (open && shouldOpen && !(details.trigger && shouldOpen(details.trigger))) { details.cancel(); return; } if (!escapePassesThrough) return; if (!open && details.reason === 'escape-key') { details.cancel(); details.allowPropagation(); } setControlledOpen(open); } : undefined } > {/* Above the annotation toolbar (z-[100]), so a tooltip that flips toward it is never tucked under it. */} {content} ); };