import { useState, useSyncExternalStore } from "react"; import { PopoverSurface, useAnchoredPopover } from "./AnchoredPopover"; import { hintsEnabled, subscribeHints } from "../lib/hints"; type Side = "top" | "bottom" | "left" | "right"; /** Wrap any element to show a short glass hint caption on hover — by default only while the * app-wide hints toggle (Settings → Appearance) is on. Anchored by the R8 popover platform, so it * is never clipped by an overflow/stacking ancestor, flips when the preferred side runs out of room * and slides back inside the viewport near an edge. * `always` bypasses the hints toggle (for a label that IS the primary affordance, e.g. the slim nav * rail's flyout names); `openDelay` waits before showing (a rest-on-the-icon feel). */ export default function Tooltip({ hint, side = "top", always = false, openDelay = 0, children, }: { hint: string; side?: Side; always?: boolean; openDelay?: number; children: React.ReactNode; }) { const hintsOn = useSyncExternalStore(subscribeHints, hintsEnabled, hintsEnabled); const enabled = always || hintsOn; const [open, setOpen] = useState(false); const pop = useAnchoredPopover({ open: open && enabled && !!hint, onOpenChange: setOpen, mode: "hover", placement: side, offset: 8, // Show/hide on contact by default (as the pre-platform tooltip did); `openDelay` adds a // rest-before-show for labels that shouldn't flash on a fast sweep across an icon row. hoverDelay: { open: openDelay, close: 0 }, }); if (!enabled || !hint) return <>{children}; return ( <> {children} {/* Passive caption: it must not swallow a click aimed at a neighbouring control that it overlaps while the hover-close delay runs out. */} {hint} ); }