From e93f565629ab767fcd5ececfc17d444ae5e50d7f Mon Sep 17 00:00:00 2001 From: Guido Date: Sat, 22 Aug 2026 20:54:03 +0200 Subject: [PATCH] fix: Einstellungskarten in feste Spalten statt Spaltenfluss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Abrufen und beim Bearbeiten sprang die halbe Seite. Ursache war der CSS-Spaltenfluss: Aendert eine Karte ihre Hoehe, verteilt der Browser ALLE Karten neu auf die Spalten. Ich hatte das zweimal am Symptom kuriert (Formular in den Dialog verschoben), aber die naechste wachsende Karte brachte es zurueck. Jetzt bekommt jede Karte fest ihre Spalte. Waechst eine, verschiebt sich nur, was in derselben Spalte darunter steht. Die Spaltenzahl kommt aus der tatsaechlichen Containerbreite (ResizeObserver), nicht aus der Fensterbreite – in der schmalen Seitenleisten-Ansicht stimmte das sonst nicht. Co-Authored-By: Claude Opus 5 --- web/src/components/Kartenspalten.jsx | 47 ++++++++++++++++++++++++++++ web/src/finance.css | 16 ---------- web/src/pages/Account.jsx | 5 +-- web/src/pages/Settings.jsx | 5 +-- 4 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 web/src/components/Kartenspalten.jsx diff --git a/web/src/components/Kartenspalten.jsx b/web/src/components/Kartenspalten.jsx new file mode 100644 index 0000000..87e9a91 --- /dev/null +++ b/web/src/components/Kartenspalten.jsx @@ -0,0 +1,47 @@ +import { Children, useEffect, useRef, useState } from "react"; + +/** + * Karten in feste Spalten verteilen. + * + * Warum nicht CSS: Ein Raster richtet die Karten zeilenweise aus – die zweite + * Reihe beginnt erst unter der höchsten Karte der ersten, was bei sehr + * unterschiedlich hohen Karten grosse Löcher hinterlässt. Der CSS-Spaltenfluss + * (`column-width`) füllt zwar schön auf, verteilt aber ALLE Karten neu, sobald + * eine ihre Höhe ändert. Beim Bearbeiten oder nach einem Abruf sprang deshalb + * die halbe Seite. + * + * Hier bekommt jede Karte fest ihre Spalte (reihum). Wächst eine, verschiebt + * sich nur, was in derselben Spalte darunter steht – alles andere bleibt liegen. + */ +export default function Kartenspalten({ children, min = 380, gap = "var(--sp-4)" }) { + const container = useRef(null); + const [spalten, setSpalten] = useState(1); + + useEffect(() => { + const el = container.current; + if (!el) return undefined; + const messen = () => { + const breite = el.getBoundingClientRect().width; + setSpalten(Math.max(1, Math.floor((breite + 24) / (min + 24)))); + }; + messen(); + const beobachter = new ResizeObserver(messen); + beobachter.observe(el); + return () => beobachter.disconnect(); + }, [min]); + + const karten = Children.toArray(children).filter(Boolean); + const verteilt = Array.from({ length: spalten }, () => []); + karten.forEach((karte, i) => verteilt[i % spalten].push(karte)); + + return ( +
+ {verteilt.map((spalte, i) => ( +
+ {spalte} +
+ ))} +
+ ); +} diff --git a/web/src/finance.css b/web/src/finance.css index f66f352..042e86b 100644 --- a/web/src/finance.css +++ b/web/src/finance.css @@ -284,22 +284,6 @@ min-width: 0; } -/* Einstellungen & Mein Konto: Karten fliessen spaltenweise. - Ein Raster richtet die Karten zeilenweise aus – die zweite Reihe beginnt erst - unter der HOECHSTEN Karte der ersten, was bei sehr unterschiedlich hohen - Karten ein grosses Loch hinterlaesst. Der Spaltenfluss stapelt stattdessen - weiter, sobald oben Platz frei wird. Die Spalten sind bewusst breit: vier - schmale Karten nebeneinander waren auf dem Notebook nicht mehr lesbar. */ -.settings-grid { - column-width: 380px; - column-gap: var(--sp-4); -} -.settings-grid > * { - break-inside: avoid; - margin-bottom: var(--sp-4); - width: 100%; -} - /* Nutzer-Chip in der Seitenleisten-Fußzeile ist ein Link auf „Mein Konto". */ a.sidebar-user { text-decoration: none; diff --git a/web/src/pages/Account.jsx b/web/src/pages/Account.jsx index 470eb27..13b9795 100644 --- a/web/src/pages/Account.jsx +++ b/web/src/pages/Account.jsx @@ -6,6 +6,7 @@ import { api, ApiError } from "../api"; import { useToast } from "../toast"; import Icon from "../components/Icon"; import PasswordInput from "../components/PasswordInput"; +import Kartenspalten from "../components/Kartenspalten"; import PasswordRules from "../components/PasswordRules"; import Geraetemarken from "./settings/Geraetemarken"; import { DEFAULT_POLICY, meetsPolicy } from "../passwordPolicy"; @@ -56,7 +57,7 @@ export default function Account() { return (

{t("account.title")}

-
+

{t("account.password")}

{fehler && ( @@ -101,7 +102,7 @@ export default function Account() {
-
+ ); } diff --git a/web/src/pages/Settings.jsx b/web/src/pages/Settings.jsx index e2a052c..257dc51 100644 --- a/web/src/pages/Settings.jsx +++ b/web/src/pages/Settings.jsx @@ -5,6 +5,7 @@ import { useAuth } from "../auth"; import { useToast } from "../toast"; import { api } from "../api"; import Icon from "../components/Icon"; +import Kartenspalten from "../components/Kartenspalten"; import PolicyCard from "./settings/PolicyCard"; import Postfaecher from "./settings/Postfaecher"; import RestoreCard from "./settings/RestoreCard"; @@ -76,7 +77,7 @@ export default function Settings() { )} -
+

{t("settings.general")}

+
); }