Draw the toolbar without Tailwind

Plan step 36 (navigation group, second stream): <x-toolbar>'s class
lists move into resources/css/components/toolbar.css, keyed on
data-md-toolbar (data-md-variant/-vertical/-vibrant/-rounded/
-toolbar-place) and data-md-toolbar-group/-toolbar-fab. The docked
form with a FAB (N-02's --material-bottom-bar clearance), the vertical
24dp margin (N-12), the standard toolbar's primary icon buttons
(N-13), and the rounded large-screen form with dividers from 840px
were all already correct; only their hooks, units and layer needed to
change. The file no longer sits outside every layer: button.css and
fab.css now draw their own ink through data-md-* attributes rather
than a Tailwind utility, so a toolbar's more specific selector always
beats their single base color/background-color/box-shadow declaration
without needing to escape the cascade layers.

Sizes stay px (64px row, 40px divider); spacing goes through the
measurement tokens where one matches the value (4px leading/trailing
gap, 16/32px docked spread, 8px floating ends, 16/24px placed
margins).

Hooks renamed: every unprefixed data-toolbar* attribute to data-md-*,
updated in tests/Feature/Components/{AppBarTest,ToolbarTest}.php and
tests/Browser/BarsTest.php. toolbar.js needed no change: it reads the
bar through $root and role="toolbar", not by hook name.

Carry-over from the app-bar step: layout/pane.css now imports
app-bar.css and button.css for the pane's own top app bar and back
button, now that both are rewritten. That import crosses into
material.components for the first time from a layout file, so
tests/Feature/StylesheetsTest.php's two layout-shape checks now skip
non-`layout/` files reached through it — each already covered by its
own group's stylesheet test.

Added the owed browser tests (docs/plans/material-3-browser-tests.md):
a docked toolbar clear of the navigation bar below medium, and the
rounded form with its divider height from 840px, clear of the
window's edges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 23:27:26 +02:00
co-authored by Claude Sonnet 5
parent b7641a35c2
commit 9c134b2dc7
10 changed files with 322 additions and 217 deletions
+25 -20
View File
@@ -136,20 +136,20 @@ it('draws floating and docked toolbars', function () {
BLADE);
expect($html)
->toContain('<div data-toolbar-group data-vertical data-toolbar-place="end" >')
->toContain('<div data-md-toolbar-group data-md-vertical data-md-toolbar-place="end" >')
->toContain('role="toolbar"')
->toContain('materialToolbar(true)')
->toContain('data-variant="floating"')
->toContain('data-md-variant="floating"')
->toContain('aria-orientation="vertical"')
->toContain('data-vibrant')
->toContain('data-md-vibrant')
->toContain('aria-label="Formatting"')
->toContain('<button>+</button>')
->and((string) $this->blade('<x-toolbar variant="docked" vibrant vertical place="bottom"><button>B</button></x-toolbar>'))
->toContain('data-variant="docked"')
->not->toContain('data-vibrant')
->not->toContain('data-vertical')
->toContain('data-toolbar-place="bottom"')
->not->toContain('data-toolbar-group');
->toContain('data-md-variant="docked"')
->not->toContain('data-md-vibrant')
->not->toContain('data-md-vertical')
->toContain('data-md-toolbar-place="bottom"')
->not->toContain('data-md-toolbar-group');
});
it('sets a FAB at the end of a docked toolbar, resting flat on it', function () {
@@ -162,15 +162,17 @@ it('sets a FAB at the end of a docked toolbar, resting flat on it', function ()
expect($html)
// Inside the toolbar, after its controls, where the arrow keys reach it too.
->toMatch('/role="toolbar".*<button>Draw<\/button>\s*<div data-toolbar-fab><button>\+<\/button><\/div>\s*<\/div>/s')
->not->toContain('data-toolbar-group')
->toMatch('/role="toolbar".*<button>Draw<\/button>\s*<div data-md-toolbar-fab><button>\+<\/button><\/div>\s*<\/div>/s')
->not->toContain('data-md-toolbar-group')
->and((string) $this->blade('<x-toolbar variant="docked"><button>Draw</button></x-toolbar>'))
->not->toContain('data-toolbar-fab');
->not->toContain('data-md-toolbar-fab');
expect(file_get_contents(__DIR__.'/../../../resources/css/components/toolbar.css'))
->toMatch('/\[data-toolbar-fab\] \{\s+display: flex;\s+margin-inline-start: auto;/')
$css = ComponentStylesheet::read('toolbar');
expect($css->declarations('[data-md-toolbar-fab]'))->toBe(['display' => 'flex', 'margin-inline-start' => 'auto'])
// A nested FAB rests at elevation 0.
->toMatch('/\[data-toolbar-fab\] \[data-md-fab\],\s+\[data-toolbar-fab\] \[data-md-fab\]:hover \{\s+box-shadow: none;/');
->and($css->declarations('[data-md-toolbar-fab] [data-md-fab], [data-md-toolbar-fab] [data-md-fab]:hover'))
->toBe(['box-shadow' => 'none']);
});
it('centres a headline on a three-column row and curves the search container', function () {
@@ -199,16 +201,19 @@ it('collapses a medium or large bar into its row at the breakpoint its title col
});
it('keeps a toolbar at the bottom clear of the navigation bar', function () {
$css = file_get_contents(__DIR__.'/../../../resources/css/components/toolbar.css');
$css = ComponentStylesheet::read('toolbar');
expect($css)
expect($css->declarations('[data-md-toolbar-place=\'bottom\']'))
// Never a sum: --material-bottom-bar already swallows the bottom safe area (N-02).
->toContain('bottom: calc(max(var(--material-bottom-bar, 0px), var(--material-safe-bottom, env(safe-area-inset-bottom))) + 1rem);')
->toContain('bottom: var(--material-bottom-bar, 0px);')
->toHaveKey('bottom', 'calc(max(var(--material-bottom-bar, 0px), var(--material-safe-bottom, env(safe-area-inset-bottom))) + var(--md-sys-measurement-space200))')
->and($css->declarations('[data-md-toolbar][data-md-variant=\'docked\'][data-md-toolbar-place=\'bottom\']'))
->toHaveKey('bottom', 'var(--material-bottom-bar, 0px)')
// A vertical toolbar keeps 24dp from the edge where a horizontal one keeps 16 (N-12).
->toContain('inset-inline-end: calc(1.5rem + var(--material-safe-right, env(safe-area-inset-right)));')
->and($css->declarations('[data-md-toolbar][data-md-vertical][data-md-toolbar-place=\'end\'], [data-md-toolbar-group][data-md-vertical][data-md-toolbar-place=\'end\']'))
->toBe(['inset-inline-end' => 'calc(var(--md-sys-measurement-space300) + var(--material-safe-right, env(safe-area-inset-right)))'])
// A standard toolbar's standard buttons are primary (N-13).
->toContain('[data-toolbar]:not([data-vibrant]) [data-md-icon-button]:not([aria-pressed="true"]) {');
->and($css->declarations('[data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]:not([aria-pressed=\'true\'])'))
->toBe(['color' => 'var(--md-sys-color-primary)']);
});
it('offers M3\'s three contrast levels, marking the one in force', function () {