feat(web): passe responsive téléphone (portage mobile #39, phase 3) #55
5 changed files with 134 additions and 0 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue