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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { RefreshCw, X } from 'lucide-react';
|
import { RefreshCw, X } from 'lucide-react';
|
||||||
import { useVersionCheck } from '../hooks/useVersionCheck';
|
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
|
* 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
|
* Seite neu und holt damit den frischen Code; "später" blendet für die
|
||||||
* laufende Session aus.
|
* 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 <html>-Element; ohne Banner ist sie 0px.
|
||||||
*/
|
*/
|
||||||
export default function UpdateBanner() {
|
export default function UpdateBanner() {
|
||||||
const updateAvailable = useVersionCheck();
|
const updateAvailable = useVersionCheck();
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
|
const barRef = useRef<HTMLDivElement>(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 (
|
return (
|
||||||
<div className="bg-blue-600 text-white px-6 py-2.5">
|
<div ref={barRef} className="sticky top-0 z-40 bg-blue-600 text-white px-6 py-2.5">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<RefreshCw className="w-4 h-4 flex-shrink-0" />
|
<RefreshCw className="w-4 h-4 flex-shrink-0" />
|
||||||
|
|||||||
@@ -1828,7 +1828,7 @@ export default function ContractDetail() {
|
|||||||
{/* Sticky-Header: Vertragsnummer + Kunde + Aktionsbuttons bleiben
|
{/* Sticky-Header: Vertragsnummer + Kunde + Aktionsbuttons bleiben
|
||||||
beim Scrollen oben stehen. -mx-8 px-8 zieht den Balken auf die
|
beim Scrollen oben stehen. -mx-8 px-8 zieht den Balken auf die
|
||||||
Layout-Padding-Kante, damit's optisch bündig ist. */}
|
Layout-Padding-Kante, damit's optisch bündig ist. */}
|
||||||
<div className="sticky top-0 z-20 -mx-8 px-8 py-4 mb-6 bg-gray-100 border-b border-gray-200 flex items-center justify-between">
|
<div className="sticky top-[var(--app-banner-h,0px)] z-20 -mx-8 px-8 py-4 mb-6 bg-gray-100 border-b border-gray-200 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-4 mb-2">
|
<div className="flex items-center gap-4 mb-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -860,7 +860,7 @@ export default function ContractForm() {
|
|||||||
{/* Sticky-Header: Zurück-Button + Titel + Kunde-Zeile bleiben beim
|
{/* Sticky-Header: Zurück-Button + Titel + Kunde-Zeile bleiben beim
|
||||||
Scrollen oben stehen. Layout-Padding wird per -mx-8 px-8
|
Scrollen oben stehen. Layout-Padding wird per -mx-8 px-8
|
||||||
überbrückt, damit der Balken bündig ist. */}
|
überbrückt, damit der Balken bündig ist. */}
|
||||||
<div className="sticky top-0 z-20 -mx-8 px-8 pt-4 pb-3 mb-6 bg-gray-100 border-b border-gray-200">
|
<div className="sticky top-[var(--app-banner-h,0px)] z-20 -mx-8 px-8 pt-4 pb-3 mb-6 bg-gray-100 border-b border-gray-200">
|
||||||
<div className="flex items-center gap-4 mb-2">
|
<div className="flex items-center gap-4 mb-2">
|
||||||
<Button variant="ghost" size="sm" onClick={() => navigate(back.to, { state: back.state })}>
|
<Button variant="ghost" size="sm" onClick={() => navigate(back.to, { state: back.state })}>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
|
|||||||
Reference in New Issue
Block a user