batchCooking/apps/web/src/pages/cooking-session/cooking-session-page.scss
Nicolas bdc1b4c9e9
Some checks failed
CI / lint (push) Successful in 52s
CI / build (push) Successful in 1m40s
CI / intent-service-test (push) Successful in 3m57s
CI / e2e (push) Successful in 6m32s
CI / test (push) Failing after 13m50s
feat(cooking-session): déplie une recette de l'onglet Recettes au lieu de rediriger
Cliquer sur une recette de l'onglet Recettes de /cuisiner naviguait vers
/recettes/:id, quittant la session de cuisine en cours. La ligne devient un
bouton qui déplie désormais le détail complet de la recette directement
sous elle (RecipeDetailPanel, showActions=false — même traitement "aperçu"
que RecipePickerDialog), chargé à la demande via GET /recipes/:id, un seul
dépliant ouvert à la fois (même logique que le sous-menu Paramètres de la
sidebar). RecipesTab est désormais keyed par semaine comme PlanTab, pour
que changer de semaine referme un dépliant devenu obsolète.

Clé i18n recipesTab.viewRecipe supprimée (plus d'appelant).

Tests : cooking-session-page.cy.ts (déplier/replier, un seul ouvert à la
fois) + cooking-session.feature (scénario dédié). pnpm lint + pnpm --filter
web build clean ; 16/16 tests passants (Chrome headless).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 23:08:47 +02:00

488 lines
13 KiB
SCSS

// =============================================================================
// Styles specific to CookingSessionPage — colocated next to
// CookingSessionPage.tsx since nothing else uses these classes.
//
// Layout: a fixed page shell (header + subtitle + tab bar), then one of two
// tab panels. The "Recettes" panel is a plain scrolling list; the "Plan"
// panel (`.cooking-plan`) is itself a flex column — a pinned stepper and
// meta line on top, a pinned prev/next bar at the bottom, and the current
// phase scrolling in between so the nav is always reachable.
//
// Every value goes through a `var(--token)` from _theme.scss; light/dark is
// handled entirely by those tokens being redefined, nothing here branches
// on the theme.
// =============================================================================
.cooking-session-page {
height: 100%;
display: flex;
flex-direction: column;
&__header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: var(--space-md);
margin-bottom: var(--space-sm);
}
&__subtitle {
flex-shrink: 0;
margin: 0 0 var(--space-md);
color: var(--color-text-muted);
font-size: var(--font-size-md);
}
&__status {
color: var(--color-text-muted);
font-size: var(--font-size-md);
}
&__status--error {
color: var(--color-error);
}
// --- Tab bar ---------------------------------------------------------------
// Same underline-tab language as the recipe catalog's `.recipe-tabs`
// (features/recipes/recipes.scss): scrolls within itself rather than
// widening the page (global.scss forbids a horizontal body scroll).
&__tabs {
flex-shrink: 0;
display: flex;
gap: var(--space-xs);
overflow-x: auto;
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--space-md);
}
&__tab {
appearance: none;
flex-shrink: 0;
padding: var(--space-sm) var(--space-md);
font-family: var(--font-display);
font-size: var(--font-size-sm);
font-weight: 700;
letter-spacing: 0.02em;
color: var(--color-text-muted);
background: none;
border: none;
border-bottom: 2.5px solid transparent;
cursor: pointer;
transition:
color 0.15s ease,
border-color 0.15s ease;
&:hover {
color: var(--color-text);
}
&.active {
color: var(--color-primary);
border-color: var(--color-primary);
}
}
}
// --- "Recettes" tab ---------------------------------------------------------
// The week's planned recipes as a flat list — name and its own portion
// count on an always-visible summary row; clicking one unfolds its full
// detail inline underneath (fetched on demand, see `RecipesTab`/`&__detail`
// below) instead of navigating to the recipe's own page.
.cooking-recipes {
flex: 1;
min-height: 0;
overflow-y: auto;
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-xs);
// The card: background/radius/shadow live here (not on `&__row` below) so
// the summary row and, once unfolded, the detail panel read as one block
// instead of two stacked cards.
&__item {
display: flex;
flex-direction: column;
background: var(--color-surface);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
// Keeps the detail panel's own edges (see `&__detail` below) inside the
// card's rounded corners instead of poking past them.
overflow: hidden;
}
// The always-visible clickable summary — a real `<button>` (not a `<div
// onClick>`) so the whole row is a proper keyboard/click target for the
// expand/collapse toggle (`RecipesTab`), styled to read exactly like the
// plain flex row it replaces.
&__row {
appearance: none;
width: 100%;
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
border: none;
background: none;
font: inherit;
text-align: start;
color: inherit;
cursor: pointer;
}
&__name {
font-weight: 600;
color: var(--color-text);
}
&__portions {
padding: 0.1rem var(--space-sm);
border-radius: var(--radius-pill);
background: var(--color-surface-alt);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-variant-numeric: tabular-nums;
}
// Pushed to the trailing edge — a quiet affordance hinting the row
// unfolds, not an action of its own (the whole row is already clickable).
&__chevron {
margin-left: auto;
flex-shrink: 0;
color: var(--color-text-muted);
}
// The unfolded recipe — reuses `.recipe-detail-panel` wholesale (see
// recipes.scss) rather than re-implementing a second "read a recipe"
// layout, just inset under the summary row with a separating rule.
&__detail {
padding: 0 var(--space-md) var(--space-md);
border-top: 1px solid var(--color-border);
.recipe-detail-panel {
// The shared panel is styled for a standalone side panel (its own
// card background/shadow, fills 100% of a sized flex parent, scrolls
// internally) — already sitting inside this card and in normal
// document flow here, so neutralize all of that rather than double
// up on card chrome or clip content to a height nothing constrains.
height: auto;
overflow-y: visible;
padding-top: var(--space-md);
background: none;
box-shadow: none;
border-radius: 0;
}
}
}
// --- "Plan" tab -----------------------------------------------------------
.cooking-plan {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: var(--space-md);
// Meta line under the stepper: "Étape 2 / 4 · Cuisson" on the left, the
// "{done} / {total} tâches" counter on the right.
&__meta {
flex-shrink: 0;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: var(--space-sm);
}
&__position {
font-family: var(--font-display);
font-weight: 700;
font-size: var(--font-size-md);
color: var(--color-text);
}
&__count {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-variant-numeric: tabular-nums;
}
// The current phase scrolls; everything around it stays put.
.cooking-phase {
flex: 1;
min-height: 0;
overflow-y: auto;
}
&__nav {
flex-shrink: 0;
display: flex;
justify-content: space-between;
gap: var(--space-sm);
}
&__nav-btn {
appearance: none;
padding: var(--space-sm) var(--space-lg);
border: 1.5px solid var(--color-primary);
border-radius: var(--radius-base);
background: var(--color-surface);
color: var(--color-primary);
font-family: var(--font-display);
font-weight: 700;
font-size: var(--font-size-sm);
cursor: pointer;
transition:
background-color 0.15s ease,
opacity 0.15s ease;
&:hover:not(:disabled) {
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
}
&:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
// --- Stepper --------------------------------------------------------------
// One clickable node per phase, in a row that scrolls sideways within
// itself when the phases don't all fit. A thin connector sits between
// consecutive nodes.
.cooking-stepper {
flex-shrink: 0;
list-style: none;
margin: 0;
padding: 0 0 var(--space-xs);
display: flex;
gap: var(--space-xs);
overflow-x: auto;
&__step {
flex: 0 0 auto;
display: flex;
align-items: center;
&:not(:last-child)::after {
content: "";
width: var(--space-md);
height: 2px;
background: var(--color-border);
margin: 0 var(--space-xs);
}
}
&__node {
appearance: none;
display: flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
border: 1.5px solid var(--color-border);
border-radius: var(--radius-pill);
background: var(--color-surface);
color: var(--color-text-muted);
font-family: var(--font-display);
font-size: var(--font-size-sm);
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition:
color 0.15s ease,
border-color 0.15s ease,
background-color 0.15s ease;
&:hover {
color: var(--color-text);
border-color: var(--color-primary);
}
// The phase currently on screen.
&--current {
color: var(--color-primary);
border-color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
}
// Every task in the phase is ticked.
&--done {
color: var(--color-success);
border-color: var(--color-success);
}
}
// The leading circle — a step number, or a check once the phase is done.
&__marker {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.4rem;
height: 1.4rem;
border-radius: var(--radius-pill);
background: var(--color-surface-alt);
font-size: var(--font-size-xs);
font-variant-numeric: tabular-nums;
}
&__node--current &__marker {
background: var(--color-primary);
color: var(--color-surface);
}
&__node--done &__marker {
background: var(--color-success);
color: var(--color-surface);
}
}
// --- One phase ----------------------------------------------------------------
.cooking-phase {
display: flex;
flex-direction: column;
gap: var(--space-sm);
// A phase-level caveat — today only the "four requis à 2 températures"
// conflict. A tinted callout in the accent colour so it reads as a
// heads-up, not a plain paragraph.
&__note {
margin: 0;
padding: var(--space-sm) var(--space-md);
border-left: 3px solid var(--color-accent);
border-radius: 0 var(--radius-md) var(--radius-md) 0;
background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
color: var(--color-text);
font-size: var(--font-size-sm);
font-weight: 600;
}
// "Pendant ce temps" — passive cooks still running from earlier phases.
&__background {
border-left: 3px solid var(--color-border);
padding: var(--space-xs) var(--space-md);
color: var(--color-text-muted);
font-size: var(--font-size-sm);
&-title {
display: block;
font-weight: 600;
margin-bottom: 0.15rem;
}
ul {
margin: 0;
padding-left: var(--space-md);
}
}
// Mini sections — one per exact combination of recipes a task applies to
// (see `groupTasksByRecipes` in cooking-session.ts): a pooled prep/setup
// task pools two-plus recipes and gets its own heading, a plain step's
// section names just the one recipe it's for. Lets a cook juggling
// several recipes at once jump straight to "what do I do for Recipe X"
// instead of scanning one flat list.
&__recipe-group {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
&__recipe-group-title {
margin: 0;
padding-bottom: var(--space-xs);
border-bottom: 1px solid var(--color-border);
font-family: var(--font-display);
font-size: var(--font-size-sm);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--color-text-muted);
}
&__tasks {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-sm);
> li {
display: flex;
}
// The task card *is* a shared "selectable card" checkbox label (see
// global.scss's `label:has(> input[type="checkbox"])`), so its frame,
// hover, focus ring and hidden native input all come from there. The
// rules below only set what's task-specific — a top-aligned multi-line
// body, the merged-prep accent, and a faded/struck "done" look — and
// are kept at `.cooking-phase__tasks` depth on purpose so they outrank
// that shared rule's own `&.is-selected` treatment.
.cooking-task {
flex: 1;
align-items: flex-start;
}
.cooking-task--merged-prep,
.cooking-task--merged-setup {
border-left: 3px solid var(--color-accent);
}
// Ticked = done: cancel the shared rule's "selected" green tint/border
// (which would clash with the stepper's own green "current" node) and
// just fade the card and strike its headline.
.cooking-task--done {
opacity: 0.7;
background: var(--color-surface);
border-color: var(--color-border);
.cooking-task__headline {
text-decoration: line-through;
}
}
}
}
// --- One task card --------------------------------------------------------
// (frame comes from the shared checkbox-label rule — see above)
.cooking-task {
&__body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
&__headline {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-xs);
font-weight: 600;
color: var(--color-text);
}
// A pooled prep task is the headline feature of this page — flag it.
&__badge {
padding: 0.05rem var(--space-xs);
border-radius: var(--radius-pill);
background: var(--color-accent);
color: var(--color-surface);
font-size: var(--font-size-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
&__utensils {
display: block;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
}