71 lines
3.5 KiB
PHP
71 lines
3.5 KiB
PHP
@props([
|
|
'name',
|
|
'label' => null,
|
|
'hint' => null,
|
|
'on' => null,
|
|
'off' => null,
|
|
])
|
|
@php $id = $attributes->get('id', $name); @endphp
|
|
{{--
|
|
An on/off control that reads as on/off.
|
|
|
|
A two-option <select> was the honest first cut — two states, so two options —
|
|
but it reads as "pick a driver from a list", which is exactly the impression
|
|
a setting like mail delivery must NOT give. A switch says the thing itself:
|
|
it is on, or it is off.
|
|
|
|
The state WORD next to it is deliberate and not decoration. A bare toggle
|
|
leaves "is grey off, or is grey merely unselected?" to the reader, and on a
|
|
setting that decides whether customers hear from us at all, that question
|
|
must not exist. It is also what a screen reader without the visual gets.
|
|
|
|
Pure CSS. The input is a real checkbox, so wire:model, keyboard focus and
|
|
screen readers work with nothing added — but everything that reacts to it
|
|
has to be a SIBLING of the input, because Tailwind's `peer-checked:` compiles
|
|
to `.peer:checked ~ …`. Hence the knob sitting absolutely over the track
|
|
rather than inside it: nested, it would simply never move.
|
|
--}}
|
|
<div class="flex items-start justify-between gap-4">
|
|
<div class="min-w-0">
|
|
@if ($label)
|
|
<label for="{{ $id }}" class="block text-sm font-medium text-ink">{{ $label }}</label>
|
|
@endif
|
|
@if ($hint)
|
|
<p class="mt-0.5 max-w-[68ch] text-xs leading-relaxed text-muted">{{ $hint }}</p>
|
|
@endif
|
|
{{ $slot }}
|
|
</div>
|
|
|
|
<label for="{{ $id }}" class="relative inline-flex shrink-0 cursor-pointer items-center gap-2.5">
|
|
<input id="{{ $id }}" name="{{ $name }}" type="checkbox" role="switch"
|
|
class="peer sr-only"
|
|
{{ $attributes->except(['id', 'class']) }}>
|
|
|
|
{{-- Track. --}}
|
|
<span class="block h-6 w-11 rounded-pill border border-line-strong bg-surface-2 transition-colors
|
|
peer-checked:border-accent-border peer-checked:bg-accent-active
|
|
peer-focus-visible:ring-2 peer-focus-visible:ring-accent-border"></span>
|
|
|
|
{{-- Knob — sibling of the input, laid over the track.
|
|
|
|
`top-1/2 -translate-y-1/2` rather than a top offset in pixels: an
|
|
absolutely positioned flex child has no useful static position to
|
|
fall back on, and a hard-coded offset would drift the moment the
|
|
row's line-height changes. Tailwind composes translate-x and
|
|
translate-y through separate custom properties, so the checked
|
|
state can move it sideways without losing the centring. --}}
|
|
<span class="pointer-events-none absolute left-0.5 top-1/2 size-5 -translate-y-1/2 rounded-pill bg-surface
|
|
shadow-xs ring-1 ring-line transition-transform peer-checked:translate-x-5"></span>
|
|
|
|
@if ($on !== null && $off !== null)
|
|
{{-- Gleiche Mindestbreite für beide Wörter, sonst wandert der
|
|
Schalter beim Umlegen: „Wird versendet" ist breiter als „Nur
|
|
ins Log", und die Zeile ist rechtsbündig. `min-w`, nicht `w` —
|
|
eine längere Übersetzung darf schieben, aber nie abgeschnitten
|
|
werden. --}}
|
|
<span class="inline-block min-w-[6.5rem] text-xs font-medium text-muted peer-checked:hidden">{{ $off }}</span>
|
|
<span class="hidden min-w-[6.5rem] text-xs font-medium text-accent-text peer-checked:inline">{{ $on }}</span>
|
|
@endif
|
|
</label>
|
|
</div>
|