` in the slot. Bind with `wire:model` (entangled), or `selected` / `x-model` without Livewire. `variant` `primary` (default) or `secondary`; `stacked` (icon over label), `scrollable`. Give two identical tab sets on one page distinct `id`s.
diff --git a/resources/css/components/toolbar.css b/resources/css/components/toolbar.css
index d523c17f..14e95ab2 100644
--- a/resources/css/components/toolbar.css
+++ b/resources/css/components/toolbar.css
@@ -11,6 +11,10 @@
* Placed over the page, a horizontal toolbar keeps 16dp from the window's edge and a vertical one
* 24dp, which is M3's minimum for each.
*
+ * On web and large screens a docked toolbar "can be rounded", and dividers can organise its
+ * controls into groups (docs/reference/m3/components-navigation-selection-inputs.md § Toolbars):
+ * `rounded` does the first from `expanded`, and a divider in the slot the second at any width.
+ *
* Unlayered on purpose: a toolbar recolours the icon buttons inside it — primary in a standard
* one, on-primary-container in a vibrant one — and they draw their ink as utilities, which a rule
* in any layer loses to.
@@ -90,6 +94,22 @@
box-shadow: none;
}
+/* Groups of controls, a divider between each two (``, or `` in a
+ vertical toolbar). The divider draws itself stretched across its row, which in a toolbar would
+ run it into the container's edges; here it stands as long as the 40px icon buttons it separates,
+ centred on them, as the large-screen docked toolbar in M3's guidelines draws it
+ (toolbars/guidelines § Adaptive design → Resizing). The divider is `self-stretch`, a utility,
+ which this unlayered rule beats. */
+[data-toolbar]:not([data-vertical]) > [role="separator"][aria-orientation="vertical"] {
+ align-self: center;
+ height: 2.5rem;
+}
+
+[data-toolbar][data-vertical] > [role="separator"][aria-orientation="horizontal"] {
+ align-self: center;
+ width: 2.5rem;
+}
+
/* A floating toolbar and its FAB, side by side (or stacked, vertical). */
[data-toolbar-group] {
display: inline-flex;
@@ -139,3 +159,24 @@
left: 0;
translate: none;
}
+
+/* The large-screen docked toolbar: from `expanded` (840px) fully rounded, like the one M3 shows on
+ the web, and spanning what it is written in. At the bottom of the window it lifts off the edges —
+ 16dp from each side, M3's minimum outside padding for a toolbar, and 16px above the bottom bar or
+ the safe area, as a floating toolbar sits — so the curve never meets the window's frame, and the
+ bottom safe area, which it no longer touches, stops padding it. Below `expanded` it is the square,
+ full-width docked toolbar M3 requires ("Avoid applying rounded corners to the container"). */
+@media (width >= 52.5rem) {
+ [data-toolbar][data-variant="docked"][data-rounded] {
+ min-height: 4rem;
+ padding-bottom: 0;
+ border-radius: var(--md-sys-shape-corner-full);
+ }
+
+ [data-toolbar][data-variant="docked"][data-rounded][data-toolbar-place="bottom"] {
+ right: calc(1rem + var(--material-safe-right, env(safe-area-inset-right)));
+ bottom: calc(max(var(--material-bottom-bar, 0px), var(--material-safe-bottom, env(safe-area-inset-bottom))) + 1rem);
+ left: calc(1rem + var(--material-safe-left, env(safe-area-inset-left)));
+ width: auto;
+ }
+}
diff --git a/resources/views/components/toolbar.blade.php b/resources/views/components/toolbar.blade.php
index d4af1c60..8afb5592 100644
--- a/resources/views/components/toolbar.blade.php
+++ b/resources/views/components/toolbar.blade.php
@@ -11,6 +11,17 @@
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.
+ `rounded` gives a docked toolbar the form M3 lets it take on web and large screens: "the docked
+ toolbar can be rounded. Dividers can be used to organize large amounts of items", and it "can be
+ … placed in different parts of the page" (docs/reference/m3/components-navigation-selection-inputs.md
+ § Toolbars → Behaviour and guidelines; toolbars/guidelines § Adaptive design → Resizing). From `expanded`
+ (840px) it is a fully rounded bar that spans what it is written in, and at `place="bottom"` it
+ lifts clear of the window's edges; below `expanded` it is the square, full-width docked toolbar,
+ as M3 requires there. Separate groups of controls with `` between them (in
+ a vertical toolbar, ``): inside a toolbar it stands as tall as an icon button rather
+ than across the whole bar. The floating toolbar has no such form — it is fully rounded already,
+ and M3's large-screen advice for it is to show more controls, or two toolbars at opposite edges.
+
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
one. Either way a toolbar placed at `bottom` clears `--material-bottom-bar`, so it is never
@@ -26,12 +37,14 @@
'vertical' => false,
'place' => null,
'label' => null,
+ 'rounded' => false,
])
@php
$variant = $variant === 'docked' ? 'docked' : 'floating';
$vertical = $vertical && $variant === 'floating';
$place = in_array($place, ['bottom', 'end'], true) ? $place : null;
+ $rounded = $rounded && $variant === 'docked';
$grouped = isset($fab) && $variant === 'floating';
$docksFab = isset($fab) && $variant === 'docked';
@endphp
@@ -48,6 +61,7 @@
data-variant="{{ $variant }}"
@if ($vertical) data-vertical aria-orientation="vertical" @endif
@if ($vibrant && $variant === 'floating') data-vibrant @endif
+ @if ($rounded) data-rounded @endif
@if ($place && ! $grouped) data-toolbar-place="{{ $place }}" @endif
@if (filled($label)) aria-label="{{ $label }}" @endif
{{ $attributes }}
diff --git a/resources/views/showcase/sections/bars.blade.php b/resources/views/showcase/sections/bars.blade.php
index 668baf6c..5ead9279 100644
--- a/resources/views/showcase/sections/bars.blade.php
+++ b/resources/views/showcase/sections/bars.blade.php
@@ -96,6 +96,19 @@
+
+ {{-- M3's docked toolbar on a large screen: rounded from 840px, its groups of controls divided. Narrower, it is the square docked bar. --}}
+
+
+
+
+
+
+
+
+
+
+
BLADE,
'Primary tabs' => <<<'BLADE'
diff --git a/tests/Feature/Components/ToolbarTest.php b/tests/Feature/Components/ToolbarTest.php
new file mode 100644
index 00000000..8bd2e268
--- /dev/null
+++ b/tests/Feature/Components/ToolbarTest.php
@@ -0,0 +1,47 @@
+blade(<<<'BLADE'
+
+
+
+
+
+ BLADE);
+
+ expect($docked)
+ ->toMatch('/role="toolbar"[^>]*data-variant="docked"\s+data-rounded/')
+ // The divider between two groups is the toolbar's child, where toolbar.css stands it up.
+ ->toMatch('/role="toolbar".*aria-label="Undo".*]*>\s*<\/div>.*aria-label="Bold"/s')
+ ->and((string) $this->blade(''))
+ ->toContain('data-variant="floating"')
+ ->not->toContain('data-rounded')
+ ->and((string) $this->blade(''))
+ ->not->toContain('data-rounded');
+});
+
+it('rounds a docked toolbar from expanded only, and lifts a placed one off the window\'s edges', function () {
+ $css = file_get_contents(__DIR__.'/../../../resources/css/components/toolbar.css');
+
+ preg_match('/@media \(width >= 52\.5rem\) \{(.*)\}\s*$/s', $css, $expanded);
+
+ expect($expanded[1] ?? '')
+ // "On web and large screens, the docked toolbar can be rounded" (toolbars/guidelines).
+ ->toMatch('/\[data-toolbar\]\[data-variant="docked"\]\[data-rounded\] \{[^}]*border-radius: var\(--md-sys-shape-corner-full\);/')
+ ->toMatch('/\[data-toolbar\]\[data-variant="docked"\]\[data-rounded\] \{[^}]*padding-bottom: 0;/')
+ // 16dp from each side, M3's minimum outside padding; 16px above a bottom bar.
+ ->toMatch('/\[data-rounded\]\[data-toolbar-place="bottom"\] \{[^}]*right: calc\(1rem \+ var\(--material-safe-right, env\(safe-area-inset-right\)\)\);/')
+ ->toMatch('/\[data-rounded\]\[data-toolbar-place="bottom"\] \{[^}]*left: calc\(1rem \+ var\(--material-safe-left, env\(safe-area-inset-left\)\)\);/')
+ ->toMatch('/\[data-rounded\]\[data-toolbar-place="bottom"\] \{[^}]*bottom: calc\(max\(var\(--material-bottom-bar, 0px\), var\(--material-safe-bottom, env\(safe-area-inset-bottom\)\)\) \+ 1rem\);/')
+ // Nothing outside the query rounds a docked toolbar: below expanded it stays square.
+ ->and(str_replace($expanded[0] ?? '', '', $css))
+ ->not->toContain('[data-rounded]');
+});
+
+it('stands a divider in a toolbar as tall as the buttons it separates', function () {
+ expect(file_get_contents(__DIR__.'/../../../resources/css/components/toolbar.css'))
+ ->toMatch('/\[data-toolbar\]:not\(\[data-vertical\]\) > \[role="separator"\]\[aria-orientation="vertical"\] \{\s+align-self: center;\s+height: 2\.5rem;/')
+ ->toMatch('/\[data-toolbar\]\[data-vertical\] > \[role="separator"\]\[aria-orientation="horizontal"\] \{\s+align-self: center;\s+width: 2\.5rem;/');
+});