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
79 lines
3.4 KiB
PHP
79 lines
3.4 KiB
PHP
{{-- One choice from a few, each with room for a line of explanation: M3's radio buttons.
|
|
|
|
A `<fieldset>` whose legend is the question, so a screen reader reads the group's name with each
|
|
option, and native radios sharing a name, so the arrow keys move between them. `options` as
|
|
`['id' => …, 'name' => …, 'hint' => …]` (`'disabled' => true` greys one out), `option-value`,
|
|
`option-label` and `option-hint` to read other keys, `inline` to lay them in a row from `sm`, and
|
|
`value` for the option chosen when the page loads (with `wire:model`, Livewire sets it).
|
|
Errors are read from the bag under the `wire:model` name
|
|
(resources/css/components/selection.css). --}}
|
|
|
|
@props([
|
|
'label' => null,
|
|
'hint' => null,
|
|
'options' => [],
|
|
'optionValue' => 'id',
|
|
'optionLabel' => 'name',
|
|
'optionHint' => 'hint',
|
|
'inline' => false,
|
|
'value' => null,
|
|
])
|
|
|
|
@php
|
|
$model = $attributes->whereStartsWith('wire:model')->first();
|
|
$name = $attributes->get('name') ?? $model ?? 'radio-'.substr(md5($label.'|'.json_encode($options)), 0, 12);
|
|
$id = 'radio-'.substr(md5($name.'|'.$label), 0, 12);
|
|
$messages = $model !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($model)) : [];
|
|
@endphp
|
|
|
|
<fieldset
|
|
{{ $attributes->only(['class', 'wire:key'])->class(['min-w-0']) }}
|
|
@if ($messages !== [] || filled($hint)) aria-describedby="{{ $id }}-support" @endif
|
|
>
|
|
@if (filled($label))
|
|
<legend class="mb-3 type-label-lg text-on-surface-variant">{{ $label }}</legend>
|
|
@endif
|
|
|
|
<div @class(['grid gap-4', 'sm:flex sm:flex-wrap sm:gap-x-6' => $inline])>
|
|
@foreach ($options as $option)
|
|
@php($optionHintText = data_get($option, $optionHint))
|
|
|
|
<label data-selection @if ($messages !== []) data-invalid @endif @class([
|
|
'flex gap-4',
|
|
'items-start' => filled($optionHintText),
|
|
'items-center' => blank($optionHintText),
|
|
])>
|
|
<span data-radio @class(['mt-0.5' => filled($optionHintText)])>
|
|
<input
|
|
{{ $attributes->except(['class', 'wire:key', 'name', 'id']) }}
|
|
type="radio"
|
|
name="{{ $name }}"
|
|
value="{{ data_get($option, $optionValue) }}"
|
|
@disabled(data_get($option, 'disabled'))
|
|
@checked($value !== null && (string) $value === (string) data_get($option, $optionValue))
|
|
@if ($messages !== []) aria-invalid="true" @endif
|
|
/>
|
|
<span data-dot></span>
|
|
</span>
|
|
|
|
<span class="min-w-0">
|
|
<span class="block type-body-lg text-on-surface" data-selection-label>{{ data_get($option, $optionLabel) }}</span>
|
|
@if (filled($optionHintText))
|
|
<span class="mt-0.5 block type-body-sm text-on-surface-variant">{{ $optionHintText }}</span>
|
|
@endif
|
|
</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if ($messages !== [])
|
|
<div id="{{ $id }}-support" class="mt-2 type-body-sm text-error" role="alert">
|
|
@foreach ($messages as $message)
|
|
<p>{{ $message }}</p>
|
|
@endforeach
|
|
</div>
|
|
@elseif (filled($hint))
|
|
<p id="{{ $id }}-support" class="mt-2 type-body-sm text-on-surface-variant">{{ $hint }}</p>
|
|
@endif
|
|
</fieldset>
|