feat(web): passe responsive téléphone (portage mobile #39, phase 3) #55

Merged
kyuno merged 1 commit from feat/mobile-responsive-pass into main 2026-09-06 03:56:01 +02:00
5 changed files with 134 additions and 0 deletions
Showing only changes of commit a8d86c7099 - Show all commits

View file

@ -213,6 +213,84 @@ describe("Responsive breakpoint — sidebar becomes a horizontal top bar under 6
});
});
describe("Responsive breakpoint — page content under 640px (issue #39)", () => {
const PHONE: [number, number] = [375, 812];
beforeEach(() => {
interceptAuth();
});
it("tightens .app-content padding from 32px to 16px on a phone", () => {
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null });
cy.viewport(1600, 900);
cy.visit("/");
cy.get(".app-content").should(($el) => {
const s = getComputedStyle($el[0]);
// Desktop: var(--space-xl) === 2rem === 32px, all four sides.
expect(s.paddingTop).to.equal("32px");
expect(s.paddingLeft).to.equal("32px");
});
cy.viewport(...PHONE);
cy.visit("/");
cy.get(".app-content").should(($el) => {
const s = getComputedStyle($el[0]);
// Phone: var(--space-md) === 1rem === 16px.
expect(s.paddingTop).to.equal("16px");
expect(s.paddingLeft).to.equal("16px");
});
});
it("keeps the 7-day planning grid on its own horizontal scroll rather than overflowing the page", () => {
cy.intercept("GET", /\/planning\?/, {
statusCode: 200,
body: {
id: 1,
startDate: "2026-08-17T00:00:00.000Z",
finishDate: "2026-08-23T00:00:00.000Z",
items: [
{
id: 1,
weekDay: "lundi",
meal: "dejeuner",
portions: 4,
portionsRequested: 4,
discardedPortions: 0,
sourceItem: null,
recipe: { id: 1, name: "Curry de lentilles" },
},
],
},
});
cy.viewport(...PHONE);
cy.visit("/");
// The grid wrapper is the scroll container, and its content is genuinely
// wider than the phone viewport (7 columns never crushed).
cy.get(".planning-grid-wrapper").should(($el) => {
expect(getComputedStyle($el[0]).overflowX).to.be.oneOf(["auto", "scroll"]);
expect($el[0].scrollWidth).to.be.greaterThan($el[0].clientWidth);
});
// ...and that overflow stays *inside* the wrapper — the document itself
// never scrolls sideways (global rule against a horizontally-scrolling
// body).
cy.document()
.its("documentElement.scrollWidth")
.should("be.lte", 375 + 1);
// The per-recipe remove "✕" is hover-gated on desktop; on touch it must
// be permanently visible or a planned recipe can't be removed at all.
cy.get(".recipe-chip__remove")
.first()
.should(($btn) => {
expect(Number(getComputedStyle($btn[0]).opacity)).to.be.greaterThan(0);
});
});
});
describe("Color theme — light/dark tokens actually reach the rendered chrome", () => {
beforeEach(() => {
interceptAuth();

View file

@ -23,6 +23,16 @@
&::backdrop {
background: rgba(0, 0, 0, 0.45);
}
// On a phone the `48rem` cap is irrelevant (the viewport is narrower),
// but the `var(--space-2xl)` top/bottom breathing room is a large slice
// of a short screen — tighten it so a form-heavy dialog isn't
// needlessly cramped, and keep a small side gutter so the panel never
// sits edge to edge.
@media (max-width: 640px) {
max-width: calc(100vw - var(--space-md));
max-height: calc(100vh - var(--space-md));
}
}
.dialog-panel__header {

View file

@ -55,6 +55,17 @@
.recipes-page__catalog {
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.
@media (max-width: 640px) {
.recipes-page__catalog {
grid-template-columns: 1fr;
}
}
}
.recipe-picker__filters {

View file

@ -365,6 +365,15 @@
flex-direction: column;
}
// The desktop `var(--space-xl)` (32px) all around eats a big share of a
// ~375px screen — every page then has ~64px less usable width than the
// viewport already gives it. Drop to `--space-md` (16px) on phones; the
// grid/table pages that still overflow fall back to their own horizontal
// scroll (same philosophy as the sidebar below).
.app-content {
padding: var(--space-md);
}
.app-sidebar {
flex-direction: row;
align-items: center;

View file

@ -264,3 +264,29 @@ table.planning-grid {
background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}
}
// --- Phone (issue #39, responsive pass) -----------------------------------
// The grid itself already degrades gracefully on a narrow screen — the
// wrapper's `overflow: auto` + the table's `min-width: 62rem` mean it just
// scrolls horizontally rather than crushing seven day-columns into
// illegibility (same philosophy as the sidebar's own fallback). What does
// NOT survive the switch to touch are the two hover-gated affordances
// below.
@media (max-width: 640px) {
// `.recipe-chip__remove` sits at `opacity: 0` until its chip is hovered
// — invisible, and untappable, on a touch screen where nothing is ever
// "hovered". Pin it visible so a planned recipe can actually be removed
// on a phone. Also give it a real tap target.
.recipe-chip__remove {
opacity: 0.7;
width: 1.5rem;
height: 1.5rem;
font-size: 0.8rem;
}
// A slightly taller "+" so adding to a slot is a comfortable tap, not a
// 5px sliver.
.add-recipe-btn {
padding: var(--space-sm);
}
}