| ... | @@ -2,6 +2,10 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; | ... | @@ -2,6 +2,10 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; |
| 2 | import { createContext, use, useEffect, useRef, type ComponentProps, type RefObject } from "react"; | 2 | import { createContext, use, useEffect, useRef, type ComponentProps, type RefObject } from "react"; |
| 3 | | 3 | |
| 4 | import { cn } from "~/lib/utils"; | 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 | const TooltipProvider = TooltipPrimitive.Provider; | 10 | const TooltipProvider = TooltipPrimitive.Provider; |
| 7 | | 11 | |
| ... | @@ -80,6 +84,28 @@ function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) { | ... | @@ -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 | function TooltipPopup({ | 109 | function TooltipPopup({ |
| 84 | className, | 110 | className, |
| 85 | align = "center", | 111 | align = "center", |
| ... | @@ -99,14 +125,7 @@ function TooltipPopup({ | ... | @@ -99,14 +125,7 @@ function TooltipPopup({ |
| 99 | }) { | 125 | }) { |
| 100 | return ( | 126 | return ( |
| 101 | <TooltipPrimitive.Portal> | 127 | <TooltipPrimitive.Portal> |
| 102 | <TooltipPrimitive.Positioner | 128 | <TooltipPositioner align={align} anchor={anchor} side={side} sideOffset={sideOffset}> |
| 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 | > | | |
| 110 | <TooltipPrimitive.Popup | 129 | <TooltipPrimitive.Popup |
| 111 | className={cn( | 130 | className={cn( |
| 112 | "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%)]", | 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,7 +148,7 @@ function TooltipPopup({ |
| 129 | {children} | 148 | {children} |
| 130 | </TooltipPrimitive.Viewport> | 149 | </TooltipPrimitive.Viewport> |
| 131 | </TooltipPrimitive.Popup> | 150 | </TooltipPrimitive.Popup> |
| 132 | </TooltipPrimitive.Positioner> | 151 | </TooltipPositioner> |
| 133 | </TooltipPrimitive.Portal> | 152 | </TooltipPrimitive.Portal> |
| 134 | ); | 153 | ); |
| 135 | } | 154 | } |