Give an unlabelled checkbox, radio or switch a 48px target
A selection control with a label is pressed anywhere along its row, but one without — a table's "select all", a row's tick — was only its 18, 20 or 32px box, because the 40px state layer is a ::before that catches no pointer. The box now wears the shared touch-target utility, which M3 asks for on all three (checkbox, radio and switch specs: target size 48dp). Plan step 13, finding IN-03. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
35548dc13b
commit
da4952e684
@@ -19,6 +19,10 @@
|
||||
* Every state layer is on-surface when unselected and primary when selected, 8% on hover (only
|
||||
* where a pointer can hover) and 10% on focus and press; the focus indicator is the package's
|
||||
* secondary ring. A disabled control is on-surface at 38% and has no state layer.
|
||||
*
|
||||
* The state layer is a ::before with no pointer events, so it is not a target: a labelled control
|
||||
* is pressed anywhere along its row, and a control with no label wears the `touch-target` utility
|
||||
* (resources/css/tokens/state.css) so its box catches presses over M3's 48px minimum.
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
partly ticked: HTML has no attribute for it, so the input is marked `data-indeterminate` and
|
||||
resources/js/field.js keeps the property in step, on load and after every morph. Errors are
|
||||
read from the bag under the `wire:model` name. Every other attribute reaches the `<input>`
|
||||
(resources/css/components/selection.css). --}}
|
||||
(resources/css/components/selection.css).
|
||||
|
||||
Without a label — a "select all" in a table header, a row's tick — the row is only the 18px box,
|
||||
so the box carries `touch-target` and catches presses over M3's 48px minimum. --}}
|
||||
|
||||
@props([
|
||||
'label' => null,
|
||||
@@ -30,7 +33,10 @@
|
||||
'items-center' => blank($hint),
|
||||
'flex-row-reverse justify-between' => $right,
|
||||
])>
|
||||
<span data-checkbox @class(['mt-[3px]' => filled($hint)])>
|
||||
<span data-checkbox @class([
|
||||
'mt-[3px]' => filled($hint),
|
||||
'touch-target' => blank($label) && blank($hint),
|
||||
])>
|
||||
<input
|
||||
{{ $attributes->except(['class', 'id', 'wire:key']) }}
|
||||
id="{{ $id }}"
|
||||
|
||||
@@ -46,7 +46,10 @@
|
||||
'items-start' => filled($optionHintText),
|
||||
'items-center' => blank($optionHintText),
|
||||
])>
|
||||
<span data-radio @class(['mt-0.5' => filled($optionHintText)])>
|
||||
<span data-radio @class([
|
||||
'mt-0.5' => filled($optionHintText),
|
||||
'touch-target' => blank(data_get($option, $optionLabel)) && blank($optionHintText),
|
||||
])>
|
||||
<input
|
||||
{{ $attributes->except(['class', 'wire:key', 'name', 'id']) }}
|
||||
type="radio"
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
its name — a switch without `label` needs an `aria-label`. `right` puts it at the end of the
|
||||
row, where a setting's switch usually is. `icons` draws a check on the handle when on and a
|
||||
cross when off; `icons="selected"` only the check. Every other attribute reaches the `<input>`
|
||||
(resources/css/components/selection.css). --}}
|
||||
(resources/css/components/selection.css).
|
||||
|
||||
Without a label the row is only the 52×32 track, so the track carries `touch-target` and catches
|
||||
presses over M3's 48px minimum. --}}
|
||||
|
||||
@props([
|
||||
'label' => null,
|
||||
@@ -27,7 +30,7 @@
|
||||
'items-center' => blank($hint),
|
||||
'flex-row-reverse justify-between' => $right,
|
||||
])>
|
||||
<span data-switch @if ($icons) data-icons="{{ $icons }}" @endif>
|
||||
<span data-switch @if ($icons) data-icons="{{ $icons }}" @endif @class(['touch-target' => blank($label) && blank($hint)])>
|
||||
<input
|
||||
{{ $attributes->except(['class', 'id', 'wire:key']) }}
|
||||
id="{{ $id }}"
|
||||
|
||||
@@ -20,6 +20,17 @@ it('marks a checkbox that is partly ticked', function () {
|
||||
expect((string) $this->blade('<x-checkbox label="Select all" indeterminate />'))->toContain('data-indeterminate');
|
||||
});
|
||||
|
||||
it('gives a selection control without a label a 48px target', function () {
|
||||
expect((string) $this->blade('<x-checkbox aria-label="Select all" />'))->toContain('touch-target')
|
||||
->and((string) $this->blade('<x-toggle aria-label="Public link" />'))->toContain('touch-target')
|
||||
->and((string) $this->blade('<x-radio :options="[[\'id\' => \'a\', \'name\' => \'\']]" />'))->toContain('touch-target');
|
||||
|
||||
// A labelled control is pressed anywhere along its row, so it needs no extra target.
|
||||
expect((string) $this->blade('<x-checkbox label="Notify me" />'))->not->toContain('touch-target')
|
||||
->and((string) $this->blade('<x-toggle label="Public link" />'))->not->toContain('touch-target')
|
||||
->and((string) $this->blade('<x-radio :options="[[\'id\' => \'a\', \'name\' => \'A\']]" />'))->not->toContain('touch-target');
|
||||
});
|
||||
|
||||
it('puts a checkbox at the end of its row on request', function () {
|
||||
expect((string) $this->blade('<x-checkbox label="Show" right />'))->toContain('flex-row-reverse justify-between');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user