fix(planning): la grille remplit l'écran, une case ne grandit plus (#110)
All checks were successful
CI / lint (push) Successful in 2m23s
CI / build (push) Successful in 5m41s
CI / intent-service-test (push) Successful in 7m13s
CI / e2e (push) Successful in 10m50s
CI / test (push) Successful in 29m0s
CI / build-android (push) Has been skipped

`table.planning-grid` a `height: 100%` mais des hauteurs de ligne pilotées
par le contenu : un créneau avec plusieurs recettes étirait son `<td>`,
donc toute la ligne (les autres cases gagnant la même hauteur morte sous
`vertical-align: top`), puis la table débordait de `.planning-grid-wrapper`
en scroll vertical.

- `tbody tr { height: 1px }` : les cinq lignes se partagent la hauteur du
  wrapper à parts égales (une hauteur de ligne est un minimum que le
  navigateur étire pour absorber la hauteur restante de la table), au lieu
  d'être aussi hautes que la colonne la plus chargée.
- `.meal-cell__recipes` : `max-height: 8.5rem; overflow-y: auto` — au-delà
  d'une poignée de recettes la liste scrolle dans la case. Cap en `rem` et
  pas en `%` : les hauteurs en pourcentage sur un `<td>` ne se résolvent
  pas de façon fiable selon le moteur. `.add-recipe-btn` (frère flex hors
  de cette boîte) reste épinglé juste en dessous.
- `min-width: 62rem` laissé tel quel : les 7 colonnes ont besoin d'un
  plancher pour rester lisibles, et le scroll horizontal dessous est
  couvert par `layout.cy.ts`. Le breakpoint phone (vue `PlanningDayView`)
  n'est pas concerné.

Closes #110

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Nicolas 2026-09-08 01:33:37 +02:00
parent b52586e076
commit c4c2c2ae39
2 changed files with 75 additions and 0 deletions

View file

@ -182,6 +182,49 @@ describe("Planning grid", () => {
cy.contains("Semaine du 10 au 16 août 2026").should("be.visible"); cy.contains("Semaine du 10 au 16 août 2026").should("be.visible");
}); });
// Issue #110 — a slot with many recipes used to stretch its `<td>`, and
// with it the whole row and the table, past `.planning-grid-wrapper`
// into a vertical scroll (the other cells of that row just gaining dead
// space). The chip list now scrolls inside its own cell instead.
it("keeps a heavy slot's recipes scrolling inside the cell, not stretching the grid", () => {
cy.intercept("GET", /\/planning\?/, {
statusCode: 200,
body: {
id: 1,
startDate: "2026-08-17T00:00:00.000Z",
finishDate: "2026-08-23T00:00:00.000Z",
items: Array.from({ length: 14 }, (_, i) => ({
id: i + 1,
weekDay: "lundi",
meal: "dejeuner",
portions: 2,
recipe: { id: i + 1, name: `Recette ${i + 1}` },
})),
},
});
cy.visit("/");
cy.get(".recipe-chip").should("have.length", 14);
// The busy cell's own recipe list is now the scroller: it's capped and
// scrolls its own overflow instead of stretching the `<td>` (and with
// it the whole row, then the table past `.planning-grid-wrapper` into a
// tall vertical scroll).
cy.get(".meal-cell__recipes").should(($lists) => {
const list = [...$lists].find(
(el) => el.querySelectorAll(".recipe-chip").length === 14,
) as HTMLElement;
expect(list, "the 14-recipe cell's list").to.exist;
expect(getComputedStyle(list).overflowY).to.be.oneOf(["auto", "scroll"]);
expect(list.scrollHeight).to.be.greaterThan(list.clientHeight);
expect(list.clientHeight).to.be.lessThan(200);
});
cy.get(".planning-grid-wrapper").should(($w) => {
expect($w[0].scrollHeight).to.be.lessThan($w[0].clientHeight * 1.25);
});
});
it("jumps to an arbitrary week by picking a day in the calendar popover", () => { it("jumps to an arbitrary week by picking a day in the calendar popover", () => {
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null }).as("getPlanning"); cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null }).as("getPlanning");

View file

@ -84,6 +84,18 @@ table.planning-grid {
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed; table-layout: fixed;
// Share the wrapper's height evenly across the five meal rows instead of
// letting each row size to its own content. A `height` on a table row is
// a minimum the browser then stretches to absorb the table's leftover
// height; an equal tiny value on every row spreads that leftover evenly,
// so the grid genuinely fills the screen rather than being only as tall
// as its busiest column (with dead space under the short ones). The
// per-cell scroll below is what then keeps a heavy slot from pushing its
// row past that share.
tbody tr {
height: 1px;
}
th, th,
td { td {
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
@ -178,12 +190,32 @@ table.planning-grid {
flex-direction: column; flex-direction: column;
gap: var(--space-xs); gap: var(--space-xs);
height: 100%; height: 100%;
// The content must not be what defines the row's height: a slot with
// several recipes would otherwise stretch its `<td>`, and with it the
// whole row (every other day's cell gaining the same dead height under
// `vertical-align: top`), then the table past the wrapper into a
// vertical scroll. `min-height: 0` lets this flex column shrink to its
// share of the row so `.meal-cell__recipes` below can take over the
// scrolling.
min-height: 0;
} }
.meal-cell__recipes { .meal-cell__recipes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-xs); gap: var(--space-xs);
// A slot only ever holds a handful of recipes in real use; past that the
// list scrolls inside the cell instead of stretching the `<td>` (and
// with it the whole row, then the table past `.planning-grid-wrapper`
// into a vertical scroll — issue #110). A `rem` cap rather than a `%` of
// the cell: percentage heights against a `<td>` don't resolve
// consistently across engines. `flex: 1` still lets the list use the
// row's spare height up to that cap. `.add-recipe-btn` is a flex sibling
// (outside this box), so it stays pinned just below, always reachable.
flex: 1;
min-height: 0;
max-height: 8.5rem;
overflow-y: auto;
} }
.recipe-chip { .recipe-chip {