Vertrag-UI: Sticky-Header + Sticky-Footer

ContractDetail: der obere Balken (Vertragsnummer, Kunde-Link,
Aktions-Buttons wie Folgevertrag/Bearbeiten/Löschen) bleibt beim
Scrollen sichtbar. -mx-8 px-8 überbrückt das Layout-Padding,
damit der Balken bündig sitzt.

ContractForm: Heading + Kunde-Zeile sticky oben, Speichern/
Abbrechen sticky unten. Damit muss man beim langen Formular nicht
mehr nach oben oder unten rollen.

ScrollToTopButton: von bottom-6 auf bottom-24 verschoben, sonst
sitzt der runde Nach-oben-Button beim Runterscrollen genau über
dem Speichern-Button im Sticky-Footer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 20:46:15 +02:00
parent b23ebeefc3
commit b48cd72a2b
3 changed files with 56 additions and 40 deletions
@@ -29,10 +29,14 @@ export default function ScrollToTopButton() {
if (!isVisible) return null; if (!isVisible) return null;
// bottom-24 statt bottom-6: der Nach-oben-Button muss auf Seiten mit
// Sticky-Footer (z.B. ContractForm Speichern/Abbrechen) über dem Footer
// sitzen, sonst hängt er beim Runterscrollen genau über dem Speichern-
// Button. 96px reichen für alle heute vorhandenen Sticky-Footer.
return ( return (
<button <button
onClick={scrollToTop} onClick={scrollToTop}
className="fixed bottom-6 right-6 p-3 bg-gray-200 hover:bg-gray-300 text-gray-600 rounded-full shadow-md transition-all duration-300 opacity-70 hover:opacity-100 z-50" className="fixed bottom-24 right-6 p-3 bg-gray-200 hover:bg-gray-300 text-gray-600 rounded-full shadow-md transition-all duration-300 opacity-70 hover:opacity-100 z-50"
aria-label="Nach oben scrollen" aria-label="Nach oben scrollen"
title="Nach oben" title="Nach oben"
> >
@@ -1795,7 +1795,10 @@ export default function ContractDetail() {
return ( return (
<div> <div>
<div className="flex items-center justify-between mb-6"> {/* 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. */}
<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> <div>
<div className="flex items-center gap-4 mb-2"> <div className="flex items-center gap-4 mb-2">
<Button <Button
+11 -2
View File
@@ -777,6 +777,10 @@ export default function ContractForm() {
return ( return (
<div> <div>
{/* Sticky-Header: Zurück-Button + Titel + Kunde-Zeile bleiben beim
Scrollen oben stehen. Layout-Padding wird per -mx-8 px-8
ü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="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" />
@@ -786,7 +790,7 @@ export default function ContractForm() {
</h1> </h1>
</div> </div>
{customer && ( {customer && (
<p className="text-gray-500 ml-12 mb-6 flex items-center gap-1"> <p className="text-gray-500 ml-12 flex items-center gap-1">
Kunde:{' '} Kunde:{' '}
<Link <Link
to={`/customers/${customer.id}`} to={`/customers/${customer.id}`}
@@ -815,6 +819,7 @@ export default function ContractForm() {
</button> </button>
</p> </p>
)} )}
</div>
{error && ( {error && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded-lg"> <div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded-lg">
@@ -1784,7 +1789,11 @@ export default function ContractForm() {
/> />
</Card> </Card>
<div className="flex justify-end gap-4"> {/* Sticky-Footer: Speichern/Abbrechen bleiben unten sichtbar,
damit man beim langen Formular nicht ganz nach unten scrollen
muss. -mx-8 px-8 -mb-8 zieht den Balken bündig an die
Layout-Padding-Kante. */}
<div className="sticky bottom-0 z-20 -mx-8 px-8 pt-3 pb-3 -mb-8 mt-6 bg-gray-100 border-t border-gray-200 flex justify-end gap-4">
<Button type="button" variant="secondary" onClick={() => navigate(back.to, { state: back.state })}> <Button type="button" variant="secondary" onClick={() => navigate(back.to, { state: back.state })}>
Abbrechen Abbrechen
</Button> </Button>