diff --git a/src/components/BreadcrumbBar.test.tsx b/src/components/BreadcrumbBar.test.tsx
index 575581e5..d870e426 100644
--- a/src/components/BreadcrumbBar.test.tsx
+++ b/src/components/BreadcrumbBar.test.tsx
@@ -307,6 +307,24 @@ describe('BreadcrumbBar — action buttons always right-aligned', () => {
expect(widthActionGroup).toHaveClass('gap-2')
})
+ it('end-aligns toolbar action tooltips so zoomed windows keep them inside the right edge', async () => {
+ render(
+ ,
+ )
+
+ act(() => {
+ fireEvent.focus(screen.getByRole('button', { name: 'Add to favorites' }))
+ })
+
+ const tooltip = await screen.findByRole('tooltip')
+ expect(document.querySelector('[data-slot="tooltip-content"]')).toHaveAttribute('data-align', 'end')
+ expect(tooltip).toHaveTextContent('Add to favorites')
+ })
+
it('lets the title use the free space before the fixed drag gap', () => {
const { container } = render()
diff --git a/src/components/BreadcrumbBar.tsx b/src/components/BreadcrumbBar.tsx
index 684bc050..e9f8c514 100644
--- a/src/components/BreadcrumbBar.tsx
+++ b/src/components/BreadcrumbBar.tsx
@@ -120,7 +120,7 @@ function IconActionButton({
style,
children,
testId,
- tooltipAlign,
+ tooltipAlign = 'end',
}: {
copy: ActionTooltipCopy
onClick?: () => void
diff --git a/src/components/ui/overlayPresence.test.tsx b/src/components/ui/overlayPresence.test.tsx
index 53886710..78949a0e 100644
--- a/src/components/ui/overlayPresence.test.tsx
+++ b/src/components/ui/overlayPresence.test.tsx
@@ -1,11 +1,12 @@
-import { render, screen } from '@testing-library/react'
-import { describe, expect, it } from 'vitest'
+import { act, render, renderHook, screen } from '@testing-library/react'
+import { beforeEach, describe, expect, it } from 'vitest'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from './tooltip'
+import { useZoom } from '@/hooks/useZoom'
import {
Popover,
PopoverContent,
@@ -15,7 +16,8 @@ import {
const PRESENCE_ANIMATION_CLASS_PARTS = [
'animate-',
'fade-',
- 'zoom-',
+ 'zoom-in-',
+ 'zoom-out-',
'slide-in-from',
]
@@ -30,6 +32,11 @@ function expectNoPresenceAnimationClasses(element: HTMLElement) {
}
describe('overlay presence stability', () => {
+ beforeEach(() => {
+ document.documentElement.style.removeProperty('--tolaria-overlay-zoom-factor')
+ document.documentElement.style.removeProperty('--tolaria-overlay-zoom-inverse')
+ })
+
it('keeps tooltip content free of Radix presence animation classes', () => {
render(
@@ -45,6 +52,38 @@ describe('overlay presence stability', () => {
expectNoPresenceAnimationClasses(screen.getByTestId('tooltip-content'))
})
+ it('publishes zoom variables for overlay portal positioning and visual scale', () => {
+ const { result } = renderHook(() => useZoom())
+
+ act(() => {
+ result.current.zoomIn()
+ })
+
+ expect(document.documentElement.style.getPropertyValue('--tolaria-overlay-zoom-factor')).toBe(String(110 / 100))
+ expect(document.documentElement.style.getPropertyValue('--tolaria-overlay-zoom-inverse')).toBe(String(100 / 110))
+ })
+
+ it('compensates tooltip portal positioning without cancelling content zoom', () => {
+ document.documentElement.style.setProperty('--tolaria-overlay-zoom-factor', '1.4')
+ document.documentElement.style.setProperty('--tolaria-overlay-zoom-inverse', String(1 / 1.4))
+
+ render(
+
+
+
+
+
+ Tooltip copy
+
+ ,
+ )
+
+ const positionShell = document.querySelector('[data-slot="tooltip-content"]') as HTMLElement
+ const visualShell = document.querySelector('[data-slot="tooltip-visual-scale"]') as HTMLElement
+ expect(positionShell.className).toContain('[zoom:var(--tolaria-overlay-zoom-inverse,1)]')
+ expect(visualShell.className).toContain('[zoom:var(--tolaria-overlay-zoom-factor,1)]')
+ })
+
it('keeps popover content free of Radix presence animation classes', () => {
render(
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index 62345776..acc85d01 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -33,7 +33,9 @@ function TooltipTrigger({
function TooltipContent({
className,
sideOffset = 0,
+ collisionPadding = 8,
children,
+ style,
...props
}: React.ComponentProps) {
return (
@@ -41,14 +43,23 @@ function TooltipContent({
- {children}
-
+
+ {children}
+
+
)
diff --git a/src/hooks/useZoom.ts b/src/hooks/useZoom.ts
index 333af557..1b9bbdca 100644
--- a/src/hooks/useZoom.ts
+++ b/src/hooks/useZoom.ts
@@ -28,6 +28,8 @@ function loadPersistedZoom(): number {
function applyZoomToDocument(level: number): void {
document.documentElement.style.setProperty('zoom', `${level}%`)
+ document.documentElement.style.setProperty('--tolaria-overlay-zoom-factor', String(level / DEFAULT_ZOOM))
+ document.documentElement.style.setProperty('--tolaria-overlay-zoom-inverse', String(DEFAULT_ZOOM / level))
window.dispatchEvent(new Event('laputa-zoom-change'))
}
@@ -40,7 +42,7 @@ export function useZoom() {
const level = loadPersistedZoom()
// Apply zoom synchronously during init so child components (e.g. CodeMirror)
// measure the correct scale factor in their own effects.
- document.documentElement.style.setProperty('zoom', `${level}%`)
+ applyZoomToDocument(level)
return level
})