fix: offline banner no longer covers the top of the app (#813)

OfflineBanner was fixed at top:0 but the rest of the page had no
idea it was visible, so on mobile (and the desktop nav on wider
screens) the banner sat on top of the header content.

When the banner is visible it now sets --offline-banner-h on <html>;
body reserves that space via padding-top, and the desktop fixed
Navbar shifts its top by the same amount. When back online the var
is removed and everything snaps back.
This commit is contained in:
Maurice
2026-04-21 22:10:11 +02:00
parent 82b16a4bf5
commit 906d8821a4
3 changed files with 22 additions and 1 deletions
+1
View File
@@ -89,6 +89,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
touchAction: 'manipulation', touchAction: 'manipulation',
paddingTop: 'env(safe-area-inset-top, 0px)', paddingTop: 'env(safe-area-inset-top, 0px)',
height: 'var(--nav-h)', height: 'var(--nav-h)',
top: 'var(--offline-banner-h, 0px)',
transition: 'background 240ms cubic-bezier(0.23,1,0.32,1), backdrop-filter 240ms cubic-bezier(0.23,1,0.32,1), box-shadow 240ms cubic-bezier(0.23,1,0.32,1)', transition: 'background 240ms cubic-bezier(0.23,1,0.32,1), backdrop-filter 240ms cubic-bezier(0.23,1,0.32,1), box-shadow 240ms cubic-bezier(0.23,1,0.32,1)',
}} className="hidden md:flex items-center px-4 gap-4 fixed top-0 left-0 right-0 z-[200]"> }} className="hidden md:flex items-center px-4 gap-4 fixed top-0 left-0 right-0 z-[200]">
{/* Left side */} {/* Left side */}
@@ -40,6 +40,22 @@ export default function OfflineBanner(): React.ReactElement | null {
}, []) }, [])
const hidden = isOnline && pendingCount === 0 const hidden = isOnline && pendingCount === 0
// When the banner is visible, reserve space at the top of the page so it
// doesn't cover the nav/header. Uses a CSS var on <html> so we can offset
// via a global `body` rule instead of rewiring every layout.
useEffect(() => {
const root = document.documentElement
if (hidden) {
root.style.removeProperty('--offline-banner-h')
} else {
// 32px for icon+text row + the top safe-area inset that the banner adds
// in its own padding. Kept in one place so it's easy to tweak.
root.style.setProperty('--offline-banner-h', 'calc(env(safe-area-inset-top, 0px) + 32px)')
}
return () => { root.style.removeProperty('--offline-banner-h') }
}, [hidden])
if (hidden) return null if (hidden) return null
const offline = !isOnline const offline = !isOnline
+5 -1
View File
@@ -431,6 +431,8 @@ input[type="number"], input[type="time"], input[type="date"], input[type="dateti
--safe-top: env(safe-area-inset-top, 0px); --safe-top: env(safe-area-inset-top, 0px);
--nav-h: 0px; --nav-h: 0px;
--bottom-nav-h: 0px; --bottom-nav-h: 0px;
/* Set by OfflineBanner when it's visible so body can reserve space. */
--offline-banner-h: 0px;
--font-system: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif; --font-system: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
--sp-1: 4px; --sp-1: 4px;
--sp-2: 8px; --sp-2: 8px;
@@ -536,7 +538,9 @@ body {
font-family: var(--font-system); font-family: var(--font-system);
background-color: var(--bg-primary); background-color: var(--bg-primary);
color: var(--text-primary); color: var(--text-primary);
transition: background-color 0.2s, color 0.2s; transition: background-color 0.2s, color 0.2s, padding-top 0.15s ease;
/* Reserve space when OfflineBanner is visible; 0 when online. */
padding-top: var(--offline-banner-h, 0px);
} }
/* ── Marker cluster custom styling ────────────── */ /* ── Marker cluster custom styling ────────────── */