Plan step 36 (navigation group, first stream): <x-app-bar>'s class
lists move into resources/css/components/app-bar.css, keyed on
data-md-app-bar (with data-md-variant/-sticky/-subtitled/-scrolled/
-collapsed) and its parts data-md-app-bar-row/-leading/-headline/
-title/-subtitle/-search/-trailing/-action/-overflow/-expanded. The
overflow menu (two icon buttons below 600px, four from it, N-09's
312px search cap and N-11's three-column centred row) already matched
the audit; only its hooks, units and layer needed to change. Every
length is px, spacing through the measurement tokens where one
matches (M3's own "4dp leading/trailing space" and "16dp" headline
inset had no exact token, so those stay literal). Imports button.css,
menu.css and menu-item.css for the overflow menu the view renders.
Hooks renamed: every unprefixed data-app-bar-* attribute and
data-scrolled/data-collapsed to data-md-*, updated in
tests/Feature/Components/{AppBarTest,PaneTest}.php and
tests/Browser/{BarsTest,NavigationTest}.php. app-bar.js needed no
change: it reads the bar through $root, not by hook name.
tests/Feature/Components/NavigationStylesheetsTest.php is the
navigation twin of ContainmentStylesheetsTest.php, on the same
tests/Support/ViewClasses.php rule; its dataset starts at 'app-bar'
and a 'tabs' entry (once that commit lands) will read both
tabs.blade.php and tab.blade.php, since they share one stylesheet.
Added the owed browser tests (docs/plans/material-3-browser-tests.md):
the overflow at 599/600px, keyboard in its menu, a menu closing once
its width no longer shows it, and a wire:click action from both the
icon button and the menu item form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
92 lines
4.8 KiB
PHP
92 lines
4.8 KiB
PHP
<?php
|
|
|
|
it('draws a content region with a body and no app bar when it has no title', function () {
|
|
$html = (string) $this->blade('<x-pane>The page</x-pane>');
|
|
|
|
expect(layoutRoot($html))->toMatchArray(['<' => 'div', 'data-md-pane' => 'data-md-pane'])
|
|
->not->toHaveKeys(['data-md-width', 'data-md-hide-below', 'data-md-hide-from'])
|
|
->and($html)->toMatch('/<div data-md-pane-body>The page<\/div>/')
|
|
->not->toContain('data-md-app-bar')
|
|
->not->toContain('data-md-pane-navigation');
|
|
});
|
|
|
|
it('gives the pane its own app bar, a direct child spanning it, with the title, subtitle and actions', function () {
|
|
$html = (string) $this->blade(<<<'BLADE'
|
|
<x-pane title="Settings" subtitle="Your account" heading="h2">
|
|
<x-slot:actions><span>ACTIONS</span></x-slot:actions>
|
|
BODY
|
|
</x-pane>
|
|
BLADE);
|
|
|
|
expect($html)
|
|
->toMatch('/^\s*<div data-md-pane[^>]*>\s*<header\b[^>]*data-md-app-bar[^>]*data-md-pane-bar/s')
|
|
->toContain('<h2 data-md-app-bar-title>Settings</h2>')
|
|
->toContain('<p data-md-app-bar-subtitle>Your account</p>')
|
|
->toMatch('/<div data-md-app-bar-trailing><span>ACTIONS<\/span><\/div>/')
|
|
->toContain('data-md-sticky')
|
|
->toMatch('/<\/header>\s*<div data-md-pane-body>\s*BODY\s*<\/div>/s')
|
|
->and((string) $this->blade('<x-pane title="Settings" :sticky="false" />'))->not->toContain('data-md-sticky');
|
|
});
|
|
|
|
it('draws a bar for actions alone, and names the heading h1 by default', function () {
|
|
expect((string) $this->blade('<x-pane><x-slot:actions><span>ACTIONS</span></x-slot:actions></x-pane>'))->toContain('data-md-pane-bar')
|
|
->and((string) $this->blade('<x-pane title="Inbox" />'))->toContain('<h1 data-md-app-bar-title>Inbox</h1>');
|
|
});
|
|
|
|
it('puts the section navigation under the bar and above the body', function () {
|
|
$html = (string) $this->blade(<<<'BLADE'
|
|
<x-pane title="Settings">
|
|
<x-slot:navigation><nav>SECTIONS</nav></x-slot:navigation>
|
|
BODY
|
|
</x-pane>
|
|
BLADE);
|
|
|
|
expect($html)->toMatch('/<\/header>\s*<div data-md-pane-navigation><nav>SECTIONS<\/nav><\/div>\s*<div data-md-pane-body>\s*BODY/s')
|
|
// It is the pane's section navigation, not the bar's leading button.
|
|
->not->toContain('data-md-app-bar-leading');
|
|
});
|
|
|
|
it('leads the bar with a back link, a back action for a list-detail, or the leading slot', function () {
|
|
$link = (string) $this->blade('<x-pane title="Recipients" back="/shares" />');
|
|
$action = (string) $this->blade('<x-pane title="Message" back />');
|
|
$leading = (string) $this->blade('<x-pane title="Inbox" back><x-slot:leading><button>MENU</button></x-slot:leading></x-pane>');
|
|
|
|
expect($link)->toMatch('/data-md-app-bar-leading><span data-md-pane-back\s*>/')
|
|
->toMatch('/<a[^>]*href="\/shares"[^>]*aria-label="Back"/s')
|
|
->not->toContain('data-md-list-detail-back')
|
|
->and($action)->toMatch('/<span data-md-pane-back\s+data-md-list-detail-back\s*>/')
|
|
->toMatch('/<button[^>]*aria-label="Back"[^>]*x-on:click="back\(\)"/s')
|
|
->and($leading)->toContain('<div data-md-app-bar-leading><button>MENU</button></div>')
|
|
->not->toContain('data-md-pane-back');
|
|
});
|
|
|
|
it('caps its width with M3\'s narrow measure or the wider steps', function () {
|
|
foreach (['full', 'narrow', 'medium', 'wide'] as $width) {
|
|
expect(layoutRoot((string) $this->blade('<x-pane :width="$width" />', ['width' => $width]))['data-md-width'])->toBe($width);
|
|
}
|
|
|
|
expect(layoutRoot((string) $this->blade('<x-pane width="40rem" />')))->not->toHaveKey('data-md-width');
|
|
});
|
|
|
|
it('takes the element, the visibility props and the caller\'s class and style', function () {
|
|
expect(layoutRoot((string) $this->blade('<x-pane as="section" hide-below="expanded" class="checkout" style="max-width: 50rem" aria-labelledby="checkout-title" />')))
|
|
->toMatchArray([
|
|
'<' => 'section',
|
|
'data-md-hide-below' => 'expanded',
|
|
'class' => 'checkout',
|
|
'style' => 'max-width: 50rem;',
|
|
'aria-labelledby' => 'checkout-title',
|
|
]);
|
|
});
|
|
|
|
it('keeps M3\'s margin on the body, once, in the layout layer', function () {
|
|
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/layout/pane.css');
|
|
|
|
expect($css)->toContain('@layer material.layout')
|
|
->toContain('padding-inline: var(--md-layout-margin, var(--md-sys-measurement-space200));')
|
|
->toContain("@media (width >= 600px) {\n padding-inline: var(--md-layout-margin, var(--md-sys-measurement-space300));")
|
|
->toContain("[data-md-pane-body] > * {\n --md-layout-margin: 0px;")
|
|
->toContain("[data-md-pane][data-md-width='narrow'] {\n max-inline-size: 40rem;")
|
|
->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/pane.css';");
|
|
});
|