Seitenleiste: die vier Bestandslisten klappen zusammen

Lebensmittel, Gegenstaende, Einzelstuecke und Chargen stehen jetzt unter
einer Gruppe "Listen". Sie ist der laengste Block der Leiste und wird im
Alltag selten gebraucht - wer einlagert, nimmt die Knoepfe darueber.
Vorgabe ist zugeklappt, der Zustand bleibt im Browser (wie die
Spalteneinstellungen der Tabellen).

Steht man auf einer dieser Seiten, klappt die Gruppe auf, ohne den
gemerkten Zustand zu ueberschreiben - sonst saehe man nicht, wo man ist.

Die Eintraege bleiben dabei im Dokument und werden nur per Klasse
verborgen: Auf schmalen Bildschirmen wird die Leiste waagerecht, dort gibt
es keine Abschnitte und kein Platzproblem, also blendet die CSS sie wieder
ein und die Gruppe selbst aus. Waeren sie ausgehaengt, waeren sie dort weg.

Die Mobil-Regeln brauchen `.sidebar` davor: Ein Media-Query erhoeht die
Spezifitaet nicht, und die Grundregeln stehen weiter unten in der Datei -
ohne das gewinnt die spaetere Regel und die Ausnahme greift nie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-02 12:36:23 +02:00
co-authored by Claude Opus 5
parent afacf01f11
commit a182037153
2 changed files with 74 additions and 4 deletions
+50 -4
View File
@@ -34,6 +34,17 @@ import Konto from "./pages/Konto";
const navClass = ({ isActive }) => (isActive ? "nav-link active" : "nav-link"); const navClass = ({ isActive }) => (isActive ? "nav-link active" : "nav-link");
// Die vier Bestandslisten stehen unter einer zuklappbaren Gruppe. Sie sind der
// laengste Block der Leiste und wird im Alltag selten gebraucht – wer einlagert,
// nutzt die Knoepfe darueber.
const LISTEN = [
{ to: "/lebensmittel", icon: "food", label: "Lebensmittel" },
{ to: "/gegenstaende", icon: "package", label: "Gegenstände" },
{ to: "/items", icon: "tag", label: "Einzelstücke" },
{ to: "/chargen", icon: "box", label: "Chargen" },
];
const LISTEN_OFFEN_KEY = "nav:listen-offen";
function NavItem({ to, icon, label, end }) { function NavItem({ to, icon, label, end }) {
return ( return (
<NavLink to={to} end={end} className={navClass}> <NavLink to={to} end={end} className={navClass}>
@@ -46,7 +57,24 @@ function NavItem({ to, icon, label, end }) {
function Sidebar() { function Sidebar() {
const { user, isAdmin, logout } = useAuth(); const { user, isAdmin, logout } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const { pathname } = useLocation();
const [dashboards, setDashboards] = useState([]); const [dashboards, setDashboards] = useState([]);
// Zugeklappt als Vorgabe; der Zustand bleibt im Browser, wie die
// Spalteneinstellungen der Tabellen.
const [listenOffen, setListenOffen] = useState(
() => localStorage.getItem(LISTEN_OFFEN_KEY) === "1",
);
// Steht man auf einer dieser Seiten, ist die Gruppe offen – ohne den
// gemerkten Zustand zu ueberschreiben. Sonst saehe man nicht, wo man ist.
const aufEinerListe = LISTEN.some((e) => pathname.startsWith(e.to));
function klappeListen() {
setListenOffen((alt) => {
const neu = !alt;
localStorage.setItem(LISTEN_OFFEN_KEY, neu ? "1" : "0");
return neu;
});
}
// Die Dashboards des Benutzers stehen als eigene Eintraege in der Leiste. // Die Dashboards des Benutzers stehen als eigene Eintraege in der Leiste.
// Sie aendern sich beim Anlegen und Loeschen, deshalb horcht die Leiste auf // Sie aendern sich beim Anlegen und Loeschen, deshalb horcht die Leiste auf
@@ -82,10 +110,28 @@ function Sidebar() {
))} ))}
<NavItem to="/checkin" icon="checkin" label="Einlagern" /> <NavItem to="/checkin" icon="checkin" label="Einlagern" />
<NavItem to="/checkout" icon="checkout" label="Auslagern" /> <NavItem to="/checkout" icon="checkout" label="Auslagern" />
<NavItem to="/lebensmittel" icon="food" label="Lebensmittel" /> {/* Auf schmalen Bildschirmen wird die Leiste waagerecht; dort gibt es
<NavItem to="/gegenstaende" icon="package" label="Gegenstände" /> keine Abschnitte (styles.css) und die Eintraege stehen flach
<NavItem to="/items" icon="tag" label="Einzelstücke" /> nebeneinander – zuklappen loest dort kein Platzproblem. */}
<NavItem to="/chargen" icon="box" label="Chargen" /> <button type="button" className="nav-group" onClick={klappeListen}
aria-expanded={listenOffen || aufEinerListe}>
<Icon name="chevron" size={14}
className={listenOffen || aufEinerListe ? "" : "rot-90"} />
<span>Listen</span>
</button>
{LISTEN.map((e) => (
<NavLink key={e.to} to={e.to}
className={({ isActive }) => [
"nav-link nav-child",
isActive ? "active" : "",
// Nicht ausgehaengt, nur verborgen: auf schmalen Bildschirmen
// blendet die CSS sie wieder ein, dort gibt es keine Gruppe.
listenOffen || aufEinerListe ? "" : "nav-zu",
].filter(Boolean).join(" ")}>
<Icon name={e.icon} size={16} />
<span className="label">{e.label}</span>
</NavLink>
))}
<NavItem to="/shopping" icon="cart" label="Einkaufsliste" /> <NavItem to="/shopping" icon="cart" label="Einkaufsliste" />
<NavItem to="/mindestbestaende" icon="check" label="Mindestbestände" /> <NavItem to="/mindestbestaende" icon="check" label="Mindestbestände" />
<NavItem to="/verliehen" icon="loan" label="Verliehen" /> <NavItem to="/verliehen" icon="loan" label="Verliehen" />
+24
View File
@@ -1036,6 +1036,15 @@ select.zeitraum:hover { color: var(--text); }
.sidebar-brand { padding: var(--sp-3) var(--sp-4); } .sidebar-brand { padding: var(--sp-3) var(--sp-4); }
.sidebar-nav { flex-direction: row; overflow-x: auto; padding: var(--sp-2) var(--sp-3); gap: var(--sp-1); } .sidebar-nav { flex-direction: row; overflow-x: auto; padding: var(--sp-2) var(--sp-3); gap: var(--sp-1); }
.nav-section { display: none; } .nav-section { display: none; }
/* Waagerechte Leiste: Zuklappen loest hier kein Platzproblem, und
untereinander ist ohnehin kein Platz. Die Gruppe verschwindet, ihre
Eintraege stehen flach zwischen den anderen – wie vorher.
Mit `.sidebar` davor, weil die Grundregeln weiter unten in der Datei
stehen: ein Media-Query erhoeht die Spezifitaet nicht, und bei
Gleichstand gewinnt die spaetere Regel. */
.sidebar .nav-group { display: none; }
.sidebar .nav-link.nav-zu { display: flex; }
.sidebar .nav-link.nav-child { padding-left: 8px; }
.nav-link { white-space: nowrap; } .nav-link { white-space: nowrap; }
.nav-link span.label { display: none; } .nav-link span.label { display: none; }
.nav-link { padding: 8px; } .nav-link { padding: 8px; }
@@ -1088,6 +1097,21 @@ select.zeitraum:hover { color: var(--text); }
} }
.nav-link.sub.active { font-weight: 600; } .nav-link.sub.active { font-weight: 600; }
/* Zuklappbare Gruppe in der Seitenleiste: sieht aus wie eine
Abschnittsueberschrift, ist aber ein Knopf. */
.nav-group {
display: flex; align-items: center; gap: var(--sp-2);
width: 100%; background: none; border: none; cursor: pointer;
padding: var(--sp-4) var(--sp-3) var(--sp-1);
font-size: 0.68rem; font-weight: 600; letter-spacing: 0.06em;
text-transform: uppercase; color: var(--muted); opacity: 0.7;
font-family: inherit; text-align: left;
}
.nav-group:hover { opacity: 1; }
/* Eingerueckt wie die Dashboard-Unterpunkte, aber mit Platz fuers Symbol. */
.nav-link.nav-child { padding-left: 26px; font-size: 0.86rem; }
.nav-link.nav-zu { display: none; }
/* ---- Aktionskarten: Ein- und Auslagern ohne Seitenwechsel ---- */ /* ---- Aktionskarten: Ein- und Auslagern ohne Seitenwechsel ---- */
.aktion-karte { gap: 10px; } .aktion-karte { gap: 10px; }