From cafe620e01106262b8876f1bb927189337523b75 Mon Sep 17 00:00:00 2001 From: oliver <97427849+flamboh@users.noreply.github.com> Date: Sun, 11 Oct 2026 14:27:41 -0700 Subject: [PATCH] fix(desktop): tooltips no longer open under the Windows and Linux window controls (#17672) --- apps/web/src/components/ui/tooltip.tsx | 37 +++++++++++++++++------ apps/web/src/lib/windowControlsOverlay.ts | 19 ++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/ui/tooltip.tsx b/apps/web/src/components/ui/tooltip.tsx index 1017a787931fd4233f5571c46b8842bd0fa9ee79..fbd338afbc16aece179578d7e6b8e82742f92193 100644 --- a/apps/web/src/components/ui/tooltip.tsx +++ b/apps/web/src/components/ui/tooltip.tsx @@ -2,6 +2,10 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; import { createContext, use, useEffect, useRef, type ComponentProps, type RefObject } from "react"; import { cn } from "~/lib/utils"; +import { useNativeTitlebarHeight } from "~/lib/windowControlsOverlay"; + +// Base UI's default, restated because a padding object zeroes the sides it omits. +const COLLISION_PADDING = 5; const TooltipProvider = TooltipPrimitive.Provider; @@ -80,6 +84,28 @@ function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) { ); } +/** + * Treats the native titlebar strip as off-screen, so a tooltip that would open + * under the window controls flips to its other side. Rendered inside the + * portal so only open tooltips track the titlebar. + */ +function TooltipPositioner(props: TooltipPrimitive.Positioner.Props) { + const titlebarHeight = useNativeTitlebarHeight(); + return ( + + ); +} + function TooltipPopup({ className, align = "center", @@ -99,14 +125,7 @@ function TooltipPopup({ }) { return ( - + - + ); } diff --git a/apps/web/src/lib/windowControlsOverlay.ts b/apps/web/src/lib/windowControlsOverlay.ts index 7c9e8e8553b3f12be35fc2a4ee4842ee9bd2b63b..6508ebc4a0edb9e125fa40949287ad07732fb989 100644 --- a/apps/web/src/lib/windowControlsOverlay.ts +++ b/apps/web/src/lib/windowControlsOverlay.ts @@ -1,3 +1,5 @@ +import { useSyncExternalStore } from "react"; + import { isWindowsPlatform } from "./utils"; const WCO_CLASS_NAME = "wco"; @@ -6,6 +8,7 @@ const ELECTRON_WINDOWS_CLASS_NAME = "electron-windows"; interface WindowControlsOverlayLike { readonly visible: boolean; + getTitlebarAreaRect(): DOMRect; addEventListener(type: "geometrychange", listener: EventListener): void; removeEventListener(type: "geometrychange", listener: EventListener): void; } @@ -43,6 +46,22 @@ export function syncDocumentWindowControlsOverlayClass(): () => void { }; } +function subscribeTitlebarArea(listener: () => void): () => void { + const overlay = getWindowControlsOverlay(); + overlay?.addEventListener("geometrychange", listener); + return () => overlay?.removeEventListener("geometrychange", listener); +} + +function getNativeTitlebarHeight(): number { + const overlay = getWindowControlsOverlay(); + return overlay?.visible ? overlay.getTitlebarAreaRect().bottom : 0; +} + +/** Height of the strip at the top of the window where native window controls paint over the page. */ +export function useNativeTitlebarHeight(): number { + return useSyncExternalStore(subscribeTitlebarArea, getNativeTitlebarHeight, () => 0); +} + function getElectronPlatformClassNames( platform: string, ): -- 2.54.0