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>
128 lines
6.1 KiB
PHP
128 lines
6.1 KiB
PHP
{{-- The scaffold: M3's structure of bars, rails and panes around the page, and the FAB, with
|
|
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.
|
|
|
|
`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.
|
|
|
|
`--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 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"` (SKILL.md) sets a custom property on. --}}
|
|
|
|
@props([
|
|
'destinations' => [],
|
|
'label' => null,
|
|
'railWidth' => '16rem',
|
|
'tallBar' => false,
|
|
'hideBarOnScroll' => false,
|
|
'hideRailWhenCollapsed' => false,
|
|
])
|
|
|
|
@php
|
|
$label ??= __('Main');
|
|
$current = \Livewire\Livewire::isLivewireRequest() ? \Livewire\Livewire::originalUrl() : request()->url();
|
|
|
|
$items = collect($destinations)
|
|
->filter(fn ($item): bool => is_array($item) && filled($item['title'] ?? null))
|
|
->map(fn (array $item): array => [
|
|
'title' => (string) $item['title'],
|
|
'icon' => $item['icon'] ?? null,
|
|
'url' => $item['url'] ?? null,
|
|
'active' => (bool) ($item['active'] ?? (filled($item['url'] ?? null) && rtrim(url($item['url']), '/') === rtrim($current, '/'))),
|
|
'badge' => $item['badge'] ?? null,
|
|
'badgeLabel' => filled($item['badgeLabel'] ?? null) ? (string) $item['badgeLabel'] : null,
|
|
'section' => filled($item['section'] ?? null) ? (string) $item['section'] : null,
|
|
'bar' => ($item['bar'] ?? true) !== false,
|
|
'navigate' => ($item['navigate'] ?? true) !== false,
|
|
])
|
|
->values();
|
|
|
|
$barItems = $items->where('bar', true)->values();
|
|
|
|
// Consecutive destinations under the same heading form one group, in the order given.
|
|
$groups = $items->chunkWhile(fn (array $item, int $key, $chunk): bool => $item['section'] === $chunk->last()['section']);
|
|
@endphp
|
|
|
|
<div data-md-scaffold {{ $attributes->only(['class', 'style']) }}>
|
|
<a href="#content" data-md-skip-link>{{ __('Skip to content') }}</a>
|
|
|
|
@isset($banner)
|
|
<div data-md-scaffold-banner>{{ $banner }}</div>
|
|
@endisset
|
|
|
|
<div data-md-scaffold-row>
|
|
<x-livewire-material::navigation-rail mode="adaptive" :label="$label" :width="$railWidth" :hide-when-collapsed="$hideRailWhenCollapsed">
|
|
@isset($brand)
|
|
<x-slot:brand>{{ $brand }}</x-slot:brand>
|
|
@endisset
|
|
|
|
@isset($railHeader)
|
|
<x-slot:header>{{ $railHeader }}</x-slot:header>
|
|
@endisset
|
|
|
|
@foreach ($groups as $group)
|
|
@if ($group->first()['section'] !== null)
|
|
<x-livewire-material::navigation-rail-section :label="$group->first()['section']">
|
|
@foreach ($group as $item)
|
|
<x-livewire-material::navigation-rail-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :badge-label="$item['badgeLabel']" :no-wire-navigate="! $item['navigate']" />
|
|
@endforeach
|
|
</x-livewire-material::navigation-rail-section>
|
|
@else
|
|
@foreach ($group as $item)
|
|
<x-livewire-material::navigation-rail-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :badge-label="$item['badgeLabel']" :no-wire-navigate="! $item['navigate']" />
|
|
@endforeach
|
|
@endif
|
|
@endforeach
|
|
|
|
@if (isset($railFooter) || isset($actions))
|
|
<x-slot:footer>
|
|
{{ $railFooter ?? '' }}
|
|
|
|
@isset($actions)
|
|
<div data-md-scaffold-actions>
|
|
{{ $actions }}
|
|
</div>
|
|
@endisset
|
|
</x-slot:footer>
|
|
@endif
|
|
</x-livewire-material::navigation-rail>
|
|
|
|
<div data-md-scaffold-content>
|
|
{{ $top ?? '' }}
|
|
|
|
@isset($fab)
|
|
<div data-md-scaffold-fab>{{ $fab }}</div>
|
|
@endisset
|
|
|
|
<main id="content" tabindex="-1" wire:transition.navigate>
|
|
{{ $slot }}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
@if ($barItems->isNotEmpty())
|
|
<div data-md-scaffold-bar>
|
|
<x-livewire-material::navigation-bar :label="$label" :tall="$tallBar" :hide-on-scroll="$hideBarOnScroll">
|
|
@foreach ($barItems as $item)
|
|
<x-livewire-material::navigation-bar-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :badge-label="$item['badgeLabel']" :no-wire-navigate="! $item['navigate']" />
|
|
@endforeach
|
|
</x-livewire-material::navigation-bar>
|
|
</div>
|
|
@endif
|
|
|
|
<x-livewire-material::toast />
|
|
</div>
|