From aff3f7e84a834011c9c597dd33022aa87786875d Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 12 Aug 2026 12:20:31 +0200 Subject: [PATCH] Update-Banner: klebt oben fest (sticky), Sticky-Header rasten darunter ein Der Hinweis "Neue Version verfuegbar" scrollte bisher weg. Jetzt sticky an der Viewport-Oberkante; das Banner meldet seine gemessene Hoehe als CSS-Var --app-banner-h, an der sich die Sticky-Header von ContractDetail/ContractForm ausrichten (top-[var(--app-banner-h,0px)]), damit sie nicht dahinter verschwinden. Co-Authored-By: Claude Opus 4.8 --- frontend/src/components/UpdateBanner.tsx | 38 +++++++++++++++++-- .../src/pages/contracts/ContractDetail.tsx | 2 +- frontend/src/pages/contracts/ContractForm.tsx | 2 +- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/UpdateBanner.tsx b/frontend/src/components/UpdateBanner.tsx index d5bfe643..bcb43325 100644 --- a/frontend/src/components/UpdateBanner.tsx +++ b/frontend/src/components/UpdateBanner.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { RefreshCw, X } from 'lucide-react'; import { useVersionCheck } from '../hooks/useVersionCheck'; @@ -7,15 +7,47 @@ import { useVersionCheck } from '../hooks/useVersionCheck'; * Version verfügbar ist (siehe useVersionCheck). "Jetzt neu laden" lädt die * Seite neu und holt damit den frischen Code; "später" blendet für die * laufende Session aus. + * + * Das Banner klebt (`sticky top-0`) an der Viewport-Oberkante, bleibt beim + * Scrollen also stehen. Damit die seitenspezifischen Sticky-Header + * (ContractDetail/ContractForm mit `top-[var(--app-banner-h)]`) nicht darunter + * verschwinden, meldet das Banner seine gemessene Höhe als CSS-Variable + * `--app-banner-h` am -Element; ohne Banner ist sie 0px. */ export default function UpdateBanner() { const updateAvailable = useVersionCheck(); const [dismissed, setDismissed] = useState(false); + const barRef = useRef(null); - if (!updateAvailable || dismissed) return null; + const visible = updateAvailable && !dismissed; + + useLayoutEffect(() => { + const root = document.documentElement; + if (!visible) { + root.style.setProperty('--app-banner-h', '0px'); + return; + } + const el = barRef.current; + if (!el) return; + const apply = () => root.style.setProperty('--app-banner-h', `${el.offsetHeight}px`); + apply(); + const ro = new ResizeObserver(apply); + ro.observe(el); + return () => { + ro.disconnect(); + root.style.setProperty('--app-banner-h', '0px'); + }; + }, [visible]); + + // Sicherstellen, dass die Variable auch beim Unmount zurückgesetzt wird. + useEffect(() => () => { + document.documentElement.style.setProperty('--app-banner-h', '0px'); + }, []); + + if (!visible) return null; return ( -
+
diff --git a/frontend/src/pages/contracts/ContractDetail.tsx b/frontend/src/pages/contracts/ContractDetail.tsx index 0c2aa94b..7685fe9a 100644 --- a/frontend/src/pages/contracts/ContractDetail.tsx +++ b/frontend/src/pages/contracts/ContractDetail.tsx @@ -1828,7 +1828,7 @@ export default function ContractDetail() { {/* Sticky-Header: Vertragsnummer + Kunde + Aktionsbuttons bleiben beim Scrollen oben stehen. -mx-8 px-8 zieht den Balken auf die Layout-Padding-Kante, damit's optisch bündig ist. */} -
+