Files
livewire-material/resources/views/components/search.blade.php
T
Andreas Reinhold / reiniandClaude Opus 5 0fee2a0952
tests / lint (push) Successful in 1m4s
tests / feature (8.4) (push) Successful in 1m8s
tests / feature (8.5) (push) Successful in 1m8s
tests / browser (chrome, chromium) (push) Successful in 3m6s
tests / browser (firefox, firefox) (push) Successful in 3m45s
tests / browser (safari, webkit) (push) Successful in 5m0s
Add chips, choices and search
M3's assist, filter, input and suggestion chips with chip sets; choices
as filter chips or a searchable combobox whose list is an anchored
popover; and the search bar that opens into a docked or full-screen
search view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 08:07:59 +02:00

92 lines
3.5 KiB
PHP

{{-- M3's search: a search bar that opens into a search view with the results.
Bind the input like any other (`wire:model.live.debounce.300ms="query"`) and render the
results in the slot, from a Livewire property or computed property that follows the query;
`empty` is shown instead when the slot renders nothing (say, "No shares match"). Results are
usually `<x-list-item>`s with a `link`, or buttons: choosing one closes the view.
Docked under the bar from `sm`, full screen below it with a back arrow (resources/css/
components/search.css); `docked` keeps it docked at every width. `placeholder` ("Search"),
`label` (the input's name when it differs from the placeholder), leading `icon` (`search`), and
a `trailing` slot for an avatar or icon buttons in the bar. Every other attribute reaches the
`<input type="search">`. --}}
@props([
'placeholder' => null,
'label' => null,
'icon' => 'search',
'docked' => false,
])
@php
$model = $attributes->wire('model')->value() ?: null;
$placeholder ??= __('Search');
$id = $attributes->get('id') ?? 'material-search-'.substr(md5($model.'|'.$placeholder), 0, 10);
@endphp
<div
x-data="materialSearch({{ $docked ? 'true' : 'false' }})"
x-on:keydown.escape="if (open) { $event.stopPropagation(); close(true); }"
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
{{ $attributes->only(['class', 'wire:key'])->class(['relative']) }}
>
<div data-search-bar role="search" x-on:click="if ($event.target === $el) $refs.input.focus()">
<span data-search-leading x-show="! fullScreen">
<x-icon :name="$icon" />
</span>
<button type="button" data-search-leading data-search-back x-show="fullScreen" x-cloak x-on:click="close()" aria-label="{{ __('Back') }}">
<x-icon name="arrow_back" />
</button>
<input
{{ $attributes->except(['class', 'wire:key', 'id', 'placeholder', 'type']) }}
x-ref="input"
id="{{ $id }}"
type="search"
autocomplete="off"
enterkeyhint="search"
placeholder="{{ $placeholder }}"
aria-label="{{ $label ?? $placeholder }}"
aria-controls="{{ $id }}-view"
aria-expanded="false"
x-bind:aria-expanded="open.toString()"
x-on:focus="focused()"
x-on:click="show()"
x-on:input="show()"
x-on:keydown.arrow-down.prevent="show(); $nextTick(() => step(1))"
data-search-input
/>
<button type="button" data-search-clear x-on:click="clear()" aria-label="{{ __('Clear') }}">
<x-icon name="close" />
</button>
@isset($trailing)
<span data-search-trailing>{{ $trailing }}</span>
@endisset
</div>
<div
x-ref="view"
id="{{ $id }}-view"
data-search-view
x-cloak
x-show="open"
x-on:keydown.arrow-down.prevent="step(1)"
x-on:keydown.arrow-up.prevent="step(-1)"
x-on:click="choose($event)"
>
@if ($slot->hasActualContent())
<div data-search-results>{{ $slot }}</div>
@elseif (isset($empty))
<div data-search-results><p class="px-4 py-3 type-body-md text-on-surface-variant">{{ $empty }}</p></div>
@endif
</div>
</div>