diff --git a/resources/css/components.css b/resources/css/components.css index 4216cc33..5aba67ca 100644 --- a/resources/css/components.css +++ b/resources/css/components.css @@ -26,6 +26,7 @@ @import './components/toggle.css'; @import './components/chip.css'; @import './components/chip-set.css'; +@import './components/choices.css'; /* Containment */ diff --git a/resources/css/components/choices.css b/resources/css/components/choices.css new file mode 100644 index 00000000..18391cec --- /dev/null +++ b/resources/css/components/choices.css @@ -0,0 +1,31 @@ +/* + * : choosing from a list, as M3's filter chips or as a searchable field with a menu + * (resources/views/components/choices.blade.php). + * + * Almost nothing of its own to draw: the chips are 's and 's, the searchable + * field is 's with its arrow turning over while the list is open (components/field.css), + * and the open list is M3's menu, drawn by components/menu.css on `data-md-field-menu` and + * `data-md-field-option`. What is left is the row that says nothing matches: body-medium in + * on-surface-variant, padded like a menu row (16px across, 12px down). + * + * [data-md-choices] the root; data-md-searchable + * [data-md-choices-empty] the list's "Nothing matches" row + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import './field.css'; +@import './icon.css'; +@import './chip-set.css'; +@import './chip.css'; +/* TODO(step 36): @import './menu.css' once the menu leaves Tailwind (actions stream); its field-menu rules draw the open list. */ + +@layer material.components { + [data-md-choices-empty] { + padding: 12px var(--md-sys-measurement-space200); + color: var(--md-sys-color-on-surface-variant); + font: var(--md-sys-typescale-body-md); + letter-spacing: var(--md-sys-typescale-body-md-tracking); + font-variation-settings: normal; + } +} diff --git a/resources/views/components/choices.blade.php b/resources/views/components/choices.blade.php index b74c1873..1c66c8eb 100644 --- a/resources/views/components/choices.blade.php +++ b/resources/views/components/choices.blade.php @@ -19,7 +19,12 @@ string: a list of integers stays a list of integers. Without `wire:model` it works on its own and binds with `x-model`. Errors for the property and its items replace the hint. When the options change on the server, give the component a `wire:key` that changes with them: the - options are baked into its Alpine state. --}} + options are baked into its Alpine state. + + The root renders `data-md-choices` (and `data-md-searchable`); the chips are ``'s, the + field ``'s, and the open list M3's menu (resources/css/components/menu.css, as + `data-md-field-menu` and `data-md-field-option`). resources/css/components/choices.css brings + them together. --}} @props([ 'label' => null, @@ -64,7 +69,9 @@ @if ($searchable)
only(['class', 'wire:key', 'x-model']) }} + data-md-choices + data-md-searchable + {{ $attributes->only(['class', 'style', 'wire:key', 'x-model']) }} x-data="{ @if ($model !== null) value: @entangle($attributes->wire('model')), @else value: @js($current), @endif options: @js($choices), @@ -187,12 +194,13 @@ -
  • {{ __('Nothing matches') }}
  • +
  • {{ __('Nothing matches') }}
  • @else
    only(['class', 'wire:key', 'x-model']) }} + data-md-choices + {{ $attributes->only(['class', 'style', 'wire:key', 'x-model']) }} x-data="{ @if ($model !== null) selection: @entangle($attributes->wire('model')), @else selection: @js($current ?? ($single ? null : [])), @endif options: @js(array_column($choices, 'value')), diff --git a/tests/Feature/Components/ChoicesTest.php b/tests/Feature/Components/ChoicesTest.php index d2234a4e..2cfbe5a7 100644 --- a/tests/Feature/Components/ChoicesTest.php +++ b/tests/Feature/Components/ChoicesTest.php @@ -7,6 +7,8 @@ it('lays every option out as a filter chip', function () { $html = (string) $this->blade(''); expect($html) + ->toMatch('/not->toContain('data-md-searchable') ->toContain('role="group"') ->toContain('Days') ->toContain('Pick any') @@ -42,6 +44,7 @@ it('draws a searchable combobox with a popover list', function () { $html = (string) $this->blade(''); expect($html) + ->toMatch('/toContain('role="combobox"') ->toContain('aria-controls="zone-list"') ->toContain('aria-expanded="false"') @@ -55,7 +58,10 @@ it('draws a searchable combobox with a popover list', function () { // WAI-ARIA's combobox keyboard, Home and End included. ->toContain('x-on:keydown.home="jump($event, false)"') ->toContain('x-on:keydown.end="jump($event, true)"') - ->toContain('Nothing matches'); + ->toContain('
  • Nothing matches
  • ') + ->toContain('data-md-field-menu') + ->toContain('data-md-field-option') + ->not->toContain('class='); preg_match('/anchor-name: (--material-choices-[a-z0-9]{10})/', $html, $anchor); @@ -92,3 +98,13 @@ it('shows the errors for the property and its items', function () { ->toContain('Choose a time zone.') ->toContain('aria-invalid="true"'); }); + +it('brings the stylesheets of what it renders, and draws its empty row', function () { + $css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/choices.css'); + + expect($css)->toContain("@import './field.css';") + ->toContain("@import './chip-set.css';") + ->toContain("@import './chip.css';") + ->toContain('[data-md-choices-empty] {') + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/choices.css';"); +});