fix(planning): affiche les recettes du picker sur mobile (#111)
All checks were successful
All checks were successful
Sur téléphone, le dialogue d'ajout d'une recette au planning devenait plein écran mais `.recipe-picker-dialog` gardait un `height: 80vh` figé. Dans cette colonne flex de hauteur bornée, le bloc `.recipe-picker__filters` (rendu pour les 4 onglets non-source) consommait toute la place et `.recipes-page__catalog` (`flex: 1`) s'effondrait à ~0 : la table des recettes était dans le DOM mais dans un scrollport de hauteur nulle, donc les onglets Favoris / Perso / Foyer / Publique paraissaient vides. Sous le breakpoint phone : le dialogue passe vraiment plein écran (comme tout autre `Dialog`), `.recipe-picker__filters` ne se laisse plus compresser, et le catalogue prend sa hauteur de contenu — c'est `.dialog-panel__body` qui scrolle. Closes #111 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
a87e3da025
commit
642b37ebec
2 changed files with 112 additions and 5 deletions
|
|
@ -300,3 +300,76 @@ describe("Planning day view (phone)", () => {
|
|||
cy.contains(".recipe-picker-dialog", "Choisir une recette").should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
// Issue #111 — on a phone the recipe picker is a full-height flex column;
|
||||
// a regression in the mobile full-screen-dialog CSS let its tall filter
|
||||
// block (search + ingredient/diet pickers, rendered only for the four
|
||||
// non-source tabs) consume the column and collapse the recipe table below
|
||||
// it into a zero-height scrollport. The table was in the DOM the whole
|
||||
// time, so Favoris / Perso / Foyer / Publique all just looked empty.
|
||||
describe("Recipe picker dialog (phone)", () => {
|
||||
// Full `RecipeSummaryView` shape — `RecipeTable` reads `allergens`/`diets`
|
||||
// (`.length`/`.map`) on every row, so `{ id, name }` alone crashes it.
|
||||
function summary(id: number, name: string) {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
description: null,
|
||||
picture: null,
|
||||
portions: 4,
|
||||
authorId: 1,
|
||||
visibility: "PERSONAL" as const,
|
||||
allergens: [],
|
||||
diets: [],
|
||||
isFavorite: false,
|
||||
};
|
||||
}
|
||||
const recipesByTab: Record<string, ReturnType<typeof summary>[]> = {
|
||||
favoris: [summary(1, "Ratatouille")],
|
||||
perso: [summary(2, "Omelette")],
|
||||
foyer: [summary(3, "Gratin dauphinois")],
|
||||
publique: [summary(4, "Soupe de potiron")],
|
||||
};
|
||||
const tabLabel: Record<string, string> = {
|
||||
favoris: "Favoris",
|
||||
perso: "Perso",
|
||||
foyer: "Foyer",
|
||||
publique: "Publique",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cy.setViewport("phone");
|
||||
freezeToday();
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null });
|
||||
// The dialog's own reference-data / source fetches — all empty, so the
|
||||
// filter block is only as tall as its always-present controls, which is
|
||||
// already enough to trigger the collapse this covers.
|
||||
cy.intercept("GET", "**/reference/ingredients", { statusCode: 200, body: [] });
|
||||
cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [] });
|
||||
cy.intercept("GET", "**/reference/sources", { statusCode: 200, body: [] });
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: null });
|
||||
cy.intercept("GET", "**/house/current/sources", { statusCode: 200, body: [] });
|
||||
cy.intercept("GET", /\/recipes\?/, (req) => {
|
||||
const tab = new URL(req.url).searchParams.get("tab") ?? "favoris";
|
||||
req.reply({ statusCode: 200, body: recipesByTab[tab] ?? [] });
|
||||
});
|
||||
});
|
||||
|
||||
it("shows the recipe table on every non-source tab, not a collapsed empty area", () => {
|
||||
cy.visit("/");
|
||||
cy.get(".add-recipe-btn").first().click();
|
||||
cy.get(".recipe-picker-dialog").should("be.visible");
|
||||
|
||||
for (const tab of Object.keys(recipesByTab)) {
|
||||
cy.contains(".recipe-tabs__tab", tabLabel[tab]).click();
|
||||
// `be.visible` fails on a zero-height / clipped element — the exact
|
||||
// shape of the regression.
|
||||
cy.get(".recipe-picker-dialog .recipe-table").should("be.visible");
|
||||
cy.get(".recipe-picker-dialog .recipe-table tbody tr").should("have.length.greaterThan", 0);
|
||||
cy.contains(".recipe-picker-dialog .recipe-table__name", recipesByTab[tab][0].name).should(
|
||||
"be.visible",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,14 +58,48 @@
|
|||
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
|
||||
}
|
||||
|
||||
// recipes.scss already collapses `&__catalog` to a single column under
|
||||
// 1024px, but the rule just above (2 classes) outweighs that page-scoped
|
||||
// `@media` block and would keep two ~90px columns side by side on a
|
||||
// phone. Re-collapse it here for the dialog's own narrow case — the list
|
||||
// then stacks above the detail pane, same as the full `/recettes` page.
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue