[UI mobile] Session de cuisine : passe mobile #98

Merged
kyuno merged 1 commit from feat/mobile-cooking-session into main 2026-09-07 17:39:42 +02:00
2 changed files with 143 additions and 0 deletions
Showing only changes of commit 2b5ae6722a - Show all commits

View file

@ -3,6 +3,8 @@
// `GET /cooking-session` behavior (including the optimizer) against a real // `GET /cooking-session` behavior (including the optimizer) against a real
// database. // database.
import { PHONE_VIEWPORT } from "../support/viewports";
const authenticatedProfile = { const authenticatedProfile = {
id: 1, id: 1,
firstName: "Alice", firstName: "Alice",
@ -421,3 +423,72 @@ describe("Cooking session page", () => {
cy.contains("Impossible de charger").should("be.visible"); cy.contains("Impossible de charger").should("be.visible");
}); });
}); });
// Ticket #89 — this page's first dedicated responsive pass (it previously
// had no `@media` rule at all, see cooking-session-page.scss's own header
// comment). No layout is restructured on phone (the flex-column shell and
// the two horizontally-scrolling rows already degrade safely) — this covers
// the two things that actually needed fixing: comfortable touch targets on
// the screen's own controls, and a real flex overflow bug in the Recettes
// tab (a long recipe name had no upper bound and could crowd its portions
// badge/chevron out of the row).
describe("Cooking session page (phone)", () => {
beforeEach(() => {
cy.setViewport("phone");
cy.clock(TODAY, ["Date"]);
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
});
it("gives the task cards and stepper nodes a taller, more tappable padding than on desktop", () => {
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() });
cy.visit("/cuisiner?date=2026-08-17");
cy.get(".cooking-task")
.first()
.should(($el) => {
// --space-md (16px) on phone vs. the shared checkbox-label rule's
// own --space-xs (4px) app-wide default — see cooking-session-page
// .scss's phone block.
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
});
cy.get(".cooking-stepper__node")
.first()
.should(($el) => {
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
});
// The document itself never scrolls sideways, same global rule every
// other page is held to.
cy.document()
.its("documentElement.scrollWidth")
.should("be.lte", PHONE_VIEWPORT.width + 1);
});
it("truncates a long recipe name in the Recettes tab instead of pushing its portions badge out of view", () => {
const longName =
"Un très long nom de recette qui ne devrait surtout pas pousser le badge de portions hors de l'écran";
cy.intercept("GET", /\/cooking-session\?/, {
statusCode: 200,
body: {
startDate: "2026-08-17T00:00:00.000Z",
finishDate: "2026-08-23T00:00:00.000Z",
recipes: [{ recipeId: 1, name: longName, portions: 4 }],
phases: [],
},
});
cy.visit("/cuisiner?date=2026-08-17");
cy.contains(".cooking-session-page__tab", "Recettes").click();
cy.get(".cooking-recipes__name").should(($el) => {
const s = getComputedStyle($el[0]);
expect(s.textOverflow).to.equal("ellipsis");
expect(s.whiteSpace).to.equal("nowrap");
// The name's own box is clipped narrower than its full text — proof
// the truncation is actually engaged, not just declared.
expect($el[0].scrollWidth).to.be.greaterThan($el[0].clientWidth);
});
// ...and the badge survives, still on screen, not clipped away by the
// card's own `overflow: hidden`.
cy.contains(".cooking-recipes__portions", "4 portions").should("be.visible");
});
});

View file

@ -13,6 +13,8 @@
// on the theme. // on the theme.
// ============================================================================= // =============================================================================
@use "../../styles/breakpoints" as bp;
.cooking-session-page { .cooking-session-page {
height: 100%; height: 100%;
display: flex; display: flex;
@ -134,12 +136,24 @@
cursor: pointer; cursor: pointer;
} }
// `flex: 1; min-width: 0` + truncation — without it, a long recipe name
// has no upper bound in this flex row and can crowd the portions badge
// and chevron off to the side (or past the card's own `overflow: hidden`
// on `&__item`, silently clipping them) instead of the name itself
// truncating. Same fix as `.recipe-chip__name` (planning-page.scss) for
// the identical shape of bug.
&__name { &__name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600; font-weight: 600;
color: var(--color-text); color: var(--color-text);
} }
&__portions { &__portions {
flex-shrink: 0;
padding: 0.1rem var(--space-sm); padding: 0.1rem var(--space-sm);
border-radius: var(--radius-pill); border-radius: var(--radius-pill);
background: var(--color-surface-alt); background: var(--color-surface-alt);
@ -486,3 +500,61 @@
color: var(--color-text-muted); color: var(--color-text-muted);
} }
} }
// --- Phone (issue #89, first responsive pass) -------------------------------
// Nothing here reorganizes the layout — the flex-column shell and the two
// horizontally-scrolling rows (`&__tabs`, `.cooking-stepper`, both the same
// generic `overflow-x: auto` pattern used throughout the app) already
// degrade safely on a narrow screen, and every text block wraps within its
// own flex/block container (no fixed widths). What was actually missing:
// comfortable touch targets on this screen's own controls — the task cards
// especially, since ticking one off is THE primary, repeated, one-handed
// action of the whole "Plan" tab. Same audit `planning-page.scss` got in
// #88, applied here to this page's own elements.
@include bp.phone {
// The task card is a shared "selectable card" checkbox label
// (global.scss's `label:has(> input[type="checkbox"])`, padding
// `--space-xs --space-sm` app-wide — fine with a mouse, too tight to tap
// reliably mid-cooking). `--space-md` all around clears Android's 48dp
// minimum touch target (this app is Android-only, see the root README)
// even in the worst case — a one-line headline with no utensils and no
// "Mutualisé" badge (~25px of text): 25 + 2×16 = 57px. Bumped here, not
// in the global rule: this page's task cards are the one place site-wide
// where quick, repeated tapping actually matters. Scoped under
// `.cooking-phase__tasks` (not a bare `.cooking-task` selector) for the
// same specificity reason as `&__tasks .cooking-task--done` above: a
// plain single class loses to `label:has(> input[type="checkbox"])`'s
// own specificity (a `:has()` argument counts its inner element +
// attribute), so it silently never applied without this.
.cooking-phase__tasks .cooking-task {
padding: var(--space-md);
}
// Same reasoning for the stepper's own nodes: the enlarged 1.75rem
// (28px) marker below is this node's tallest element, so `--space-md`
// padding puts its total height at 28 + 2×16 = 60px, comfortably past
// 48dp rather than landing exactly on it.
.cooking-stepper__node {
padding: var(--space-md);
}
.cooking-stepper__marker {
width: 1.75rem;
height: 1.75rem;
}
// Tab bar, Recettes summary row, Prev/Next — same treatment, so every
// tappable control on this page ends up consistently comfortable rather
// than fixing only the one most obviously broken.
.cooking-session-page__tab {
padding: var(--space-md);
}
.cooking-recipes__row {
padding: var(--space-md);
}
.cooking-plan__nav-btn {
padding: var(--space-md) var(--space-lg);
}
}