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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
471d927e64
commit
247c596c3a
@@ -1,108 +1,26 @@
|
||||
{{-- The scaffold: M3's structure of bars, rails and panes around the page, and the FAB, with
|
||||
navigation that changes shape with the window. M3 and Compose call it the scaffold
|
||||
(docs/reference/m3/foundations.md § Layout → Scaffold); before 2.0.0 it was `<x-app-shell>`.
|
||||
navigation that changes shape with the window (docs/reference/m3/foundations.md § Layout →
|
||||
Scaffold). Before 2.0.0 it was `<x-app-shell>`. Props, slots and the breakpoint table are in
|
||||
SKILL.md — this is the WHY behind the wiring, not the API.
|
||||
|
||||
<x-scaffold :destinations="[
|
||||
['title' => 'Shares', 'icon' => 'folder_shared', 'url' => route('shares'), 'active' => request()->routeIs('shares*'), 'badge' => 3],
|
||||
['title' => 'Upload', 'icon' => 'upload', 'url' => route('upload')],
|
||||
['title' => 'Users', 'icon' => 'group', 'url' => route('users'), 'section' => 'Admin', 'bar' => false],
|
||||
]">
|
||||
<x-slot:brand><a href="/" wire:navigate class="md-type-title-lg">SealShare</a></x-slot:brand>
|
||||
<x-slot:top>…the page's app bar…</x-slot:top>
|
||||
<x-slot:fab><x-fab icon="add" tooltip="New share" /></x-slot:fab>
|
||||
`actions`' collapsed-column layout queries the rail's published `--md-navigation-rail-value`
|
||||
rather than a breakpoint of its own, so `resources/css/layout/scaffold.css` stays in step with
|
||||
the rail's conditions without repeating them.
|
||||
|
||||
…the page…
|
||||
</x-scaffold>
|
||||
`--md-layout-margin: 0px` on the content region (resources/css/layout/scaffold.css) is read by
|
||||
layout/pane.css: a pane or canonical layout inside the scaffold draws no margin of its own
|
||||
because it can see it is already in a margined region.
|
||||
|
||||
The navigation is M3's per breakpoint (docs/reference/m3/foundations.md § Layout and
|
||||
foundations-supplement.md § Breakpoints), and only those four numbers:
|
||||
|
||||
- **Compact**, below `medium` (600px): a navigation bar with the destinations marked `bar`,
|
||||
pinned to the bottom. Everything else is in the modal rail, which slides in when something
|
||||
calls `$store.rail.show()` — put a menu button in the app bar for it, hidden from `medium`:
|
||||
`<x-stack as="span" hide-from="medium"><x-button icon="menu" tooltip="Open navigation" x-on:click="$store.rail.show()" /></x-stack>`.
|
||||
- **Medium** (600–839): the collapsed rail in the layout, 96px, and no bar; its menu button
|
||||
opens it expanded over a scrim, since 256px beside the page would leave the page too little.
|
||||
- **Expanded** (840–1199): a standard rail — in the layout, nothing covered — collapsed until
|
||||
the menu button expands it in place.
|
||||
- **Large and extra-large** (from 1200): the same standard rail, expanded to begin with
|
||||
(`rail.default`), which is what M3 prefers once there is room.
|
||||
|
||||
From `expanded` the choice the menu button makes is remembered and applied before the first
|
||||
paint (`$store.rail`, <x-theme-script>), so the rail never paints one width and snaps to the
|
||||
other. A visitor who has chosen keeps that choice in both bands; one who never has gets the
|
||||
breakpoint's own default.
|
||||
|
||||
`--material-margin` is M3's window margin — 16px on a compact window, 24px from `medium` — and
|
||||
the content region is padded with it, so a page inside the scaffold writes no gutters of its own.
|
||||
Something meant to reach the window's edges opts out with `margin-inline: calc(var(--material-margin) * -1)`.
|
||||
|
||||
Panes are `<x-pane>`, and two side by side are M3's canonical layouts from `expanded`,
|
||||
`<x-list-detail>` and `<x-supporting-pane>`; inside the content region they draw no margin of
|
||||
their own (resources/css/layout/scaffold.css sets `--md-layout-margin` there).
|
||||
|
||||
`destinations` is a list of arrays: `title`, `icon` (a Material Symbol), `url`, and optionally
|
||||
`active` (by default: the URL is the page's; during a Livewire update request, the page the
|
||||
component was rendered on rather than the update endpoint), `badge` (`true` for a dot, or a count), `badgeLabel` (what a screen reader hears for
|
||||
the badge instead: "3 unread"),
|
||||
`section` (a heading the destination is grouped under in the rail; only an expanded rail shows
|
||||
it), `bar` (`false` keeps it out of the bottom bar; M3 wants three to five there) and
|
||||
`navigate` (`false` for a full page load instead of `wire:navigate`).
|
||||
|
||||
Slots, each rendered once: `banner` (a bar across the whole window, above the rail and the
|
||||
page — M3's scaffold is bars, then rails, then panes), `brand` (beside the rail's menu button
|
||||
while it is expanded), `rail-header` (under it: one `<x-fab label="…" icon="…">`, which the
|
||||
rail morphs between a FAB and an extended FAB as it opens), `rail-footer` (at the foot of the
|
||||
rail: footer destinations, an account), `actions`
|
||||
(a row of icon buttons at the very foot, stacked in a column once the rail collapses to its
|
||||
narrow width — the same selectors `navigation-rail.css` matches "collapsed" with, since
|
||||
`resources/css/layout/scaffold.css` keys off the rail's own hooks rather than a breakpoint of
|
||||
its own: a theme toggle, sign out), `top` (the page's own bar, above the page and *beside* the
|
||||
rail), `fab` (the
|
||||
page's floating action button, see below) and the page itself. The rail is one element at every width, so what is in it is also in the modal rail a
|
||||
phone opens. `label` names both navigation landmarks ("Main"); `rail-width` is the expanded
|
||||
rail's width; `tall-bar` picks M3's 80px navigation bar over the 64px one, and the bottom
|
||||
offset every pinned thing reads follows it; `hide-bar-on-scroll` lets the bar leave the window
|
||||
while the page scrolls down, and `--material-bottom-bar` goes down and comes back with it;
|
||||
`hide-rail-when-collapsed` is M3's immersive configuration — from `expanded` the rail leaves
|
||||
the layout when the menu button collapses it, rather than narrowing to 96px, so the page has
|
||||
the whole window. The only way back is `$store.rail.show()`, so put a menu button in the app
|
||||
bar at every width, not just below `medium`.
|
||||
|
||||
`banner` or `top` is a decision about what the bar belongs to: an application-wide bar — one
|
||||
search, one account menu, the same on every page — spans the window and the rail starts under
|
||||
it; a bar that titles the page belongs to the page, beside the rail. Put an app bar in one or
|
||||
the other, never both. A banner that pins itself to the top of the window says how tall it is
|
||||
— `style="--material-banner: 4rem"` on `<x-scaffold>` — so the rail sticks under it instead
|
||||
of behind it.
|
||||
|
||||
The page is `<main id="content">` with `wire:transition.navigate`, behind a skip link that is
|
||||
the first thing a keyboard reaches. The snackbar host (`<x-toast />`) is part of the scaffold;
|
||||
on a compact window it, and a `fab` button, sit above the bottom bar through `--material-bottom-bar`:
|
||||
the bar's 64px, the bottom safe area (`--material-safe-bottom`, else the device's inset) and
|
||||
`--material-bottom-extra` (0px unless the application docks something, an offline banner, on
|
||||
top of the bar).
|
||||
|
||||
`fab` places an `<x-fab>` as Compose's Scaffold places its floating action button: fixed at the
|
||||
bottom-end corner, 16px from the window's edges on a compact window and 24px from `medium`
|
||||
(M3's margins, foundations-supplement.md § Breakpoints; the FAB's own adaptive placement is
|
||||
the lower trailing corner, components-actions-communication-containment.md § FAB), above the
|
||||
navigation bar and the bottom safe area, and lifted over a snackbar while one shows —
|
||||
M3: a snackbar appears above a FAB, never in front of or behind one. Compose raises the
|
||||
snackbar over the FAB instead; here the snackbar host is one fixed element every page shares,
|
||||
so the FAB is what moves (resources/css/layout/scaffold.css). Use it or `rail-header`'s FAB,
|
||||
not both: M3 says not to show more than one FAB on a screen. It comes after the page's bar and
|
||||
before the page in focus order, where M3 puts a FAB (§ FAB → Accessibility: "the FAB should be
|
||||
prioritized in the page's focus order").
|
||||
|
||||
`overflow-x: clip` on the content region, below `expanded` only, is the backstop under every
|
||||
page: `overflow-x: hidden` would force `overflow-y` to `auto`, turn the region into a scroll
|
||||
container and break every `position: sticky` inside it (an app bar, a list-detail pane). A wide
|
||||
window never clips what overhangs on purpose.
|
||||
The snackbar host (`<x-toast />`) is part of the scaffold, not a separate element an app adds:
|
||||
Compose raises the snackbar over its FAB, but here the host is one fixed element every page
|
||||
shares, so the FAB (`fab` slot) is what moves above it instead (resources/css/layout/scaffold.css);
|
||||
both clear the bottom bar through `--material-bottom-bar` on a compact window (the bar's 64px,
|
||||
the safe-area inset and `--material-bottom-extra`, 0px unless the application docks something
|
||||
above the bar).
|
||||
|
||||
Nothing application-specific belongs in here: an app's destinations and chrome come in through
|
||||
the props and slots. The caller's `class` and `style` land on the root, `data-md-scaffold`
|
||||
itself — the element `style="--material-banner: 4rem"` above sets a custom property on. --}}
|
||||
itself — the element `style="--material-banner: 4rem"` (SKILL.md) sets a custom property on. --}}
|
||||
|
||||
@props([
|
||||
'destinations' => [],
|
||||
|
||||
Reference in New Issue
Block a user