Give a docked toolbar M3's rounded large-screen form
Plan step 25, item 8 (navigation Missing: "Toolbar, large-screen treatments"). M3's toolbar guidelines (Adaptive design, Resizing) say it of the docked toolbar: "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. `rounded` does that from expanded (840px): fully rounded, spanning its container, lifted 16px off the window's edges at place="bottom"; below expanded it stays the square full-width bar. An <x-divider vertical /> between groups of controls stands as tall as the icon buttons inside any toolbar. The floating toolbar is already fully rounded and gets no new form. 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
092100c6ae
commit
f33fb8be64
@@ -877,6 +877,18 @@ M3 Expressive toolbar, `role="toolbar"` (arrow keys move between controls). `var
|
|||||||
|
|
||||||
A docked toolbar and a navigation bar occupy the same screen region and must never be on screen together: show the bar on a primary page and the toolbar on a secondary or contextual one. A `place="bottom"` toolbar clears `--material-bottom-bar` if a bar is there anyway, so nothing is buried.
|
A docked toolbar and a navigation bar occupy the same screen region and must never be on screen together: show the bar on a primary page and the toolbar on a secondary or contextual one. A `place="bottom"` toolbar clears `--material-bottom-bar` if a bar is there anyway, so nothing is buried.
|
||||||
|
|
||||||
|
Large screens: `rounded` gives a *docked* toolbar M3's web/large-screen form — from `expanded` (840px) fully rounded and spanning its container (at `place="bottom"` it lifts 16px off the window's edges); below `expanded` it stays the square full-width bar M3 requires. Divide groups of controls with `<x-divider vertical />` (`<x-divider />` in a vertical toolbar); inside a toolbar it stands as tall as the icon buttons. A floating toolbar is fully rounded already and has no large-screen form: M3 lets it show more controls there, or splits the actions into two toolbars at opposite edges.
|
||||||
|
|
||||||
|
```blade
|
||||||
|
<x-toolbar variant="docked" rounded label="Text formatting">
|
||||||
|
<x-button icon="undo" tooltip="Undo" wire:click="undo" />
|
||||||
|
<x-button icon="redo" tooltip="Redo" wire:click="redo" />
|
||||||
|
<x-divider vertical />
|
||||||
|
<x-button icon="format_bold" tooltip="Bold" :selected="$bold" wire:click="toggleBold" />
|
||||||
|
<x-button icon="format_italic" tooltip="Italic" :selected="$italic" wire:click="toggleItalic" />
|
||||||
|
</x-toolbar>
|
||||||
|
```
|
||||||
|
|
||||||
### `<x-tabs>`, `<x-tab>`
|
### `<x-tabs>`, `<x-tab>`
|
||||||
|
|
||||||
M3 tabs with a server-rendered tablist (arrow keys, Home/End, disabled tabs skipped, the indicator moves in a view transition). `tabs`: `['name', 'label', 'icon', 'badge', 'disabled']`; panels are `<x-tab name>` 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.
|
M3 tabs with a server-rendered tablist (arrow keys, Home/End, disabled tabs skipped, the indicator moves in a view transition). `tabs`: `['name', 'label', 'icon', 'badge', 'disabled']`; panels are `<x-tab name>` 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.
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
* Placed over the page, a horizontal toolbar keeps 16dp from the window's edge and a vertical one
|
* 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.
|
* 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
|
* 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
|
* one, on-primary-container in a vibrant one — and they draw their ink as utilities, which a rule
|
||||||
* in any layer loses to.
|
* in any layer loses to.
|
||||||
@@ -90,6 +94,22 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Groups of controls, a divider between each two (`<x-divider vertical />`, or `<x-divider />` 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). */
|
/* A floating toolbar and its FAB, side by side (or stacked, vertical). */
|
||||||
[data-toolbar-group] {
|
[data-toolbar-group] {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -139,3 +159,24 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
translate: none;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,17 @@
|
|||||||
one beside a floating toolbar keeps its own. It is one of the toolbar's controls then, so the
|
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.
|
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 `<x-divider vertical />` between them (in
|
||||||
|
a vertical toolbar, `<x-divider />`): 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
|
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
|
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
|
one. Either way a toolbar placed at `bottom` clears `--material-bottom-bar`, so it is never
|
||||||
@@ -26,12 +37,14 @@
|
|||||||
'vertical' => false,
|
'vertical' => false,
|
||||||
'place' => null,
|
'place' => null,
|
||||||
'label' => null,
|
'label' => null,
|
||||||
|
'rounded' => false,
|
||||||
])
|
])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$variant = $variant === 'docked' ? 'docked' : 'floating';
|
$variant = $variant === 'docked' ? 'docked' : 'floating';
|
||||||
$vertical = $vertical && $variant === 'floating';
|
$vertical = $vertical && $variant === 'floating';
|
||||||
$place = in_array($place, ['bottom', 'end'], true) ? $place : null;
|
$place = in_array($place, ['bottom', 'end'], true) ? $place : null;
|
||||||
|
$rounded = $rounded && $variant === 'docked';
|
||||||
$grouped = isset($fab) && $variant === 'floating';
|
$grouped = isset($fab) && $variant === 'floating';
|
||||||
$docksFab = isset($fab) && $variant === 'docked';
|
$docksFab = isset($fab) && $variant === 'docked';
|
||||||
@endphp
|
@endphp
|
||||||
@@ -48,6 +61,7 @@
|
|||||||
data-variant="{{ $variant }}"
|
data-variant="{{ $variant }}"
|
||||||
@if ($vertical) data-vertical aria-orientation="vertical" @endif
|
@if ($vertical) data-vertical aria-orientation="vertical" @endif
|
||||||
@if ($vibrant && $variant === 'floating') data-vibrant @endif
|
@if ($vibrant && $variant === 'floating') data-vibrant @endif
|
||||||
|
@if ($rounded) data-rounded @endif
|
||||||
@if ($place && ! $grouped) data-toolbar-place="{{ $place }}" @endif
|
@if ($place && ! $grouped) data-toolbar-place="{{ $place }}" @endif
|
||||||
@if (filled($label)) aria-label="{{ $label }}" @endif
|
@if (filled($label)) aria-label="{{ $label }}" @endif
|
||||||
{{ $attributes }}
|
{{ $attributes }}
|
||||||
|
|||||||
@@ -96,6 +96,19 @@
|
|||||||
<x-slot:fab><x-fab icon="add" tooltip="New note" color="secondary" /></x-slot:fab>
|
<x-slot:fab><x-fab icon="add" tooltip="New note" color="secondary" /></x-slot:fab>
|
||||||
</x-toolbar>
|
</x-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- M3's docked toolbar on a large screen: rounded from 840px, its groups of controls divided. Narrower, it is the square docked bar. --}}
|
||||||
|
<div class="w-full overflow-hidden rounded-corner-lg bg-surface expanded:p-4">
|
||||||
|
<x-toolbar variant="docked" rounded label="Text formatting">
|
||||||
|
<x-button icon="undo" tooltip="Undo" />
|
||||||
|
<x-button icon="redo" tooltip="Redo" />
|
||||||
|
<x-divider vertical />
|
||||||
|
<x-button icon="format_bold" tooltip="Bold" :selected="false" />
|
||||||
|
<x-button icon="format_italic" tooltip="Italic" :selected="false" />
|
||||||
|
<x-divider vertical />
|
||||||
|
<x-button icon="link" tooltip="Insert link" />
|
||||||
|
</x-toolbar>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
BLADE,
|
BLADE,
|
||||||
'Primary tabs' => <<<'BLADE'
|
'Primary tabs' => <<<'BLADE'
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// The toolbar's first tests live in AppBarTest.php, beside the app bar they were written with.
|
||||||
|
|
||||||
|
it('gives a docked toolbar the rounded large-screen form on request, and only a docked one', function () {
|
||||||
|
$docked = (string) $this->blade(<<<'BLADE'
|
||||||
|
<x-toolbar variant="docked" rounded label="Formatting">
|
||||||
|
<x-button icon="undo" tooltip="Undo" />
|
||||||
|
<x-divider vertical />
|
||||||
|
<x-button icon="format_bold" tooltip="Bold" />
|
||||||
|
</x-toolbar>
|
||||||
|
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".*<div\s+role="separator" aria-orientation="vertical"[^>]*>\s*<\/div>.*aria-label="Bold"/s')
|
||||||
|
->and((string) $this->blade('<x-toolbar rounded><x-button icon="undo" tooltip="Undo" /></x-toolbar>'))
|
||||||
|
->toContain('data-variant="floating"')
|
||||||
|
->not->toContain('data-rounded')
|
||||||
|
->and((string) $this->blade('<x-toolbar variant="docked"><x-button icon="undo" tooltip="Undo" /></x-toolbar>'))
|
||||||
|
->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;/');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user