// RecipePickerDialog — the dialog panel itself is styled generically by // Dialog.tsx's dialog.scss; only this feature's own filter bar / chips / // portions-confirmation step live here. `.recipes-page__status` and // `.field-error` come from recipes.scss (already loaded — RecipeTable / // RecipeTabs import it themselves). @use "../../styles/breakpoints" as bp; .recipe-picker-dialog { width: 95vw; max-width: 85rem; // Fixed, not just capped — `.dialog-panel`'s own `max-height` (dialog.scss) // only bounds how tall a shrink-to-fit dialog can grow, which is right // for every other dialog's small form but leaves this one's browsing // step at the mercy of how much content happens to be on screen (a short // personal-recipe tab vs. a ~25-item source browse). Pinning both // `height`/`max-height` to the same `80vh` keeps the browsing area a // consistent, generous size regardless of tab/content, on top of the // `.dialog-panel__body` fix below that makes that area actually use the // space instead of overflowing it. height: 80vh; max-height: 80vh; // `.dialog-panel__body` (dialog.scss) is a plain block-flow scroll // container by default — fine for every other dialog's small form, but // this one's browsing step embeds the same components `/recettes` uses // (`RecipeTable`'s `.recipe-table-wrap`, `RecipeSourcesPanel`'s // `.recipes-page__catalog`), both of which size themselves with // `flex: 1; min-height: 0` and need a `display: flex` ancestor for that // to mean anything — on the real page that ancestor is `.recipes-page` // itself (see its own doc comment for the identical fix that page // needed once); this dialog never renders that wrapper, so without this // the catalog/table just grew to its full content height instead of // being clipped and independently scrollable within the dialog's own // bounds — harmless for the handful of rows a personal recipe tab // usually has, but a source tab's ~25-item browse list made it obvious: // everything past the dialog's fixed height rendered, technically, just // never inside the visible/scrollable area. Scoped to this dialog only // — every other `Dialog` caller keeps the plain block layout. .dialog-panel__body { display: flex; flex-direction: column; } .recipe-table-wrap { flex: 1; min-height: 0; } // `.recipes-page__catalog`'s own column split (recipes.scss) sizes the // detail pane with `minmax(35vw, 38vw)` — a fraction of the *viewport*, // which tracked `/recettes`' own width there (that grid spans nearly the // whole page) but has nothing to do with this dialog's width, now a // fixed 92vw/75rem of its own. Overridden here as a fraction of the // dialog itself instead, so the two panes stay proportionate to each // other regardless of viewport size. .recipes-page__catalog { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); } // On a phone this dialog goes truly full screen — same as every other // `Dialog` (see `dialog.scss`'s own `bp.phone` block) — rather than // keeping the 80vh pin above. That pin sizes a roomy desktop browsing // area, but on a ~375px-wide screen it left far too little height once // the tall `.recipe-picker__filters` block (search + ingredient chips + // `IngredientPicker` + `DietTagSelect` + household toggle, rendered only // for the four non-source tabs) was laid out first — the catalog below // it collapsed to ~0 and the recipe table, though in the DOM, was // rendered in a zero-height scrollport, so every non-source tab looked // empty (issue #111). @include bp.phone { height: 100vh; max-height: 100vh; // recipes.scss already collapses `&__catalog` to a single column under // 1024px, but the rule further up (2 classes) outweighs that // page-scoped `@media` block and would keep two ~90px columns side by // side on a phone. Re-collapse it here — the list stacks above the // detail pane, same as the full `/recettes` page. .recipes-page__catalog { grid-template-columns: 1fr; // ...and stop it competing for leftover flex space it won't get. As a // `flex: 1` (⇒ `flex-basis: 0`) child of the flex-column // `.dialog-panel__body`, with the filter block above consuming the // column, it otherwise shrinks to ~0. On a phone the whole body // scrolls instead (`.dialog-panel__body`'s own `overflow-y: auto`), // so the catalog just takes its content height. flex: none; } // Same reasoning: size the table wrapper to its content and let it // scroll with the body, instead of clipping inside a collapsed catalog. .recipe-table-wrap { flex: none; height: auto; } // The filter block must stay fully reachable — it's the one part a // person needs to actually narrow the list down — so never let the // flex column squeeze it. .recipe-picker__filters { flex-shrink: 0; } } } .recipe-picker__filters { display: flex; flex-direction: column; gap: var(--space-md); margin-bottom: var(--space-md); } .recipe-picker__search { width: 100%; padding: var(--space-sm) var(--space-md); border: 1.5px solid var(--color-border); border-radius: var(--radius-base); background: var(--color-surface); color: var(--color-text); font-size: var(--font-size-base); } .recipe-picker__filter-label { display: block; margin-bottom: var(--space-xs); font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text-muted); } .recipe-picker__chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); } .filter-chip { display: inline-flex; align-items: center; gap: var(--space-xs); padding: var(--space-xs) var(--space-sm); border-radius: var(--radius-pill); background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface)); color: var(--color-primary); font-size: var(--font-size-sm); font-weight: 600; button { border: none; background: transparent; color: inherit; cursor: pointer; padding: 0; font-size: var(--font-size-xs); } } .recipe-picker__toggle-ingredient-picker { padding: var(--space-xs) var(--space-sm); border: 1.5px dashed var(--color-border); border-radius: var(--radius-pill); background: transparent; color: var(--color-text-muted); font-size: var(--font-size-sm); cursor: pointer; &:hover { border-color: var(--color-primary); color: var(--color-primary); } } // --- Portions confirmation step ----------------------------------------- .recipe-picker-confirm { display: flex; flex-direction: column; gap: var(--space-sm); label { font-weight: 600; } input[type="number"] { padding: var(--space-sm) var(--space-md); border: 1.5px solid var(--color-border); border-radius: var(--radius-base); background: var(--color-surface); color: var(--color-text); font-size: var(--font-size-base); max-width: 8rem; } } // "→ N portions préparées, M en restes" — shown under the portions field // only when the requested count doesn't land on a whole batch of the // recipe, so the user sees the leftover a batch-cooking round-up creates // before confirming (issue #34). .recipe-picker-confirm__batched-hint { margin: 0; color: var(--color-text-muted); font-size: var(--font-size-sm); } .recipe-picker-confirm__actions { display: flex; justify-content: flex-end; gap: var(--space-sm); margin-top: var(--space-sm); } .recipe-picker-confirm__confirm { background: var(--color-primary); color: var(--color-surface); border: none; border-radius: var(--radius-base); padding: var(--space-sm) var(--space-md); font-family: var(--font-body); font-size: var(--font-size-sm); font-weight: 600; cursor: pointer; &:hover { background: var(--color-primary-hover); } &:disabled { opacity: 0.6; cursor: not-allowed; } } // The dialog-level footer (`Dialog`'s `footer` prop, used by this dialog's // main browsing step) — "Confirmer" reuses `.recipe-picker-confirm__confirm` // above (same primary-button look as the portions step's own "Ajouter au // planning"), "Fermer" needs its own secondary style since a bare // `