// ============================================================================= // Styles for AppLayout — the shell (sidebar + content area) wrapping every // authenticated page. Colocated next to AppLayout.tsx since nothing else // uses these classes. // ============================================================================= // No `@use` of the theme partial needed here — see HomePage.scss's identical // note: every token below is a CSS custom property, available at runtime. // Breakpoints are the one exception (a `@media` condition can't read a CSS // custom property, see `_breakpoints.scss`'s own doc comment) — that partial // still needs a real Sass `@use`. @use "../styles/breakpoints" as bp; // `height` (not `min-height`): the layout must stay pinned to exactly the // viewport, never grow taller than it — otherwise a tall page scrolls the // whole document, dragging the sidebar out of view with it. `overflow: // hidden` backs that up so nothing can force the document itself to grow; // `.app-content` below is the one place that actually scrolls. .app-layout { height: 100vh; overflow: hidden; display: flex; background: var(--color-background); } // Fixed-width nav rail. Its own surface (not the page background), same // elevation language as a card, so it reads as a distinct, permanent piece // of chrome rather than part of the scrolling content. `width` transitions // so collapsing/expanding (see `.collapsed` below) reads as a deliberate // motion rather than an abrupt layout jump. .app-sidebar { display: flex; flex-direction: column; flex-shrink: 0; width: 15rem; padding: var(--space-lg) var(--space-md); background: var(--color-surface); border-right: 1px solid var(--color-border); transition: width 0.15s ease; // Own scroll, independent of `.app-content` — only matters on a very // short viewport with a long nav, but keeps the rail from ever forcing // `.app-layout` past 100vh. overflow-y: auto; &__top { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--space-sm) var(--space-xl); } &__brand { display: flex; align-items: center; font-family: var(--font-display); font-weight: 700; font-size: var(--font-size-lg); color: var(--color-primary); overflow: hidden; white-space: nowrap; } // Full wordmark by default; swapped for a short monogram when collapsed // (see `.collapsed &__brand-full`/`&__brand-mark` below) — clearer than // an arbitrary icon standing in for the brand itself. &__brand-mark { display: none; } &__collapse-toggle { width: 1.75rem; height: 1.75rem; flex-shrink: 0; display: grid; place-items: center; border: 1px solid var(--color-border); border-radius: var(--radius-base); background: var(--color-surface); color: var(--color-text-muted); cursor: pointer; svg { width: 1rem; height: 1rem; transition: transform 0.15s ease; } &:hover { background: var(--color-surface-alt); color: var(--color-text); } } &__nav { display: flex; flex: 1; flex-direction: column; gap: var(--space-xs); a { display: flex; align-items: center; gap: var(--space-sm); padding: var(--space-sm); border-radius: var(--radius-base); color: var(--color-text); font-weight: 600; font-size: var(--font-size-sm); text-decoration: none; white-space: nowrap; overflow: hidden; svg { flex-shrink: 0; width: 1.25rem; height: 1.25rem; } &:hover { background: var(--color-surface-alt); } // Applied by NavLink itself (see the `className` prop in // AppLayout.tsx), not a router-provided class — plain `.active`. &.active { background: var(--color-primary); color: #fff; } } } // --- "Paramètres" collapsible menu ------------------------------------- &__settings { padding-top: var(--space-md); border-top: 1px solid var(--color-border); } &__settings-toggle { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: var(--space-sm); font-family: var(--font-body); font-size: var(--font-size-sm); font-weight: 600; text-align: left; color: var(--color-text); background: none; border: none; border-radius: var(--radius-base); cursor: pointer; &:hover { background: var(--color-surface-alt); } } &__settings-toggle-left { display: flex; align-items: center; gap: var(--space-sm); overflow: hidden; svg { flex-shrink: 0; width: 1.25rem; height: 1.25rem; } } &__settings-nav { margin-top: var(--space-xs); } // --- Account menu (bottom of the sidebar) ------------------------------- &__footer { position: relative; padding-top: var(--space-md); border-top: 1px solid var(--color-border); } &__account-toggle { display: flex; align-items: center; gap: var(--space-sm); width: 100%; padding: 0.4rem var(--space-sm); font-family: var(--font-body); font-size: var(--font-size-sm); font-weight: 600; text-align: left; cursor: pointer; border-radius: var(--radius-base); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text); overflow: hidden; white-space: nowrap; &:hover { background: var(--color-surface-alt); } } &__avatar { flex-shrink: 0; width: 1.5rem; height: 1.5rem; display: grid; place-items: center; border-radius: 50%; background: var(--color-primary); color: #fff; font-size: var(--font-size-xs); font-weight: 700; } // Anchored just above the toggle rather than inline in the flow — it's a // transient overlay, not part of the sidebar's permanent layout. &__account-menu { position: absolute; bottom: calc(100% + var(--space-xs)); left: 0; right: 0; display: flex; flex-direction: column; overflow: hidden; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-base); box-shadow: var(--shadow-md); a, button { padding: var(--space-sm); font-family: var(--font-body); font-size: var(--font-size-sm); font-weight: 600; text-align: left; text-decoration: none; color: var(--color-text); background: none; border: none; cursor: pointer; white-space: nowrap; &:hover { background: var(--color-surface-alt); } } } // App version — a quiet diagnostic footnote below the account menu, not // an interactive element (hence `aria-hidden` on the `

` in // AppLayout.tsx). Hidden whenever space is at a premium — the icon-only // rail and the mobile horizontal bar (see the `.collapsed` block and the // `@media (max-width: 640px)` block below). &__version { margin: var(--space-xs) 0 0; padding: 0 var(--space-sm); font-size: var(--font-size-xs); color: var(--color-text-muted); text-align: center; } // --- Collapsed (icon-only rail) state ----------------------------------- // A single class toggle on the root element — every nested rule below // just hides labels/chevrons and re-centers icons via CSS, no child // component needs to know the sidebar is collapsed. // // Scoped to phone-up (`_breakpoints.scss`'s exact complement of the phone // breakpoint): `isCollapsed` persists in `localStorage` // (`batchcooking:sidebarCollapsed`) independently of viewport width, so a // user who collapsed the desktop rail and then shrinks the window still // carries that flag. This guard used to matter for a second reason too — // on a phone, `.app-sidebar` isn't even rendered anymore at all (see // AppLayout.tsx: the mobile bottom nav is a fully separate component tree, // not this same `