CluPilotCloud/resources/views/components/ui/switch.blade.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>