diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index ecdf9984..38431f7b 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -787,7 +787,7 @@ A collapsing bar needs the window to scroll: no ancestor with `overflow-hidden`/ ### `` -M3 Expressive toolbar, `role="toolbar"` (arrow keys move between controls). `variant`: `floating` (default pill at elevation 3; `vibrant`, `vertical`) or `docked` (full-width surface-container bar). `place`: `bottom` or `end` to fix it over the page; `fab` slot sets a FAB beside a floating toolbar; `label` names it. +M3 Expressive toolbar, `role="toolbar"` (arrow keys move between controls). `variant`: `floating` (default pill at elevation 3; `vibrant`, `vertical`) or `docked` (full-width surface-container bar). `place`: `bottom` or `end` to fix it over the page; `fab` slot takes an `` — beside a floating toolbar, or at the end of a docked one, where the controls gather at the start and the FAB rests flat on the bar (M3's elevation 0 for a nested FAB) and the arrow keys reach it; `label` names it. ```blade diff --git a/resources/css/components/toolbar.css b/resources/css/components/toolbar.css index d53903a3..d523c17f 100644 --- a/resources/css/components/toolbar.css +++ b/resources/css/components/toolbar.css @@ -3,7 +3,8 @@ * Apache-2.0). * * docked a 64px bar across the bottom of the screen in surface-container, square, its - * controls spread out 4 to 32px apart between 16px ends + * controls spread out 4 to 32px apart between 16px ends; with a FAB, the controls + * at the start and the FAB flat at the end * floating a 64px pill in surface-container (or primary-container, `vibrant`) at elevation 3, * 8px at its ends and 4px between controls; `vertical` stands it on end * @@ -71,6 +72,24 @@ color: var(--md-sys-color-primary); } +/* A docked toolbar with a FAB: the controls at the start, the FAB at the end, resting on the bar + at elevation 0 — M3 puts a FAB nested in another component there, and Compose's + FloatingActionButtonDefaults.bottomAppBarFabElevation() is 0 in every state. `` draws its + shadow with utilities, which is one more reason this file is unlayered. */ +[data-toolbar][data-variant="docked"]:has(> [data-toolbar-fab]) { + justify-content: flex-start; +} + +[data-toolbar-fab] { + display: flex; + margin-inline-start: auto; +} + +[data-toolbar-fab] [data-fab], +[data-toolbar-fab] [data-fab]:hover { + box-shadow: none; +} + /* A floating toolbar and its FAB, side by side (or stacked, vertical). */ [data-toolbar-group] { display: inline-flex; diff --git a/resources/views/components/toolbar.blade.php b/resources/views/components/toolbar.blade.php index 61b6ecd0..d4af1c60 100644 --- a/resources/views/components/toolbar.blade.php +++ b/resources/views/components/toolbar.blade.php @@ -4,8 +4,12 @@ `vertical` stands it on end) or `docked` (a full-width bar in surface-container, for the bottom of a screen). `place` puts it over the page: `bottom` (centred above the bottom edge; a docked toolbar spans it) or `end` (centred against the end edge, for a vertical one); without it the - toolbar sits where it is written. A `fab` slot sets an `` beside a floating toolbar. - `label` names it for screen readers. + toolbar sits where it is written. A `fab` slot sets an `` beside a floating toolbar, + and at the end of a docked one — M3's "With FAB" configuration for both. On a docked toolbar the + controls then gather at the start and the FAB rests on the bar, flat: M3 gives a FAB nested in + another component elevation 0 (Compose's bottomAppBarFabElevation is 0 at every state), where + one beside a floating toolbar keeps its own. It is one of the toolbar's controls then, so the + arrow keys reach it. `label` names it for screen readers. A docked toolbar and a navigation bar occupy the same region of the screen and M3 says never to show both at once: the bar belongs on a primary page, the toolbar on a secondary or contextual @@ -29,6 +33,7 @@ $vertical = $vertical && $variant === 'floating'; $place = in_array($place, ['bottom', 'end'], true) ? $place : null; $grouped = isset($fab) && $variant === 'floating'; + $docksFab = isset($fab) && $variant === 'docked'; @endphp @if ($grouped) @@ -48,6 +53,10 @@ {{ $attributes }} > {{ $slot }} + + @if ($docksFab) +
{{ $fab }}
+ @endif @if ($grouped) diff --git a/resources/views/showcase/sections/bars.blade.php b/resources/views/showcase/sections/bars.blade.php index 81467dd5..a0ac1557 100644 --- a/resources/views/showcase/sections/bars.blade.php +++ b/resources/views/showcase/sections/bars.blade.php @@ -73,6 +73,16 @@
+ +
+ + + + + + + +
BLADE, 'Primary tabs' => <<<'BLADE' diff --git a/tests/Feature/Components/AppBarTest.php b/tests/Feature/Components/AppBarTest.php index 369d254e..ca15940a 100644 --- a/tests/Feature/Components/AppBarTest.php +++ b/tests/Feature/Components/AppBarTest.php @@ -69,6 +69,27 @@ it('draws floating and docked toolbars', function () { ->not->toContain('data-toolbar-group'); }); +it('sets a FAB at the end of a docked toolbar, resting flat on it', function () { + $html = (string) $this->blade(<<<'BLADE' + + + + + BLADE); + + expect($html) + // Inside the toolbar, after its controls, where the arrow keys reach it too. + ->toMatch('/role="toolbar".*
')) + ->not->toContain('data-toolbar-fab'); + + expect(file_get_contents(__DIR__.'/../../../resources/css/components/toolbar.css')) + ->toMatch('/\[data-toolbar-fab\] \{\s+display: flex;\s+margin-inline-start: auto;/') + // A nested FAB rests at elevation 0. + ->toMatch('/\[data-toolbar-fab\] \[data-fab\],\s+\[data-toolbar-fab\] \[data-fab\]:hover \{\s+box-shadow: none;/'); +}); + it('centres a headline on a three-column row and curves the search container', function () { $css = file_get_contents(__DIR__.'/../../../resources/css/components/app-bar.css');