Draw the search without Tailwind
<x-search> renders data-md-search with data-md-trigger and binds data-md-open and data-md-full-screen; its parts become data-md-search-* and the empty row data-md-search-empty. search.css moves into material.components on px, spacing and state tokens (plan step 36). search.js and the showcase layout follow the renamed hooks. Browser tests cover the polite result count as it changes, the icon entry point expanding full screen and returning focus, and suggestions swapping to results on the first key. 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
281c21b0a4
commit
4141ebe7b0
@@ -25,7 +25,11 @@
|
||||
The bar is a combobox: the wrapper around the input carries `role="combobox"` with
|
||||
`aria-expanded` and `aria-controls`, because ARIA gives a bare textbox neither. The results are
|
||||
a list, and a polite live region says how many of them there are whenever they change — M3 asks
|
||||
for both (docs/reference/m3/components-navigation-selection-inputs.md § Search). --}}
|
||||
for both (docs/reference/m3/components-navigation-selection-inputs.md § Search).
|
||||
|
||||
The root renders `data-md-search` with `data-md-trigger`, and `data-md-open` and
|
||||
`data-md-full-screen` while they hold; the parts are `data-md-search-*`, drawn by
|
||||
resources/css/components/search.css. `class` and `wire:key` land on the root. --}}
|
||||
|
||||
@props([
|
||||
'placeholder' => null,
|
||||
@@ -56,13 +60,13 @@
|
||||
x-on:focusout="leave($event)"
|
||||
x-on:pointerdown.outside="close()"
|
||||
x-trap.noscroll="fullScreen"
|
||||
x-bind:data-open="open ? '' : null"
|
||||
x-bind:data-full-screen="fullScreen ? '' : null"
|
||||
data-search
|
||||
data-trigger="{{ $trigger }}"
|
||||
{{ $attributes->only(['class', 'wire:key'])->class(['relative']) }}
|
||||
x-bind:data-md-open="open ? '' : null"
|
||||
x-bind:data-md-full-screen="fullScreen ? '' : null"
|
||||
data-md-search
|
||||
data-md-trigger="{{ $trigger }}"
|
||||
{{ $attributes->only(['class', 'style', 'wire:key']) }}
|
||||
>
|
||||
<div data-search-scrim x-cloak x-show="open && ! fullScreen" x-on:pointerdown="close()" aria-hidden="true"></div>
|
||||
<div data-md-search-scrim x-cloak x-show="open && ! fullScreen" x-on:pointerdown="close()" aria-hidden="true"></div>
|
||||
|
||||
@if ($trigger === 'icon')
|
||||
<button
|
||||
@@ -75,23 +79,23 @@
|
||||
aria-controls="{{ $id }}-view"
|
||||
aria-expanded="false"
|
||||
x-bind:aria-expanded="open.toString()"
|
||||
data-search-trigger
|
||||
data-md-search-trigger
|
||||
>
|
||||
<x-livewire-material::icon :name="$icon" />
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<div data-search-bar role="search" x-on:click="if ($event.target === $el) $refs.input.focus()">
|
||||
<span data-search-leading x-show="! fullScreen">
|
||||
<div data-md-search-bar role="search" x-on:click="if ($event.target === $el) $refs.input.focus()">
|
||||
<span data-md-search-leading x-show="! fullScreen">
|
||||
<x-livewire-material::icon :name="$icon" />
|
||||
</span>
|
||||
|
||||
<button type="button" data-search-leading data-search-back x-show="fullScreen" x-cloak x-on:click="close(true)" aria-label="{{ __('Back') }}">
|
||||
<button type="button" data-md-search-leading data-md-search-back x-show="fullScreen" x-cloak x-on:click="close(true)" aria-label="{{ __('Back') }}">
|
||||
<x-livewire-material::icon name="arrow_back" />
|
||||
</button>
|
||||
|
||||
<span
|
||||
data-search-field
|
||||
data-md-search-field
|
||||
role="combobox"
|
||||
aria-haspopup="dialog"
|
||||
aria-controls="{{ $id }}-view"
|
||||
@@ -111,23 +115,23 @@
|
||||
x-on:click="show()"
|
||||
x-on:input="typed($event)"
|
||||
x-on:keydown.arrow-down.prevent="show(); $nextTick(() => step(1))"
|
||||
data-search-input
|
||||
data-md-search-input
|
||||
/>
|
||||
</span>
|
||||
|
||||
<button type="button" data-search-clear x-on:click="clear()" aria-label="{{ __('Clear') }}">
|
||||
<button type="button" data-md-search-clear x-on:click="clear()" aria-label="{{ __('Clear') }}">
|
||||
<x-livewire-material::icon name="close" />
|
||||
</button>
|
||||
|
||||
@isset($trailing)
|
||||
<span data-search-trailing>{{ $trailing }}</span>
|
||||
<span data-md-search-trailing>{{ $trailing }}</span>
|
||||
@endisset
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-ref="view"
|
||||
id="{{ $id }}-view"
|
||||
data-search-view
|
||||
data-md-search-view
|
||||
x-cloak
|
||||
x-show="open"
|
||||
x-on:keydown.arrow-down.prevent="step(1)"
|
||||
@@ -135,15 +139,15 @@
|
||||
x-on:click="choose($event)"
|
||||
>
|
||||
@isset($suggestions)
|
||||
<div data-search-suggestions role="list" x-show="query === ''">{{ $suggestions }}</div>
|
||||
<div data-md-search-suggestions role="list" x-show="query === ''">{{ $suggestions }}</div>
|
||||
@endisset
|
||||
|
||||
@if ($slot->hasActualContent())
|
||||
<div data-search-results role="list" @isset($suggestions) x-cloak x-show="query !== ''" @endisset>{{ $slot }}</div>
|
||||
<div data-md-search-results role="list" @isset($suggestions) x-cloak x-show="query !== ''" @endisset>{{ $slot }}</div>
|
||||
@elseif (isset($empty))
|
||||
<div data-search-results @isset($suggestions) x-cloak x-show="query !== ''" @endisset><p class="px-4 py-3 type-body-md text-on-surface-variant">{{ $empty }}</p></div>
|
||||
<div data-md-search-results @isset($suggestions) x-cloak x-show="query !== ''" @endisset><p data-md-search-empty>{{ $empty }}</p></div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<span data-search-status class="sr-only" aria-live="polite" aria-atomic="true" x-text="announcement"></span>
|
||||
<span data-md-search-status class="md-visually-hidden" aria-live="polite" aria-atomic="true" x-text="announcement"></span>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
if (! result || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (event.button ?? 0) !== 0) return;
|
||||
event.preventDefault();
|
||||
const url = new URL(result.url, window.location.href);
|
||||
const search = this.$root.querySelector('[data-search]');
|
||||
const search = this.$root.querySelector('[data-md-search]');
|
||||
this.query = '';
|
||||
if (search) window.Alpine.$data(search).close();
|
||||
if (url.pathname === window.location.pathname) {
|
||||
|
||||
Reference in New Issue
Block a user