Add M3 text fields, selects and selection controls
tests / feature (8.4) (push) Successful in 1m5s
tests / feature (8.5) (push) Successful in 1m4s
tests / lint (push) Successful in 59s
tests / browser (chrome, chromium) (push) Successful in 2m54s
tests / browser (firefox, firefox) (push) Successful in 3m3s
tests / browser (safari, webkit) (push) Successful in 4m13s

Outlined and filled text fields (input, password, auto-growing textarea,
native select with the customizable select menu, file), checkbox with an
indeterminate state, radio buttons, the M3 switch and form, ported from
ReStride and extended. The first half of Phase 6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
Andreas Reinhold / reini
2026-09-13 07:53:14 +02:00
co-authored by Claude Opus 5
parent ad724662ca
commit 7f92f1cb29
24 changed files with 2296 additions and 1 deletions
@@ -361,6 +361,45 @@ An M3 bottom sheet, bound like `<x-modal>`: modal by default (scrim, inert page,
A row of items that change size between M3's keylines as it scrolls (native scroll snap; items are masked, content keeps its size). `<x-carousel>`: `layout` (`multi-browse` default, `hero`, `uncontained`, `full-screen`), `item-width` (px or any CSS length; the large size multi-browse aims for, the fixed size uncontained keeps, the cap for hero; 186 by default), `height` (205px), `padding` (px at the ends, 0), `centered` (hero), `label` (the region's name, "Carousel" by default), `controls` (previous/next buttons: default fine pointers only, `true` always, `false` never). `<x-carousel-item>`: slot is an `<img>` (fills and crops) or an element sized `size-full`; `label` overlays a line of text. A focusable `region` of `slide` groups named "n of m"; arrow keys move one item while the row has focus, Home/End to the ends. Works after a Livewire morph, in RTL and under reduced motion. Give items a `wire:key` in a loop.
### `<x-form>`
A one-column grid of fields with an `actions` slot at the foot (the slot takes its own `class`); `separator` draws a divider above the actions.
```blade
<x-form wire:submit="save">
<x-input label="Share name" wire:model="name" required />
<x-select label="Expires after" wire:model="hours" :options="$expiryOptions" />
<x-slot:actions>
<x-button label="Cancel" wire:click="cancel" />
<x-button label="Create share" variant="filled" type="submit" spinner="save" />
</x-slot:actions>
</x-form>
```
### `<x-field>`, `<x-input>`, `<x-password>`, `<x-textarea>`, `<x-select>`, `<x-file>`
M3 text fields. `variant`: `outlined` or `filled`; without it, `config('livewire-material.fields.variant')` (`outlined`). All take `label`, `hint`, `variant`, and read their errors from the bag under the `wire:model` name (the error replaces the hint, sets `aria-invalid`). `class` lands on the field's outer element (margins, widths); every other attribute (`wire:model`, `type`, `required`, `readonly`, `autocomplete`) reaches the control. Never pass `placeholder` expecting it to show while a label rests in the field: it shows once the field has focus.
- `<x-input>`: `icon`, `icon-right`, `prefix`, `suffix`, `clearable`, `copyable` (copies the value, confirms with a snackbar), `size` (`sm` 40px, `xs` 32px — for unlabelled toolbar controls; give them `aria-label`), `mono`.
- `<x-password>`: a reveal button; `icon`, `size`.
- `<x-textarea>`: grows from `rows` (3) to `max-rows`, then scrolls; `:autogrow="false"` for a fixed, hand-resizable one.
- `<x-select>`: native `<select>` (M3 menu where the browser supports customizable selects). `options` as `['id' => …, 'name' => …, 'disabled' => bool]`, `option-value`, `option-label`, `placeholder` + `placeholder-value`, or `<option>`s in the slot; `icon`, `size`.
- `<x-file>`: native file input; errors from `photos` and `photos.*`. Show previews of what was chosen yourself.
- `<x-field id="…" label="…" :messages="$messages">` wraps a custom control given `class="field-control"`; only for controls the package does not have.
### `<x-checkbox>`, `<x-radio>`, `<x-toggle>`
M3 selection controls on native inputs; the whole row is the label.
- `<x-checkbox label hint right indeterminate />``indeterminate` for a "select all" whose items are partly ticked (bind it to a server expression; it follows every render).
- `<x-radio label wire:model :options inline />` — options `['id', 'name', 'hint', 'disabled']` (`option-value`, `option-label`, `option-hint`); `value` checks an option without `wire:model`; `name` names an unbound group.
- `<x-toggle label hint right icons />` — M3 switch (`role="switch"`); `icons` puts a check and a cross on the handle, `icons="selected"` only the check. Without `label`, pass `aria-label`.
```blade
<x-checkbox label="All files" :checked="count($selected) === $files->count()" :indeterminate="$selected && count($selected) < $files->count()" wire:click="toggleAll" />
<x-toggle label="Notify me on download" wire:model.live="notify" right />
```
## Testing the design
```php