<x-field> renders data-md-field with its props and parts as data-md-* attributes (box, control, label, outline, support, counter, trailing buttons), and field.css moves into material.components on tokens: px geometry, spacing and state tokens, the 600px breakpoint (plan step 36). Its icons take a size prop, 24/20/16 by the field's size. Every control the field wraps now marks itself data-md-field-control, so the inputs, the pickers, choices, field.js, menu.css's select and listbox rules and timepicker.css follow the renamed hooks. Browser tests cover the error icon, the disabled field's hover, the counter and the width bound from 600px. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
61 lines
2.8 KiB
PHP
61 lines
2.8 KiB
PHP
{{-- A select: the native `<select>`, drawn as M3's exposed dropdown menu.
|
|
|
|
Native on purpose: the keyboard, type-to-find, the screen reader and the form value are the
|
|
browser's, and `wire:model` binds to it as to any select. Where the browser has the
|
|
customizable select (`appearance: base-select`) its open list is M3's menu, under the field
|
|
and as wide (resources/css/components/menu.css); elsewhere the native list stands in. So the
|
|
options stay plain text: the native list could show nothing more. A select always shows a
|
|
value, so its label always floats.
|
|
|
|
`options` as a list of `['id' => …, 'name' => …]` (`'disabled' => true` greys one out),
|
|
`option-value` and `option-label` to read other keys, and a `placeholder` that becomes the
|
|
first option, valued `placeholder-value` (empty by default). Or pass `<option>`s in the slot.
|
|
`label`, `hint`, `icon`, `variant`, `size`. --}}
|
|
|
|
@props([
|
|
'label' => null,
|
|
'hint' => null,
|
|
'hintClass' => null,
|
|
'icon' => null,
|
|
'options' => [],
|
|
'optionValue' => 'id',
|
|
'optionLabel' => 'name',
|
|
'placeholder' => null,
|
|
'placeholderValue' => null,
|
|
'size' => 'md',
|
|
'variant' => null,
|
|
])
|
|
|
|
@php
|
|
$model = $attributes->whereStartsWith('wire:model')->first();
|
|
$id = $attributes->get('id') ?? 'field-'.substr(md5($model.'|'.$label.'|select'), 0, 12);
|
|
// A plain form's field is named, not bound: its errors are under its name (`files[]` → `files`, `a[b]` → `a.b`).
|
|
$errorKey = $model ?? (filled($attributes->get('name')) ? str_replace(['[]', '[', ']'], ['', '.', ''], (string) $attributes->get('name')) : null);
|
|
$messages = $errorKey !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($errorKey)) : [];
|
|
$fieldIconSize = ['sm' => 20, 'xs' => 16][$size] ?? 24;
|
|
@endphp
|
|
|
|
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$icon :$size :$variant floated :class="$attributes->get('class')">
|
|
<select
|
|
{{ $attributes->except(['class', 'id']) }}
|
|
id="{{ $id }}"
|
|
@if ($messages !== []) aria-invalid="true" @endif
|
|
@if ($messages !== [] || filled($hint)) aria-describedby="{{ $id }}-support" @endif
|
|
data-md-field-control
|
|
>
|
|
@if (filled($placeholder))
|
|
<option value="{{ $placeholderValue }}">{{ $placeholder }}</option>
|
|
@endif
|
|
|
|
@foreach ($options as $option)
|
|
<option value="{{ data_get($option, $optionValue) }}" @disabled(data_get($option, 'disabled'))>{{ data_get($option, $optionLabel) }}</option>
|
|
@endforeach
|
|
|
|
{{ $slot }}
|
|
</select>
|
|
|
|
<x-slot:trailing>
|
|
<x-livewire-material::icon name="arrow_drop_down" :size="$fieldIconSize" data-md-field-trailing data-md-field-arrow />
|
|
</x-slot:trailing>
|
|
</x-livewire-material::field>
|