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>
110 lines
5.0 KiB
PHP
110 lines
5.0 KiB
PHP
{{-- A choice of a few options as an M3 Expressive connected button group — the successor of the
|
|
segmented button.
|
|
|
|
<x-group label="Theme" wire:model.live="theme" :options="[
|
|
['id' => 'light', 'name' => 'Light', 'icon' => 'light_mode'],
|
|
['id' => 'dark', 'name' => 'Dark', 'icon' => 'dark_mode'],
|
|
]" />
|
|
|
|
Native radios under the segments (checkboxes with `multiple`), so `wire:model` and `x-model`
|
|
bind as on any input, the arrow keys move the choice, and a screen reader announces a group.
|
|
The chosen segment rounds fully and takes the selected colour; `variant` is `tonal` (the
|
|
default), `filled` or `outlined`, as for toggle buttons. The segments share the row unless
|
|
`inline`. An option with `'disabled' => true` greys its own segment. At `xs` and `sm` a
|
|
segment keeps a 48px target and a 48px minimum width, which M3 asks for by name and tells
|
|
you never to reduce. Corners move on the spatial spring and colours on the effects one.
|
|
|
|
`shape` is M3's "Default shape | Round, square" configuration: `square` squares the two ends
|
|
of the group to the corner its inner edges take (M3's square table, 4/8/8/16/20dp by size),
|
|
and the chosen segment still rounds fully, so selection reads the same either way.
|
|
|
|
ReStride's props, kept: `label`, `hint`, `hint-class`, `name` (needed with `x-model`, which
|
|
names no property), `options`, `option-value`, `option-label`; plus `option-icon`, `size`,
|
|
`variant`, `shape`, `multiple`, `inline`. A validation message for the bound property replaces
|
|
the hint.
|
|
|
|
`hint-class` adds classes to the hint, as on `<x-field>`: a colour there paints it
|
|
(`hint-class="md-ink-warning"` for a hint that warns), because a caller's class outranks the
|
|
package's layer.
|
|
|
|
Drawn by resources/css/components/group.css, with the connected shapes of
|
|
resources/css/components/button-group.css. --}}
|
|
|
|
@props([
|
|
'label' => null,
|
|
'hint' => null,
|
|
'hintClass' => null,
|
|
'name' => null,
|
|
'options' => [],
|
|
'optionValue' => 'id',
|
|
'optionLabel' => 'name',
|
|
'optionIcon' => 'icon',
|
|
'size' => 'sm',
|
|
'variant' => 'tonal',
|
|
'shape' => 'round',
|
|
'multiple' => false,
|
|
'inline' => false,
|
|
])
|
|
|
|
@php
|
|
$model = $attributes->whereStartsWith('wire:model')->first();
|
|
$name ??= $model;
|
|
$field = \NoNameWeb\LivewireMaterial\Support\Field::class;
|
|
$errorKey = $field::key($model, $attributes->get('name'));
|
|
$messages = $field::messages($errors ?? null, $errorKey);
|
|
$size = in_array($size, ['xs', 'sm', 'md', 'lg', 'xl'], true) ? $size : 'sm';
|
|
$variant = in_array($variant, ['tonal', 'filled', 'outlined'], true) ? $variant : 'tonal';
|
|
// M3's "Default shape | Round, square": a square group's ends take the corner its inner edges
|
|
// take; the chosen segment still rounds fully, which is the cue selection has always carried.
|
|
$shape = $shape === 'square' ? 'square' : 'round';
|
|
|
|
// A 20px glyph comes from M3's 20px cut, which the icon component picks from the size.
|
|
$iconSize = ['xs' => 20, 'sm' => 20, 'md' => 24, 'lg' => 32, 'xl' => 40][$size];
|
|
// Below `medium` a segment draws under 48px, so it needs the foundation's touch target too.
|
|
$segmentNeedsTouchTarget = in_array($size, ['xs', 'sm'], true);
|
|
|
|
$root = $attributes->only(['class', 'style', 'wire:key'])->merge([
|
|
'data-md-group' => true,
|
|
'data-md-size' => $size,
|
|
'data-md-variant' => $variant,
|
|
'data-md-shape' => $shape,
|
|
'data-md-multiple' => $multiple ? true : null,
|
|
'data-md-inline' => $inline ? true : null,
|
|
]);
|
|
@endphp
|
|
|
|
<fieldset {{ $root }}>
|
|
@if (filled($label))
|
|
<legend data-md-group-legend>{{ $label }}</legend>
|
|
@endif
|
|
|
|
<div data-md-button-group="connected" data-md-size="{{ $size }}" data-md-shape="{{ $shape }}">
|
|
@foreach ($options as $option)
|
|
<label data-md-group-segment class="md-state-layer @if ($segmentNeedsTouchTarget) md-touch-target @endif">
|
|
<input
|
|
class="md-visually-hidden"
|
|
{{ $attributes->whereStartsWith(['wire:model', 'x-model']) }}
|
|
type="{{ $multiple ? 'checkbox' : 'radio' }}"
|
|
name="{{ $multiple ? $name.'[]' : $name }}"
|
|
value="{{ data_get($option, $optionValue) }}"
|
|
@disabled(data_get($option, 'disabled'))
|
|
/>
|
|
|
|
@if (filled(data_get($option, $optionIcon)))
|
|
<x-livewire-material::icon :name="data_get($option, $optionIcon)" :size="$iconSize" />
|
|
@endif
|
|
|
|
<span data-md-group-label>{{ data_get($option, $optionLabel) }}</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if ($messages !== [])
|
|
@foreach ($messages as $message)
|
|
<p data-md-group-error>{{ $message }}</p>
|
|
@endforeach
|
|
@elseif (filled($hint))
|
|
<p data-md-group-hint @if (filled($hintClass)) class="{{ $hintClass }}" @endif>{{ $hint }}</p>
|
|
@endif
|
|
</fieldset>
|