| ... | ... | @@ -2,6 +2,10 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; |
| 2 | 2 | import { createContext, use, useEffect, useRef, type ComponentProps, type RefObject } from "react"; |
| 3 | 3 | |
| 4 | 4 | import { cn } from "~/lib/utils"; |
| 5 | import { useNativeTitlebarHeight } from "~/lib/windowControlsOverlay"; |
| 6 | |
| 7 | // Base UI's default, restated because a padding object zeroes the sides it omits. |
| 8 | const COLLISION_PADDING = 5; |
| 5 | 9 | |
| 6 | 10 | const TooltipProvider = TooltipPrimitive.Provider; |
| 7 | 11 | |
| ... | ... | @@ -80,6 +84,28 @@ function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) { |
| 80 | 84 | ); |
| 81 | 85 | } |
| 82 | 86 | |
| 87 | /** |
| 88 | * Treats the native titlebar strip as off-screen, so a tooltip that would open |
| 89 | * under the window controls flips to its other side. Rendered inside the |
| 90 | * portal so only open tooltips track the titlebar. |
| 91 | */ |
| 92 | function TooltipPositioner(props: TooltipPrimitive.Positioner.Props) { |
| 93 | const titlebarHeight = useNativeTitlebarHeight(); |
| 94 | return ( |
| 95 | <TooltipPrimitive.Positioner |
| 96 | className="pointer-events-none z-[140] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none" |
| 97 | collisionPadding={{ |
| 98 | top: COLLISION_PADDING + titlebarHeight, |
| 99 | right: COLLISION_PADDING, |
| 100 | bottom: COLLISION_PADDING, |
| 101 | left: COLLISION_PADDING, |
| 102 | }} |
| 103 | data-slot="tooltip-positioner" |
| 104 | {...props} |
| 105 | /> |
| 106 | ); |
| 107 | } |
| 108 | |
| 83 | 109 | function TooltipPopup({ |
| 84 | 110 | className, |
| 85 | 111 | align = "center", |
| ... | ... | @@ -99,14 +125,7 @@ function TooltipPopup({ |
| 99 | 125 | }) { |
| 100 | 126 | return ( |
| 101 | 127 | <TooltipPrimitive.Portal> |
| 102 | | <TooltipPrimitive.Positioner |
| 103 | | align={align} |
| 104 | | anchor={anchor} |
| 105 | | className="pointer-events-none z-[140] h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none" |
| 106 | | data-slot="tooltip-positioner" |
| 107 | | side={side} |
| 108 | | sideOffset={sideOffset} |
| 109 | | > |
| 128 | <TooltipPositioner align={align} anchor={anchor} side={side} sideOffset={sideOffset}> |
| 110 | 129 | <TooltipPrimitive.Popup |
| 111 | 130 | className={cn( |
| 112 | 131 | "relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) text-balance rounded-md text-popover-foreground text-xs transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-md)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 data-instant:duration-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| ... | ... | @@ -129,7 +148,7 @@ function TooltipPopup({ |
| 129 | 148 | {children} |
| 130 | 149 | </TooltipPrimitive.Viewport> |
| 131 | 150 | </TooltipPrimitive.Popup> |
| 132 | | </TooltipPrimitive.Positioner> |
| 151 | </TooltipPositioner> |
| 133 | 152 | </TooltipPrimitive.Portal> |
| 134 | 153 | ); |
| 135 | 154 | } |