Package views now write <x-livewire-material::button>, so a configured prefix (which Blade spells <x-m::button>) and an application component of the same name can no longer break or shadow them. The showcase rewrites its examples to the configured prefix. Adds the README, and waits for the adaptive rail to hear a resize before the navigation tests press its menu button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
92 lines
3.5 KiB
PHP
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-livewire-material::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-livewire-material::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-livewire-material::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>
|