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>
488 lines
13 KiB
SCSS
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);
|
|
}
|
|
}
|