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() { -
Wird geladen…
} @@ -215,7 +248,7 @@ export default function Rezepte() { {liste.map((r) => (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; }