Merge pull request 'feat(web): passe responsive téléphone (portage mobile #39, phase 3)' (#55) from feat/mobile-responsive-pass into main
This commit is contained in:
commit
5ad9d644bd
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", () => {
|
describe("Color theme — light/dark tokens actually reach the rendered chrome", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
interceptAuth();
|
interceptAuth();
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,16 @@
|
||||||
&::backdrop {
|
&::backdrop {
|
||||||
background: rgba(0, 0, 0, 0.45);
|
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 {
|
.dialog-panel__header {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,17 @@
|
||||||
.recipes-page__catalog {
|
.recipes-page__catalog {
|
||||||
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
|
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 {
|
.recipe-picker__filters {
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,15 @@
|
||||||
flex-direction: column;
|
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 {
|
.app-sidebar {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -264,3 +264,29 @@ table.planning-grid {
|
||||||
background: color-mix(in srgb, var(--color-primary) 8%, transparent);
|
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