Files
livewire-material/resources/css/layout/scaffold.css
T
Andreas Reinhold / reiniandClaude Opus 5 247c596c3a Cut duplicated and speculative code across the package
An over-engineering audit of the whole tree, applied in five reviewed
batches. Behaviour stays the same except where UPGRADE.md says otherwise.

PHP: the showcase and error-page stylesheets are prebuilt into
resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import
(first occurrence kept, the order an application's build gives), instead
of Stylesheets::bundle() inlining imports on every request; only the
import walk DesignGuard needs stays. SchemeStylesheet::withProfiles()
replaces three copies of the scheme-plus-profiles loop, material:scheme
leaves spec and contrast checks to the node script that already made
them, and the error page's scheme cache, the hashed view namespace, the
translations path with no lang/ folder and DesignGuard's 1.x-name hints
are gone.

JS: the androidx shape port progress.js and both bin scripts each carried
lives once in resources/js/shapes.js (the generated SVGs are unchanged);
util.js holds ringIndex(), ms(), reopenGuard() and remember(), which
were written out several times; listeners are released through
AbortController; tooltip.js's hoverPopover() serves the rich tooltip too.

CSS: every rule for an element inside the navigation rail queries
`--md-navigation-rail-value` instead of repeating the seven collapsed
conditions under five media branches; badge, alert, progress, slider and
button read one non-inheriting colour-role table (components/color.css);
the dialog chrome, the submenu's popover chrome, the chip's state layer
and touch target, and the visually-hidden inputs use the shared rules
they copied; foundation/tokens.css is folded into foundation.css.

Views: Support\Field and Support\Link replace the error-key, bound-value
and link-attribute blocks copied into the fields and link components;
the timepicker period group, the menu filter and the showcase head are
partials; the datepicker's steppers and entry fields are loops; component
docblocks no longer restate SKILL.md.

Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces
four per-group files, DesignGuardTest and the layout-component tests use
datasets, browser tests share one ready() helper, CSS parsing lives in
ComponentStylesheet alone. docs/audits and the finding IDs citing it are
removed, as are pestphp/pest-plugin-laravel, the unused composer scripts
and check:font; the lint job runs in the feature job, which now installs
node packages so the prebuilt-stylesheet staleness test runs in CI.

Feature suite 1177 passed, Chrome browser suite 299 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:29:21 +02:00

212 lines
8.5 KiB
CSS

/*
* <x-scaffold>: M3's scaffold — bars, then rails, then panes (docs/reference/m3/foundations.md
* § Layout → Scaffold) — as a column: an optional `banner` full width, a row below it holding the
* adaptive navigation rail and the content region, the bottom navigation bar on a compact window,
* and the snackbar host. Plan step 36 (navigation group) moves in everything the view drew with
* Tailwind; `data-md-scaffold-fab` and the content region's `--md-layout-margin` are step 35's,
* unchanged below.
*
* `--material-margin` is M3's window margin, which the content region pads itself with and an
* application's own rules can read: 16px below `medium`, 24px from it
* (docs/reference/m3/foundations-supplement.md § Breakpoints), the same two spacing tokens
* layout/pane.css draws its own margin from. `--md-layout-margin: 0px` on the region tells a pane
* or canonical layout inside that the margin is already drawn.
*
* `[data-md-skip-link]` hides the way `md-visually-hidden` does at rest (text.css) — duplicated
* here rather than shared, because a class cannot be un-applied on `:focus` — and reveals itself
* fixed at the window's leading corner, clear of a safe area an application sets, in M3's pill
* shape and label-large type, with the elevation-3 shadow every temporary surface above the page
* takes.
*
* `[data-md-scaffold-row]` is flex from `medium` only (the width the rail joins the layout at
* instead of sliding over it as a modal) but always takes the remaining height, whichever it is;
* `[data-md-scaffold-content]` — the rail's sibling — is always a flex column, so the page's own
* bar, the FAB wrapper and `<main>` stack regardless of the row's own display.
*
* `[data-md-scaffold-actions]`'s column layout is `rail-collapsed:flex-col`, the one Tailwind
* variant scaffold.blade.php still used (`resources/css/tailwind.css`, removed with this rule): the
* actions row is always inside a rail, never the rail element itself, so it reads
* `--md-navigation-rail-value` with `@container style()` like any other application content does
* (`resources/css/components/navigation-rail.css`), rather than copying the rail's own conditions.
*
* `[data-md-scaffold-bar]` pins the navigation bar to the window's bottom edge below `medium`, at
* z-index 30: over the page, under the rail's scrim (40) and the snackbar (50, toast.css). While a
* bar is there, the scaffold's root publishes `--material-bottom-bar` below `medium` — the bar's
* 64px or, tall, 80px (`NavigationBarTokens.ContainerHeight` and `TallContainerHeight`, the bar's
* own dimensions rather than the spacing scale), the bottom safe area and `--material-bottom-extra`
* — which everything the bar would otherwise cover reads: the FAB (below), the snackbar host
* (toast.css), a placed toolbar (toolbar.css) and the content region's bottom padding. Without a
* bar, or from `medium`, the scaffold publishes nothing and each reader falls back to 0px. A bar that has slid
* away on scroll drops its own height from the variable (navigation-bar.css); that rule wins
* because `material.components` comes after this file's `material.layout`, where before this
* rewrite the variable was a Tailwind utility and the override had to sit outside every layer.
*
* The content region's `<main>` is matched as the content wrapper's own child, so a `<main>` an
* application nests inside the page takes none of the region's padding or clipping.
*
* Imports the stylesheets of every component this view renders: the adaptive rail and its item and
* section, the navigation bar and its item, and the snackbar host.
*
* In `material.layout`.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './visibility.css';
@import '../components/navigation-rail.css';
@import '../components/navigation-rail-item.css';
@import '../components/navigation-rail-section.css';
@import '../components/navigation-bar.css';
@import '../components/navigation-bar-item.css';
@import '../components/toast.css';
@layer material.layout {
[data-md-scaffold] {
--material-margin: var(--md-sys-measurement-space200);
display: flex;
min-block-size: 100dvh;
flex-direction: column;
background-color: var(--md-sys-color-surface);
color: var(--md-sys-color-on-surface);
@media (width >= 600px) {
--material-margin: var(--md-sys-measurement-space300);
}
}
[data-md-skip-link] {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
&:focus {
position: fixed;
z-index: 60;
inset-inline-start: var(--md-sys-measurement-space200);
top: calc(var(--material-safe-top, env(safe-area-inset-top)) + var(--md-sys-measurement-space200));
inline-size: auto;
block-size: auto;
padding: var(--md-sys-measurement-space100) var(--md-sys-measurement-space200);
margin: 0;
overflow: visible;
clip-path: none;
white-space: normal;
border-radius: var(--md-sys-shape-corner-full);
background-color: var(--md-sys-color-inverse-surface);
color: var(--md-sys-color-inverse-on-surface);
font: var(--md-sys-typescale-label-lg);
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
font-variation-settings: normal;
box-shadow: var(--md-sys-elevation-3);
outline: none;
}
}
[data-md-scaffold-banner] {
flex-shrink: 0;
}
[data-md-scaffold-row] {
flex: 1 1 0%;
@media (width >= 600px) {
display: flex;
}
}
[data-md-scaffold-content] {
display: flex;
min-inline-size: 0;
flex: 1 1 0%;
flex-direction: column;
}
[data-md-scaffold-actions] {
display: flex;
align-items: center;
gap: var(--md-sys-measurement-space50);
padding-block-start: var(--md-sys-measurement-space100);
padding-inline: 20px;
@container style(--md-navigation-rail-value: collapsed) {
flex-direction: column;
}
}
[data-md-scaffold-content] > main {
--md-layout-margin: 0px;
min-inline-size: 0;
flex: 1 1 0%;
padding-inline: var(--material-margin);
outline: none;
@media (width < 840px) {
overflow-x: clip;
}
@media (width < 600px) {
padding-block-end: var(--material-bottom-bar);
}
}
[data-md-scaffold-bar] {
position: fixed;
z-index: 30;
inset-inline: 0;
inset-block-end: 0;
@media (width >= 600px) {
display: none;
}
}
[data-md-scaffold]:has(> [data-md-scaffold-bar] [data-md-navigation-bar]:not([data-md-tall])) {
@media (width < 600px) {
--material-bottom-bar: calc(64px + var(--material-safe-bottom, env(safe-area-inset-bottom)) + var(--material-bottom-extra, 0px));
}
}
[data-md-scaffold]:has(> [data-md-scaffold-bar] [data-md-navigation-bar][data-md-tall]) {
@media (width < 600px) {
--material-bottom-bar: calc(80px + var(--material-safe-bottom, env(safe-area-inset-bottom)) + var(--material-bottom-extra, 0px));
}
}
[data-md-scaffold-fab] {
--md-scaffold-fab-margin: var(--md-sys-measurement-space200);
position: fixed;
z-index: 30;
inset-inline-end: max(
var(--md-scaffold-fab-margin),
var(--material-safe-right, env(safe-area-inset-right))
);
inset-block-end: calc(
max(var(--material-bottom-bar, 0px), var(--material-safe-bottom, env(safe-area-inset-bottom)))
+ var(--md-scaffold-fab-margin)
+ var(--material-snackbar-height, 0px)
+ min(var(--material-snackbar-height, 0px), var(--md-sys-measurement-space200))
);
transition: inset-block-end var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-spatial-default);
@media (width >= 600px) {
--md-scaffold-fab-margin: var(--md-sys-measurement-space300);
}
}
[data-md-scaffold-fab]:is([dir='rtl'], [dir='rtl'] *) {
inset-inline-end: max(
var(--md-scaffold-fab-margin),
var(--material-safe-left, env(safe-area-inset-left))
);
}
}