Draw the text field's chrome without Tailwind

<x-field> renders data-md-field with its props and parts as data-md-*
attributes (box, control, label, outline, support, counter, trailing
buttons), and field.css moves into material.components on tokens: px
geometry, spacing and state tokens, the 600px breakpoint (plan step 36).
Its icons take a size prop, 24/20/16 by the field's size.

Every control the field wraps now marks itself data-md-field-control,
so the inputs, the pickers, choices, field.js, menu.css's select and
listbox rules and timepicker.css follow the renamed hooks. Browser tests
cover the error icon, the disabled field's hover, the counter and the
width bound from 600px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 14:08:22 +02:00
co-authored by Claude Opus 5
parent a6e5a593d5
commit 0341f9d4ca
24 changed files with 478 additions and 322 deletions
+7 -7
View File
@@ -19,14 +19,14 @@ it('draws a read-only field that opens the picker in a labelled dialog', functio
$html = (string) $this->blade('<x-timepicker id="starts" label="Starts at" hint="In your time zone" value="14:30" format="24" />');
expect($html)
->toContain('<label for="starts" class="field-label">Starts at</label>')
->toContain('<label for="starts" data-md-field-label>Starts at</label>')
->toMatch('/<input[^>]*id="starts"[^>]*readonly/s')
->toContain('value="14:30"')
->toContain('aria-haspopup="dialog"')
->toContain('aria-controls="starts-dialog"')
->toContain('aria-expanded="false"')
->toContain('aria-describedby="starts-support"')
->toContain('<p id="starts-support" class="field-support">In your time zone</p>')
->toMatch('/<p id="starts-support" data-md-field-support\s*>In your time zone<\/p>/')
->toContain('x-on:keydown.enter.prevent="show()"')
->toContain('data-timepicker-open')
->toContain('aria-label="Choose time"')
@@ -38,7 +38,7 @@ it('draws a read-only field that opens the picker in a labelled dialog', functio
->toContain('x-modelable="value"')
->toContain("materialTimepicker( '14:30' ,")
->not->toContain('aria-invalid="true"')
->not->toContain('data-invalid');
->not->toContain('data-md-invalid');
});
it('draws the dial as a slider, with twelve numbers per ring and M3\'s 24-hour inner ring', function () {
@@ -153,7 +153,7 @@ it('shows the errors for its property in place of the hint', function () {
});
expect(Livewire::test('timepicker-errors')->html())
->toContain('data-invalid')
->toContain('data-md-invalid')
->toContain('aria-invalid="true"')
->toContain('aria-describedby="starts-support"')
->toContain('Choose a time during opening hours.')
@@ -165,13 +165,13 @@ it('puts the field props on the field, its attributes on the input, and posts a
expect($html)
->toMatch('/^<div\s+class="w-60"/')
->toContain('data-variant="filled"')
->toContain('data-size="sm"')
->toContain('data-md-variant="filled"')
->toContain('data-md-size="sm"')
->toContain('data-timepicker-field')
->toContain('required')
->toContain('placeholder="hh:mm"')
->toContain('<input type="hidden" name="alarm" value="06:30" x-bind:value="value ?? \'\'" />')
->toContain('data-field-clear')
->toContain('data-md-field-clear')
->toMatch('/<button[^>]*data-timepicker-open[^>]*disabled/s')
->and(substr_count($html, 'class="field w-60"'))->toBe(0);
});