From f30fd575cf1c0aded20a836f4785798aacf1ee3a Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Mon, 14 Sep 2026 05:58:42 +0200 Subject: [PATCH] Draw the search bar and its view as the contained style Expressive deprecates the divided style, so the results lose their divider; the full-screen layout takes its own surface-container-low, one step from the docked one; the docked view opens over a scrim, as M3's variants table says it does; and the bar is bounded at M3's 720px, grows to that width while focused, and rests with the 24px leading and trailing padding the specs table gives an unfocused bar. --search-width sets the resting width for M3's 360px bar. Plan step 20, findings IN-07, IN-08, IN-09, IN-20 and IN-21. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- .../livewire-material-development/SKILL.md | 4 +- resources/css/components/search.css | 60 ++++++++++++++++--- resources/views/components/search.blade.php | 8 ++- .../views/showcase/sections/fields.blade.php | 9 ++- tests/Feature/Components/SearchTest.php | 1 + 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index a7c80ca1..05894475 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -662,7 +662,7 @@ Bind with `wire:model` (entangled) or, without Livewire, `x-model`. The options ### `` -M3 search bar that opens into a search view: docked under the bar from `medium` (600px), full screen with a back arrow on a compact window (`docked` keeps it docked). Bind the input like any other and render the results in the slot; `empty` is shown when the slot renders nothing. Choosing a result (a link or button) closes the view; ArrowDown walks the results, Escape closes. Props: `placeholder` ("Search"), `label`, `icon`; `trailing` slot (avatar, icon buttons). +M3 search bar that opens into a search view: docked under the bar from `medium` (600px) over a scrim, full screen with a back arrow on a compact window (`docked` keeps it docked). Bind the input like any other and render the results in the slot; `empty` is shown when the slot renders nothing. The results are a list and a live region says how many there are. Choosing a result (a link or button) closes the view; ArrowDown walks the results, Escape closes. Props: `placeholder` ("Search"), `label`, `icon`; `trailing` slot (avatar, icon buttons). ```blade @@ -673,7 +673,7 @@ M3 search bar that opens into a search view: docked under the bar from `medium` ``` -The docked view overlaps what is under it; never place a search inside an element with `overflow-hidden` (a card), which clips it. +The docked view overlaps what is under it; never place a search inside an element with `overflow-hidden` (a card), which clips it. The bar is never wider than M3's 720px and grows to that width while it is focused; for M3's 360px resting bar, wrap it in an element carrying `style="--search-width: 22.5rem"`. ### `` diff --git a/resources/css/components/search.css b/resources/css/components/search.css index 04e31a2b..a4196d05 100644 --- a/resources/css/components/search.css +++ b/resources/css/components/search.css @@ -4,12 +4,19 @@ * * The bar is a 56px pill in surface-container-high: a leading search icon, the input in * body-large, a clear button once something is typed, and whatever the caller trails it with (an - * avatar, an icon button). Focus opens the view. On a medium or wider window the view is docked: - * the bar grows down into an extra-large-cornered container at elevation 3 that holds the - * results. On a compact window it takes the whole screen, and the search icon turns into a back - * arrow. `docked` keeps it docked at every width. + * avatar, an icon button). Focus opens the view. On a medium or wider window the view is docked + * over a scrim: the bar grows down into an extra-large-cornered container at elevation 3 that + * holds the results. On a compact window it takes the whole screen, and the search icon turns into + * a back arrow. `docked` keeps it docked at every width. + * + * Widths are M3's: never wider than 720px, and the bar grows to that width while it is focused — + * `--search-width` is its resting width (the room it is given, by default) and + * `--search-open-width` the focused one, so a caller that wants M3's 360px resting bar sets + * `--search-width: 22.5rem` on a wrapper. The leading and trailing padding is 24px unfocused and + * 16px focused, as the search specs table gives it. * * [data-search] the root; data-open, data-full-screen + * [data-search-scrim] over the page while the view is docked * [data-search-bar] the pill, above the view * [data-search-leading], [data-search-field] (the combobox around [data-search-input]), * [data-search-clear], [data-search-trailing] @@ -21,25 +28,56 @@ @layer components { [data-search] { --search-height: 3.5rem; + /* M3: min 360px, max 720px, and wider while focused. */ + --search-width: 100%; + --search-open-width: 45rem; } [data-search][data-open] { z-index: 50; } + /* Docked, the view opens over the page: M3 puts a scrim under it. Behind the bar and the view + inside the root's own stacking context, above everything the page draws outside it. */ + [data-search-scrim] { + position: fixed; + inset: 0; + z-index: -1; + background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); + transition-property: opacity, display; + transition-duration: var(--md-sys-motion-effects-fast-duration); + transition-timing-function: var(--md-sys-motion-effects-fast); + transition-behavior: allow-discrete; + } + + @starting-style { + [data-search-scrim] { + opacity: 0; + } + } + [data-search-bar] { position: relative; z-index: 1; display: flex; align-items: center; gap: 0.25rem; + width: min(100%, var(--search-width)); height: var(--search-height); - padding-inline: 0.25rem; + padding-inline: 0.75rem; border-radius: var(--md-sys-shape-corner-full); background-color: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); cursor: text; - transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast); + transition-property: background-color, width, padding-inline; + transition-duration: var(--md-sys-motion-effects-fast-duration); + transition-timing-function: var(--md-sys-motion-effects-fast); + } + + /* Focused: wider, and the padding drops from 24px to the contained style's 16px. */ + [data-search][data-open] [data-search-bar] { + width: min(100%, var(--search-open-width)); + padding-inline: 0.25rem; } @media (hover: hover) { @@ -143,6 +181,9 @@ top: 0; display: flex; flex-direction: column; + max-width: var(--search-open-width); + /* M3: the docked container is at least 240px tall and at most two thirds of the window. */ + min-height: 15rem; max-height: min(40rem, 70dvh); padding-top: var(--search-height); overflow: hidden; @@ -163,11 +204,12 @@ } } + /* No divider: that belongs to the divided style, which Expressive deprecates in favour of the + contained one this file draws. */ [data-search-results] { min-height: 0; overflow-y: auto; overscroll-behavior: contain; - border-top: 1px solid var(--md-sys-color-outline); padding-block: 0.5rem; } @@ -175,6 +217,7 @@ [data-search][data-full-screen] [data-search-bar] { position: fixed; inset: 0 0 auto; + width: auto; height: calc(4.5rem + var(--material-safe-top, env(safe-area-inset-top))); padding-top: var(--material-safe-top, env(safe-area-inset-top)); padding-inline: 0.25rem; @@ -184,8 +227,11 @@ [data-search][data-full-screen] [data-search-view] { position: fixed; inset: 0; + max-width: none; max-height: none; padding-top: calc(4.5rem + var(--material-safe-top, env(safe-area-inset-top))); + /* The full-screen layout has its own container role, one step from the docked one. */ + background-color: var(--md-sys-color-surface-container-low); border-radius: 0; box-shadow: none; } diff --git a/resources/views/components/search.blade.php b/resources/views/components/search.blade.php index fc7ca864..10605dc1 100644 --- a/resources/views/components/search.blade.php +++ b/resources/views/components/search.blade.php @@ -5,8 +5,10 @@ `empty` is shown instead when the slot renders nothing (say, "No shares match"). Results are usually ``s with a `link`, or buttons: choosing one closes the view. - Docked under the bar from `medium`, full screen below it with a back arrow (resources/css/ - components/search.css); `docked` keeps it docked at every width. `placeholder` ("Search"), + Docked under the bar from `medium` over a scrim, full screen below it with a back arrow + (resources/css/components/search.css); `docked` keeps it docked at every width. The bar is + never wider than M3's 720px and grows to that width while it is focused: set `--search-width` + on a wrapper for a narrower resting bar (M3's own is 360px). `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 ``. @@ -46,6 +48,8 @@ data-search {{ $attributes->only(['class', 'wire:key'])->class(['relative']) }} > + +
diff --git a/resources/views/showcase/sections/fields.blade.php b/resources/views/showcase/sections/fields.blade.php index 281433b3..ae1517c0 100644 --- a/resources/views/showcase/sections/fields.blade.php +++ b/resources/views/showcase/sections/fields.blade.php @@ -126,9 +126,12 @@
- - Type to search your settings. - + {{-- M3's resting bar is 360px and grows to 720 while focused; --search-width sets the resting one. --}} +
+ + Type to search your settings. + +
BLADE, 'A form' => <<<'BLADE' diff --git a/tests/Feature/Components/SearchTest.php b/tests/Feature/Components/SearchTest.php index db5aad10..79a642a5 100644 --- a/tests/Feature/Components/SearchTest.php +++ b/tests/Feature/Components/SearchTest.php @@ -16,6 +16,7 @@ it('draws a search bar whose combobox controls the view', function () { ->toContain('wire:model.live.debounce.300ms="query"') ->toContain('id="find-view"') ->toContain('data-search-clear') + ->toContain('data-search-scrim') ->toContain('aria-label="Back"') ->toContain('materialSearch(false,') ->not->toContain('data-search-results');