Add the grid layout component

Plan step 35: <x-grid :columns gap min-item>. The column count per
breakpoint is written as five inline custom properties, each filled from
the nearest smaller breakpoint, so a nested grid never inherits its
parent's; min-item fills a row by the room the grid has, and with columns
the counts become a ceiling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 14:50:47 +02:00
co-authored by Claude Opus 5
parent 8de053f15c
commit da7a05a619
6 changed files with 241 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
/*
* <x-grid>: children in columns, in a pane, with a column count per breakpoint.
*
* The count at each of M3's breakpoints arrives as five inline custom properties,
* `--md-columns-compact` … `--md-columns-extra-large`, every one written on every grid, each filled
* from the nearest smaller breakpoint (src/Support/Layout.php). One rule per breakpoint picks its
* own into `--md-columns` — compact below 600px, then medium 600, expanded 840, large 1200 and
* extra-large 1600, in px (docs/reference/m3/foundations.md § Layout → Breakpoints) — so a grid
* nested in another reads its own five and never its parent's. M3 publishes no column table for
* the web ("no explicit numeric column-count or gutter table is given on the current site", same
* section), only that columns grow with the breakpoint; the counts are the caller's.
*
* `data-md-min-item` (`--md-min-item`, a length) fills the row with as many columns as fit at that
* minimum width instead, the room a pane actually has deciding rather than the window. With both,
* the count is a ceiling: never more columns than the breakpoint's count, never narrower than the
* minimum. The column width the ceiling implies — the grid's width, less its gaps, over the count —
* needs the gap as a length, which is why a gap is `--md-gap` (spacing.css) and not `gap` alone.
* Every column is `minmax(0, 1fr)` or at most the grid's width, so a long word never pushes a
* column past the pane. Children keep their source order, which is the reading order.
*
* In `material.layout`; `hide-below`/`hide-from` come from visibility.css.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './spacing.css';
@import './visibility.css';
@layer material.layout {
:where([data-md-grid]) {
--md-gap: 0px;
}
[data-md-grid] {
--md-columns: var(--md-columns-compact, 1);
display: grid;
grid-template-columns: repeat(var(--md-columns), minmax(0, 1fr));
gap: var(--md-gap);
@media (width >= 600px) {
--md-columns: var(--md-columns-medium, 1);
}
@media (width >= 840px) {
--md-columns: var(--md-columns-expanded, 1);
}
@media (width >= 1200px) {
--md-columns: var(--md-columns-large, 1);
}
@media (width >= 1600px) {
--md-columns: var(--md-columns-extra-large, 1);
}
}
[data-md-grid][data-md-min-item] {
grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--md-min-item)), 1fr));
}
[data-md-grid][data-md-min-item][data-md-columns] {
grid-template-columns: repeat(
auto-fill,
minmax(min(100%, max(var(--md-min-item), (100% - (var(--md-columns) - 1) * var(--md-gap)) / var(--md-columns))), 1fr)
);
}
}