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
@@ -4,7 +4,10 @@ it('draws a search bar whose combobox controls the view', function () {
|
||||
$html = (string) $this->blade('<x-search id="find" placeholder="Search shares" wire:model.live.debounce.300ms="query" />');
|
||||
|
||||
expect($html)
|
||||
->toContain('data-search')
|
||||
->toContain('data-md-search')
|
||||
->toContain('data-md-trigger="bar"')
|
||||
->toContain('x-bind:data-md-open="open ? \'\' : null"')
|
||||
->toContain('x-bind:data-md-full-screen="fullScreen ? \'\' : null"')
|
||||
->toContain('role="search"')
|
||||
->toContain('type="search"')
|
||||
->toContain('placeholder="Search shares"')
|
||||
@@ -15,14 +18,15 @@ it('draws a search bar whose combobox controls the view', function () {
|
||||
->toContain('aria-expanded="false"')
|
||||
->toContain('wire:model.live.debounce.300ms="query"')
|
||||
->toContain('id="find-view"')
|
||||
->toContain('data-search-clear')
|
||||
->toContain('data-search-scrim')
|
||||
->toContain('data-md-search-clear')
|
||||
->toContain('data-md-search-scrim')
|
||||
->toContain('aria-label="Back"')
|
||||
->toContain('materialSearch(false,')
|
||||
->not->toContain('data-search-results');
|
||||
->not->toContain('data-md-search-results')
|
||||
->not->toContain('class="relative"');
|
||||
|
||||
// The state belongs to the combobox, never to the bare textbox ARIA gives it to.
|
||||
expect($html)->toMatch('/<span\s+data-search-field[^>]*aria-expanded/s');
|
||||
expect($html)->toMatch('/<span\s+data-md-search-field[^>]*aria-expanded/s');
|
||||
expect($html)->not->toMatch('/<input[^>]*aria-expanded/s');
|
||||
});
|
||||
|
||||
@@ -30,7 +34,7 @@ it('announces how many results there are, politely', function () {
|
||||
$html = (string) $this->blade('<x-search />');
|
||||
|
||||
expect($html)
|
||||
->toContain('data-search-status')
|
||||
->toContain('data-md-search-status class="md-visually-hidden"')
|
||||
->toContain('aria-live="polite"')
|
||||
->toContain('aria-atomic="true"')
|
||||
->toContain('No results')
|
||||
@@ -40,19 +44,19 @@ it('announces how many results there are, politely', function () {
|
||||
|
||||
it('shows the results as a list, or what to say when there are none', function () {
|
||||
expect((string) $this->blade('<x-search><a href="/shares/1">holiday.zip</a></x-search>'))
|
||||
->toContain('data-search-results role="list"')
|
||||
->toContain('data-md-search-results role="list"')
|
||||
->toContain('<a href="/shares/1">holiday.zip</a>')
|
||||
->and((string) $this->blade('<x-search><x-slot:empty>No shares match.</x-slot:empty></x-search>'))
|
||||
->toContain('No shares match.')
|
||||
->not->toContain('data-search-results role="list"');
|
||||
->toContain('<p data-md-search-empty>No shares match.</p>')
|
||||
->not->toContain('data-md-search-results role="list"');
|
||||
});
|
||||
|
||||
it('offers M3\'s search-icon entry point', function () {
|
||||
$html = (string) $this->blade('<x-search id="find" trigger="icon" label="Search shares" />');
|
||||
|
||||
expect($html)
|
||||
->toContain('data-trigger="icon"')
|
||||
->toContain('data-search-trigger')
|
||||
->toContain('data-md-trigger="icon"')
|
||||
->toContain('data-md-search-trigger')
|
||||
->toContain('x-ref="trigger"')
|
||||
->toContain('x-on:click="expand()"')
|
||||
->toContain('aria-label="Search shares"')
|
||||
@@ -61,11 +65,11 @@ it('offers M3\'s search-icon entry point', function () {
|
||||
->toContain('materialSearch(false,')
|
||||
->toContain("'icon'")
|
||||
// The bar is still there: it becomes the expanded view's header.
|
||||
->toContain('data-search-bar');
|
||||
->toContain('data-md-search-bar');
|
||||
|
||||
expect((string) $this->blade('<x-search />'))
|
||||
->toContain('data-trigger="bar"')
|
||||
->not->toContain('data-search-trigger');
|
||||
->toContain('data-md-trigger="bar"')
|
||||
->not->toContain('data-md-search-trigger');
|
||||
});
|
||||
|
||||
it('shows suggestions until the first keystroke, then the results', function () {
|
||||
@@ -77,7 +81,7 @@ it('shows suggestions until the first keystroke, then the results', function ()
|
||||
BLADE);
|
||||
|
||||
expect($html)
|
||||
->toContain('data-search-suggestions role="list"')
|
||||
->toContain('data-md-search-suggestions role="list"')
|
||||
->toContain('x-show="query === \'\'"')
|
||||
->toContain('Recent shares')
|
||||
->toContain('x-show="query !== \'\'"')
|
||||
@@ -87,7 +91,7 @@ it('shows suggestions until the first keystroke, then the results', function ()
|
||||
|
||||
// Without the slot the results stand alone and are never hidden.
|
||||
expect((string) $this->blade('<x-search><a href="/shares/1">holiday.zip</a></x-search>'))
|
||||
->not->toContain('data-search-suggestions')
|
||||
->not->toContain('data-md-search-suggestions')
|
||||
->not->toContain('x-show="query !== \'\'"');
|
||||
});
|
||||
|
||||
@@ -97,7 +101,19 @@ it('names the input, trails the bar, and stays docked on request', function () {
|
||||
expect($html)
|
||||
->toContain('placeholder="Search"')
|
||||
->toContain('aria-label="Search your shares"')
|
||||
->toContain('data-search-trailing')
|
||||
->toContain('data-md-search-trailing')
|
||||
->toContain('<button>AR</button>')
|
||||
->toContain('materialSearch(true,');
|
||||
});
|
||||
|
||||
it('draws the bar and the view from its stylesheet, at M3\'s widths', function () {
|
||||
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/search.css');
|
||||
|
||||
expect($css)->toContain('@layer material.components')
|
||||
->toMatch('/\\[data-md-search\\] \\{\\s*--search-height: 56px;[^}]*--search-open-width: 720px;/')
|
||||
->toContain('[data-md-search][data-md-full-screen] [data-md-search-view] {')
|
||||
->toContain("[data-md-search][data-md-trigger='icon']:not([data-md-open]) [data-md-search-bar] {")
|
||||
->not->toMatch('/\\d(?:\\.\\d+)?rem/')
|
||||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/search.css';")
|
||||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('search.css');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user