fix: keep admin toasts above the save bar so they never cover discard or save

This commit is contained in:
Linus Rath
2026-10-28 12:46:04 +01:00
parent 2e8382856b
commit ab417728ec
2 changed files with 19 additions and 0 deletions
@@ -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(<AdminSaveBar dirty onSave={() => {}} />);
expect(root.getPropertyValue('--bottom-controls-height')).toBe('57px');
rerender(<AdminSaveBar dirty={false} onSave={() => {}} />);
expect(root.getPropertyValue('--bottom-controls-height')).toBe('');
rect.mockRestore();
});
});
+5
View File
@@ -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 = (
<div
ref={barRef}
role="region"
aria-label="Unsaved changes"
data-testid="admin-save-bar"