Overflow an app bar's trailing actions below medium
Plan step 25, item 7 (navigation Missing: "App bar, trailing-action overflow at small widths"). `<x-app-bar :actions="[...]">` takes the trailing actions as a list whose entries take <x-menu-item>'s props plus any action attribute. M3 allows up to two trailing icon buttons, and up to four on larger screens, and lets trailing actions collapse into an overflow menu at smaller breakpoints: below medium the bar keeps at most two icon buttons, from medium four, the "More options" button counted among them, the rest in its menu. Both forms render and media queries pick one, so there is no script and no flash; each width has its own menu so the arrow keys never reach a hidden row. The actions slot still takes markup and never overflows. 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
12cdeaaf67
commit
092100c6ae
@@ -44,6 +44,87 @@ it('falls back to a small bar and an h1 for unknown values', function () {
|
||||
->toContain('<h1 data-app-bar-title>');
|
||||
});
|
||||
|
||||
it('keeps two trailing icon buttons below medium and four from it, the rest in an overflow menu', function () {
|
||||
$html = (string) $this->blade(<<<'BLADE'
|
||||
<x-app-bar title="Shares" :actions="[
|
||||
['label' => 'Search', 'icon' => 'search', 'x-on:click' => 'find()'],
|
||||
['label' => 'Share', 'icon' => 'share', 'link' => '/shares/1/share'],
|
||||
['label' => 'Star', 'icon' => 'star', 'selected' => true, 'wire:click' => 'star'],
|
||||
['label' => 'Archive', 'icon' => 'archive', 'wire:click' => 'archive'],
|
||||
['label' => 'Delete', 'icon' => 'delete', 'disabled' => true],
|
||||
]" />
|
||||
BLADE);
|
||||
|
||||
preg_match('/data-app-bar-overflow="compact">(.*?)data-app-bar-overflow="medium">(.*)$/s', $html, $menus);
|
||||
[, $compact, $medium] = $menus + [null, '', ''];
|
||||
|
||||
expect($html)
|
||||
// M3's "up to four trailing icons" on a larger screen, the overflow button one of them: the
|
||||
// first three are icon buttons, and the first stays one on a compact window too, beside the
|
||||
// overflow button — M3's "up to two icon buttons" (navigation reference § Top app bar).
|
||||
->and(substr_count($html, '<span data-app-bar-action'))->toBe(3)
|
||||
->and(substr_count($html, 'data-overflows-compact'))->toBe(2)
|
||||
->and($html)->toMatch('/<span data-app-bar-action\s*>\s*<button[^>]*aria-label="Search"/')
|
||||
->toMatch('/<span data-app-bar-action\s+data-overflows-compact\s*>\s*<a href="\/shares\/1\/share"[^>]*aria-label="Share"/')
|
||||
->not->toMatch('/<(?:button|a)[^>]*aria-label="(?:Archive|Delete)"/')
|
||||
// Every icon button is named, tooltipped and reaches 48px.
|
||||
->toMatch('/<button[^>]*aria-label="Search"[^>]*class="[^"]*touch-target/')
|
||||
->toMatch('/popover="manual"[^>]*>\s*Search<\/span>/')
|
||||
->toMatch('/<button[^>]*aria-label="Star" aria-pressed="true"/')
|
||||
// One overflow button per width, named and tooltipped.
|
||||
->and(preg_match_all('/<button[^>]*aria-label="More options"[^>]*class="[^"]*touch-target/', $html))->toBe(2)
|
||||
->and(preg_match_all('/role="menu"\s+data-menu\s+aria-label="More options"/', $html))->toBe(2)
|
||||
// Below medium: all but the first.
|
||||
->and($compact)
|
||||
->toMatch('/<a role="menuitem" tabindex="-1" href="\/shares\/1\/share"/')
|
||||
->toMatch('/role="menuitemcheckbox" aria-checked="true"[^>]*wire:click="star"/')
|
||||
->toMatch('/role="menuitem"[^>]*wire:click="archive"/')
|
||||
->toMatch('/role="menuitem" aria-disabled="true"/')
|
||||
->not->toContain('find()')
|
||||
// From medium: only what the four do not hold.
|
||||
->and($medium)
|
||||
->toContain('Archive')
|
||||
->toContain('Delete')
|
||||
->not->toContain('Star')
|
||||
->not->toContain('/shares/1/share')
|
||||
// The action goes with the entry to both of its forms.
|
||||
->and(substr_count($html, 'wire:click="star"'))->toBe(2)
|
||||
->and(substr_count($html, 'wire:click="archive"'))->toBe(2)
|
||||
->and(substr_count($html, 'x-on:click="find()"'))->toBe(1);
|
||||
});
|
||||
|
||||
it('adds an overflow menu only at the widths something overflows', function () {
|
||||
$actions = fn (int $count): array => collect(['search', 'share', 'star', 'archive', 'delete'])
|
||||
->take($count)
|
||||
->map(fn (string $icon): array => ['label' => ucfirst($icon), 'icon' => $icon])
|
||||
->all();
|
||||
|
||||
$two = (string) $this->blade('<x-app-bar title="Shares" :actions="$actions" />', ['actions' => $actions(2)]);
|
||||
$four = (string) $this->blade('<x-app-bar title="Shares" :actions="$actions" />', ['actions' => $actions(4)]);
|
||||
|
||||
expect($two)
|
||||
->not->toContain('data-app-bar-overflow')
|
||||
->not->toContain('data-overflows-compact')
|
||||
->not->toContain('More options')
|
||||
->and(substr_count($two, '<span data-app-bar-action'))->toBe(2)
|
||||
->and($four)
|
||||
->toContain('data-app-bar-overflow="compact"')
|
||||
->not->toContain('data-app-bar-overflow="medium"')
|
||||
->and(substr_count($four, '<span data-app-bar-action'))->toBe(4)
|
||||
->and(substr_count($four, 'data-overflows-compact'))->toBe(3)
|
||||
// An empty list draws no trailing area; markup in the slot is drawn as written, never
|
||||
// overflowing.
|
||||
->and((string) $this->blade('<x-app-bar title="Shares" :actions="[]" />'))
|
||||
->not->toContain('data-app-bar-trailing')
|
||||
->and((string) $this->blade('<x-app-bar title="Shares"><x-slot:actions><button>a</button><button>b</button><button>c</button></x-slot:actions></x-app-bar>'))
|
||||
->toContain('<div data-app-bar-trailing><button>a</button><button>b</button><button>c</button></div>')
|
||||
->not->toContain('data-app-bar-overflow');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/app-bar.css'))
|
||||
->toMatch('/@media \(width < 37\.5rem\) \{\s+\[data-app-bar-action\]\[data-overflows-compact\],\s+\[data-app-bar-overflow="medium"\] \{\s+display: none;/')
|
||||
->toMatch('/@media \(width >= 37\.5rem\) \{\s+\[data-app-bar-overflow="compact"\] \{\s+display: none;/');
|
||||
});
|
||||
|
||||
it('draws floating and docked toolbars', function () {
|
||||
$html = (string) $this->blade(<<<'BLADE'
|
||||
<x-toolbar label="Formatting" vibrant vertical place="end">
|
||||
|
||||
Reference in New Issue
Block a user