Draw the form without Tailwind
<x-form> renders data-md-form and data-md-form-actions, and its column and actions row move to components/form.css in material.components, on the spacing tokens (plan step 36). The actions slot's own attributes still land on the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
50431181a7
commit
a6e5a593d5
@@ -14,6 +14,7 @@
|
|||||||
/* Actions and communication */
|
/* Actions and communication */
|
||||||
|
|
||||||
/* Inputs, selection and data */
|
/* Inputs, selection and data */
|
||||||
|
@import './components/form.css';
|
||||||
|
|
||||||
/* Containment */
|
/* Containment */
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* <x-form>: its fields in one column, and its actions at the foot
|
||||||
|
* (resources/views/components/form.blade.php).
|
||||||
|
*
|
||||||
|
* The column is a one-track grid whose track is `minmax(0, 1fr)`: a bare grid's implicit column
|
||||||
|
* floors at its content's min-content, so on a phone a wide field would push the form past its
|
||||||
|
* pane. Rows take their content's height. 16px (`space200`) between fields, because a text field
|
||||||
|
* floats its label half above its outline and any less lets the hint under one field run into the
|
||||||
|
* label of the next.
|
||||||
|
*
|
||||||
|
* The actions wrap, end-aligned, 8px (`space100`) apart — M3's gap between buttons in a row. A
|
||||||
|
* caller's class on the `actions` slot lands on the row and outranks this layer.
|
||||||
|
*
|
||||||
|
* [data-md-form] the <form>
|
||||||
|
* [data-md-form-actions] the row of actions, under the divider when `separator` asks for one
|
||||||
|
*/
|
||||||
|
|
||||||
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||||||
|
|
||||||
|
/* TODO(step 36): @import './divider.css' once <x-divider> leaves Tailwind (containment stream). */
|
||||||
|
|
||||||
|
@layer material.components {
|
||||||
|
[data-md-form] {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
grid-auto-rows: min-content;
|
||||||
|
gap: var(--md-sys-measurement-space200);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-form-actions] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--md-sys-measurement-space100);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
{{-- A form: its fields in one column, and its actions at the foot.
|
{{-- A form: its fields in one column, and its actions at the foot.
|
||||||
|
|
||||||
`gap-4` between fields, because a text field floats its label half above its outline: any less
|
16px between fields, because a text field floats its label half above its outline: any less
|
||||||
and the hint under one field runs into the label of the next. The `actions` slot takes a class
|
and the hint under one field runs into the label of the next. The `actions` slot takes
|
||||||
of its own; `separator` draws a divider above it.
|
attributes of its own (a `class` from the call site lands on the row untouched); `separator`
|
||||||
|
draws a divider above it. The column is resources/css/components/form.css. --}}
|
||||||
`grid-cols-1`, because a bare `grid` has no columns and its implicit one floors at the
|
|
||||||
content's min-content: on a phone a wide field would push the form past its column. --}}
|
|
||||||
|
|
||||||
@props([
|
@props([
|
||||||
'separator' => false,
|
'separator' => false,
|
||||||
])
|
])
|
||||||
|
|
||||||
<form {{ $attributes->class(['grid grid-cols-1 auto-rows-min gap-4']) }}>
|
<form data-md-form {{ $attributes }}>
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
|
|
||||||
@isset($actions)
|
@isset($actions)
|
||||||
@@ -19,7 +17,7 @@
|
|||||||
<x-livewire-material::divider />
|
<x-livewire-material::divider />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div {{ $actions->attributes->class(['flex flex-wrap items-center justify-end gap-2']) }}>
|
<div data-md-form-actions {{ $actions->attributes }}>
|
||||||
{{ $actions }}
|
{{ $actions }}
|
||||||
</div>
|
</div>
|
||||||
@endisset
|
@endisset
|
||||||
|
|||||||
@@ -189,10 +189,20 @@ it('lays out a form with its actions under an optional divider', function () {
|
|||||||
BLADE);
|
BLADE);
|
||||||
|
|
||||||
expect($html)
|
expect($html)
|
||||||
->toContain('<form class="grid grid-cols-1 auto-rows-min gap-4" wire:submit="save">')
|
->toContain('<form data-md-form wire:submit="save">')
|
||||||
->toContain('role="separator"')
|
->toContain('role="separator"')
|
||||||
->toContain('justify-between')
|
->toContain('<div data-md-form-actions class="justify-between">')
|
||||||
|
->not->toContain('grid-cols-1')
|
||||||
->toContain('<button>Save</button>')
|
->toContain('<button>Save</button>')
|
||||||
->and((string) $this->blade('<x-form><x-slot:actions><button>Save</button></x-slot:actions></x-form>'))
|
->and((string) $this->blade('<x-form><x-slot:actions><button>Save</button></x-slot:actions></x-form>'))
|
||||||
->not->toContain('role="separator"');
|
->not->toContain('role="separator"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('draws a form\'s column and its actions from its stylesheet', function () {
|
||||||
|
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/form.css');
|
||||||
|
|
||||||
|
expect($css)->toContain('@layer material.components')
|
||||||
|
->toContain('grid-template-columns: minmax(0, 1fr);')
|
||||||
|
->toContain('gap: var(--md-sys-measurement-space200);')
|
||||||
|
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/form.css';");
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user