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>
106 lines
5.1 KiB
PHP
106 lines
5.1 KiB
PHP
{{-- A choice of colour profile: one swatch per profile `material:scheme` generated from
|
|
`livewire-material.profiles`, each its name and its primary, secondary and tertiary colour.
|
|
|
|
<x-scheme-picker label="Colour profile" wire:model="colorProfile" hint="Applies to every page after saving" />
|
|
|
|
Native radios under the swatches, so `wire:model` and `x-model` bind as on any input and the
|
|
arrow keys move the choice. Choosing one shows it on the page at once
|
|
(`$store.theme.previewScheme`); storing it — and telling `Scheme::resolveProfileUsing()` — is
|
|
the application's. The dots are the only colours not drawn from tokens: they show other
|
|
profiles than the page's, so they are custom properties set inline from the scheme file's
|
|
checked hexes, light or dark with the page. The inline pair is the standard contrast level,
|
|
which is what the server can know; Alpine swaps in the level on screen
|
|
(`$store.theme.resolvedContrast`) so a dot never promises a colour the page would not paint.
|
|
Without profiles it renders nothing.
|
|
|
|
Props: `label`, `hint`, `name` (needed with `x-model`), `profiles` (default: every generated
|
|
profile). A validation message for the bound property replaces the hint. --}}
|
|
|
|
@props([
|
|
'label' => null,
|
|
'hint' => null,
|
|
'name' => null,
|
|
'profiles' => null,
|
|
])
|
|
|
|
@php
|
|
$profiles ??= \NoNameWeb\LivewireMaterial\Support\Scheme::profiles();
|
|
$model = $attributes->whereStartsWith('wire:model')->first();
|
|
$name ??= $model ?: 'scheme';
|
|
$errorKey = $model ?: (filled($attributes->get('name')) ? (string) $attributes->get('name') : null);
|
|
$messages = \NoNameWeb\LivewireMaterial\Support\Field::messages($errors ?? null, $errorKey);
|
|
$roles = ['primary', 'secondary', 'tertiary'];
|
|
|
|
// profile => level => theme => role => hex, for the dots. Every level is filled — a profile
|
|
// the caller passed in by hand, or a scheme file without the levels, repeats its standard
|
|
// colours — so the binding below never asks whether a level is there.
|
|
$swatches = [];
|
|
|
|
foreach ($profiles === [] ? [] : \NoNameWeb\LivewireMaterial\Support\Scheme::LEVELS as $level) {
|
|
$generated = $level === 'standard' ? [] : \NoNameWeb\LivewireMaterial\Support\Scheme::profiles(contrast: $level);
|
|
|
|
foreach ($profiles as $profile => $scheme) {
|
|
foreach (['light', 'dark'] as $theme) {
|
|
foreach ($roles as $role) {
|
|
$swatches[$profile][$level][$theme][$role] = ($generated[$profile] ?? $scheme)[$theme][$role];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@endphp
|
|
|
|
@if ($profiles !== [])
|
|
<fieldset
|
|
x-data="{ swatches: @js($swatches) }"
|
|
data-md-scheme-picker
|
|
{{ $attributes->whereDoesntStartWith(['wire:model', 'x-model'])->except('name') }}
|
|
>
|
|
@if (filled($label))
|
|
<legend data-md-scheme-picker-legend class="md-type-label-lg md-ink-variant">{{ $label }}</legend>
|
|
@endif
|
|
|
|
<div data-md-scheme-picker-options>
|
|
@foreach ($profiles as $profile => $scheme)
|
|
<label data-md-scheme-picker-option="{{ $profile }}" class="md-state-layer">
|
|
<input
|
|
class="md-visually-hidden"
|
|
{{ $attributes->whereStartsWith(['wire:model', 'x-model']) }}
|
|
type="radio"
|
|
name="{{ $name }}"
|
|
value="{{ $profile }}"
|
|
x-on:change="$store.theme.previewScheme($event.target.value)"
|
|
/>
|
|
|
|
<span data-md-scheme-picker-swatches aria-hidden="true">
|
|
@foreach ($roles as $role)
|
|
<span
|
|
data-md-scheme-picker-swatch
|
|
style="--swatch-light: {{ $scheme['light'][$role] }}; --swatch-dark: {{ $scheme['dark'][$role] }}"
|
|
x-bind:style="{
|
|
'--swatch-light': swatches['{{ $profile }}'][$store.theme.resolvedContrast].light['{{ $role }}'],
|
|
'--swatch-dark': swatches['{{ $profile }}'][$store.theme.resolvedContrast].dark['{{ $role }}'],
|
|
}"
|
|
></span>
|
|
@endforeach
|
|
</span>
|
|
|
|
<span data-md-scheme-picker-label class="md-truncate md-type-label-lg">{{ __($scheme['label']) }}</span>
|
|
|
|
{{-- In the corner, so a long name keeps its room. The wrapper carries the position. --}}
|
|
<span data-md-scheme-picker-check>
|
|
<x-livewire-material::icon name="check" :size="20" />
|
|
</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if ($messages !== [])
|
|
@foreach ($messages as $message)
|
|
<p data-md-scheme-picker-hint class="md-type-body-sm md-ink-error">{{ $message }}</p>
|
|
@endforeach
|
|
@elseif (filled($hint))
|
|
<p data-md-scheme-picker-hint class="md-type-body-sm md-ink-variant">{{ $hint }}</p>
|
|
@endif
|
|
</fieldset>
|
|
@endif
|