From ab417728ec626e9486decdd73f6f9e20d18c949f Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:46:04 +0100 Subject: [PATCH] fix: keep admin toasts above the save bar so they never cover discard or save --- components/admin/__tests__/save-bar.test.tsx | 14 ++++++++++++++ components/admin/save-bar.tsx | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/components/admin/__tests__/save-bar.test.tsx b/components/admin/__tests__/save-bar.test.tsx index a29cc6e59..0fb8091c9 100644 --- a/components/admin/__tests__/save-bar.test.tsx +++ b/components/admin/__tests__/save-bar.test.tsx @@ -50,4 +50,18 @@ describe('AdminSaveBar', () => { expect(screen.getByTestId('page').querySelector('[data-testid="admin-save-bar"]')).toBeNull(); host.remove(); }); + it('lifts toasts above itself while it is shown', () => { + // A toast over Discard / Save changes caught the clicks meant for them. + const rect = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function (this: HTMLElement) { + return this.dataset.testid === 'admin-save-bar' + ? ({ top: window.innerHeight - 57, bottom: window.innerHeight, left: 0, right: 800, width: 800, height: 57 } as DOMRect) + : ({ top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0 } as DOMRect); + }); + const root = document.documentElement.style; + const { rerender } = render( {}} />); + expect(root.getPropertyValue('--bottom-controls-height')).toBe('57px'); + rerender( {}} />); + expect(root.getPropertyValue('--bottom-controls-height')).toBe(''); + rect.mockRestore(); + }); }); diff --git a/components/admin/save-bar.tsx b/components/admin/save-bar.tsx index 3dd834ed5..b6cba54cd 100644 --- a/components/admin/save-bar.tsx +++ b/components/admin/save-bar.tsx @@ -4,6 +4,7 @@ import { useEffect, type ReactNode } from 'react'; import { createPortal } from 'react-dom'; import { Loader2 } from '@/components/icons'; import { Button } from '@/components/ui/button'; +import { useBottomControls } from '@/hooks/use-bottom-controls'; import { useAdminShell } from './shell-context'; interface AdminSaveBarProps { @@ -21,9 +22,12 @@ interface AdminSaveBarProps { * The one place a page saves from. It appears at the bottom of the panel * while something is unsaved (through the shell's portal host, so it sits * below the scrolling content and above the mobile rail) and takes Ctrl+S. + * It registers as a bottom bar, so toasts sit above it instead of over its + * buttons. */ export function AdminSaveBar({ dirty, saving = false, onSave, onDiscard, saveLabel = 'Save changes', note }: AdminSaveBarProps) { const host = useAdminShell()?.saveBarHost ?? null; + const barRef = useBottomControls(dirty); useEffect(() => { if (!dirty) return; @@ -41,6 +45,7 @@ export function AdminSaveBar({ dirty, saving = false, onSave, onDiscard, saveLab const bar = (