Hold the time picker's two selectors apart

The 24-hour clock has no period selector beside the hour and minute
boxes, so M3 widens them to 114px; AM/PM takes its own
tertiary-container rather than borrowing the boxes' primary-container;
a disabled period button uses M3's 12% disabled container, not 10%; and
the pair is a radio group, which is what M3's labelling table asks for,
so the arrows move between AM and PM.

Plan step 20, findings IN-10, IN-11, IN-26 and IN-27.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 06:01:23 +02:00
co-authored by Claude Fable 5.1
parent f30fd575cf
commit 9624d4bfd4
5 changed files with 88 additions and 24 deletions
+39 -13
View File
@@ -31,14 +31,16 @@
TimePickerTokens and TimeInputTokens a surface-container-high dialog with an extra-large
corner and elevation 3, a 256dp surface-container-highest dial with body-large numbers, a primary
selector with a 48dp handle, a 2dp line and an 8dp centre, 96×80 time selector boxes in
display-large (primary-container when selected), 96×72 time fields in display-medium and
TimePicker.kt and TimePickerDialog.kt for the layout, the 24-hour inner ring (1223, at 69dp;
0011 outside at 101dp, as Material Components for Android labels them too), the gestures, the
move on to minutes and the error texts. The period selector is Compose's current default
(`isUpdatedTimepickerToggleEnabled`): two separate shape-morphing toggle buttons in
primary-container, not the outlined pair its tokens still describe. The dial's numbers are
aria-hidden; the dial is a `slider` whose value text names the hour or minute. The dialog is
`wire:ignore`, so a Livewire render never closes an open picker or resets its draft. --}}
display-large (primary-container when selected; 114dp wide on a 24-hour clock, where there is no
period selector beside them, per TimeSelector24HVerticalContainerWidth), 96×72 time fields in
display-medium and TimePicker.kt and TimePickerDialog.kt for the layout, the 24-hour inner
ring (1223, at 69dp; 0011 outside at 101dp, as Material Components for Android labels them
too), the gestures, the move on to minutes and the error texts. The period selector keeps
Compose's current default shape morph (`isUpdatedTimepickerToggleEnabled`) but M3's own
tertiary-container, which is what holds hour/minute (primary-container) and AM/PM apart; it is a
radio group, as M3's labelling table says, so the arrows move between AM and PM. The dial's
numbers are aria-hidden; the dial is a `slider` whose value text names the hour or minute. The
dialog is `wire:ignore`, so a Livewire render never closes an open picker or resets its draft. --}}
@props([
'label' => null,
@@ -211,7 +213,7 @@
<h2 id="{{ $id }}-title" data-timepicker-title x-text="mode === 'dial' ? @js(__('Select time')) : @js(__('Enter time'))">{{ __('Select time') }}</h2>
<div data-timepicker-picker>
<div data-timepicker-display>
<div data-timepicker-display x-bind:data-cycle="is24 ? '24' : '12'">
<div data-timepicker-numbers>
<button
type="button"
@@ -236,13 +238,25 @@
></button>
</div>
<div data-timepicker-period role="group" aria-label="{{ __('Select AM or PM') }}" x-show="! is24">
<div
data-timepicker-period
role="radiogroup"
aria-label="{{ __('Select AM or PM') }}"
x-show="! is24"
x-on:keydown.arrow-right.prevent="movePeriod($event)"
x-on:keydown.arrow-left.prevent="movePeriod($event)"
x-on:keydown.arrow-down.prevent="movePeriod($event)"
x-on:keydown.arrow-up.prevent="movePeriod($event)"
>
@foreach ([false => 'am', true => 'pm'] as $pm => $period)
<button
type="button"
data-timepicker-period-option="{{ $period }}"
class="state-layer focus-ring"
x-bind:aria-pressed="({{ $pm ? '' : '! ' }}isPm).toString()"
role="radio"
aria-checked="false"
x-bind:aria-checked="({{ $pm ? '' : '! ' }}isPm).toString()"
x-bind:tabindex="({{ $pm ? '' : '! ' }}isPm) ? 0 : -1"
x-bind:disabled="! periodAllowed({{ $pm ? 'true' : 'false' }})"
x-on:click="setPeriod({{ $pm ? 'true' : 'false' }})"
x-text="periods[{{ (int) $pm }}]"
@@ -330,13 +344,25 @@
</div>
@endforeach
<div data-timepicker-period role="group" aria-label="{{ __('Select AM or PM') }}" x-show="! is24">
<div
data-timepicker-period
role="radiogroup"
aria-label="{{ __('Select AM or PM') }}"
x-show="! is24"
x-on:keydown.arrow-right.prevent="movePeriod($event)"
x-on:keydown.arrow-left.prevent="movePeriod($event)"
x-on:keydown.arrow-down.prevent="movePeriod($event)"
x-on:keydown.arrow-up.prevent="movePeriod($event)"
>
@foreach ([false => 'am', true => 'pm'] as $pm => $period)
<button
type="button"
data-timepicker-period-option="{{ $period }}"
class="state-layer focus-ring"
x-bind:aria-pressed="({{ $pm ? '' : '! ' }}isPm).toString()"
role="radio"
aria-checked="false"
x-bind:aria-checked="({{ $pm ? '' : '! ' }}isPm).toString()"
x-bind:tabindex="({{ $pm ? '' : '! ' }}isPm) ? 0 : -1"
x-bind:disabled="! periodAllowed({{ $pm ? 'true' : 'false' }})"
x-on:click="setPeriod({{ $pm ? 'true' : 'false' }})"
x-text="periods[{{ (int) $pm }}]"