From 9f5d0a231038e0ba3f80835ccf36849e84082dd7 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Fri, 16 Oct 2026 16:48:43 +0200 Subject: [PATCH] Rezepte: Listenbreite ziehbar, lange Namen brechen um Die Rezeptliste hing auf hoechstens 320px fest, und .btn ist nowrap - ein Name wie "Cheesecake mit Streuseln und Fruchteinlage" lief damit einfach aus der Spalte heraus und war abgeschnitten. Jetzt sitzt zwischen Liste und Detail ein Griff zum Ziehen, Bedienung wie der Spaltengriff der Tabellen (Zeiger-Ereignisse, col-resize). Die Breite wird gemerkt und zwischen 180 und 640 Pixel gehalten: darunter ist die Liste unlesbar, darueber bleibt fuer die Zutaten nichts mehr uebrig. Dazu eine kleine Klasse .btn.umbruch, die das nowrap aufhebt - ein Knopf, dessen Beschriftung umbrechen darf, ist auch anderswo brauchbar. In der laufenden Oberflaeche nachgemessen: Ziehen um 120px ergibt genau 120px mehr und landet im Speicher, die Grenzen klemmen bei 180 und 640, und der lange Name braucht mit der Klasse zwei Zeilen (Text 372px bei 372px Spalte) statt ueber den Rand zu laufen (434px). Co-Authored-By: Claude Opus 5 --- web/src/pages/Rezepte.jsx | 48 ++++++++++++++++++++++++++++++++++++--- web/src/styles.css | 15 ++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/web/src/pages/Rezepte.jsx b/web/src/pages/Rezepte.jsx index 946d5d1..173c5c8 100644 --- a/web/src/pages/Rezepte.jsx +++ b/web/src/pages/Rezepte.jsx @@ -22,6 +22,12 @@ import { fmt } from "../units"; const BASIS_KURZ = { gram: "g", milliliter: "ml", piece: "Stk" }; const kurz = (basis) => BASIS_KURZ[basis] || basis || ""; +// Breite der Rezeptliste: gemerkt, mit Grenzen. Unter 180 ist die Liste +// unlesbar, ueber 640 bleibt fuer die Zutaten nichts mehr uebrig. +const REZEPT_BREITE = "rezepte:listenbreite"; +const BREITE_MIN = 180; +const BREITE_MAX = 640; + const ZUSTAND = { vorhanden: { text: "vorhanden", klasse: "ok", icon: "check" }, zu_wenig: { text: "zu wenig", klasse: "warn", icon: "alert" }, @@ -54,6 +60,33 @@ export default function Rezepte() { const [rezept, setRezept] = useState(null); const [portionen, setPortionen] = useState(urlPortionen); const [fehler, setFehler] = useState(null); + + // Breite der Rezeptliste. Gemerkt wie die Spaltenbreiten der Tabellen: Wer + // lange Rezeptnamen hat, will mehr Platz - und zwar dauerhaft. + const [listenBreite, setListenBreite] = useState(() => { + const roh = Number(localStorage.getItem(REZEPT_BREITE)); + return Number.isFinite(roh) && roh >= BREITE_MIN ? Math.min(roh, BREITE_MAX) : 320; + }); + const ziehen = useRef(null); + + function griffRunter(e) { + e.preventDefault(); + ziehen.current = { startX: e.clientX, startBreite: listenBreite }; + window.addEventListener("pointermove", griffBewegt); + window.addEventListener("pointerup", griffHoch); + } + function griffBewegt(e) { + const z = ziehen.current; + if (!z) return; + const breite = Math.max(BREITE_MIN, Math.min(BREITE_MAX, z.startBreite + (e.clientX - z.startX))); + setListenBreite(breite); + } + function griffHoch() { + ziehen.current = null; + window.removeEventListener("pointermove", griffBewegt); + window.removeEventListener("pointerup", griffHoch); + setListenBreite((b) => { try { localStorage.setItem(REZEPT_BREITE, String(b)); } catch { /* ignorieren */ } return b; }); + } const [laedt, setLaedt] = useState(true); // Welche Zutat gerade von Hand zugeordnet wird (null = keine). const [zuordnen, setZuordnen] = useState(null); @@ -203,8 +236,8 @@ export default function Rezepte() { -
+

{liste.length} Rezepte

{laedt &&

Wird geladen…

} @@ -215,7 +248,7 @@ export default function Rezepte() { {liste.map((r) => (
  • +
    +
    {!rezept &&

    Links ein Rezept wählen.

    } {rezept && ( diff --git a/web/src/styles.css b/web/src/styles.css index 5338c92..1e5a749 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -659,6 +659,21 @@ td select { width: auto; min-width: 0; max-width: 100%; } .dt-th.sorted .dt-th-label { color: var(--accent); } .dt-sort { font-size: 0.7em; flex: 0 0 auto; } .dt-resize { position: absolute; top: 0; right: 0; width: 8px; height: 100%; cursor: col-resize; touch-action: none; } + +/* Griff zwischen zwei Spalten einer Seite (Rezepte: Liste | Detail). Gleiche + Bedienung wie der Spaltengriff der Tabelle, nur senkrecht durchgehend. */ +.spalten-griff { + align-self: stretch; width: 8px; border-radius: 999px; + background: transparent; border: 0; padding: 0; + cursor: col-resize; touch-action: none; +} +.spalten-griff:hover, .spalten-griff:focus-visible { background: var(--accent-soft); } + +/* Ein Knopf, dessen Beschriftung umbrechen darf - .btn ist sonst nowrap und + schneidet lange Namen ab. */ +.btn.umbruch { + white-space: normal; text-align: left; height: auto; line-height: 1.35; +} .dt-resize:hover { background: var(--accent-soft); } .dt-filter td { padding: 4px 6px; vertical-align: middle; } .dt-filter-cell { display: flex; gap: 2px; align-items: center; }