Add cards, lists, dialogs, sheets and the rest of M3's containment
tests / lint (push) Failing after 2m6s
tests / feature (8.4) (push) Successful in 1m3s
tests / feature (8.5) (push) Successful in 1m7s
tests / browser (safari, webkit) (push) Successful in 3m14s
tests / browser (chrome, chromium) (push) Successful in 2m9s
tests / browser (firefox, firefox) (push) Successful in 2m29s
tests / lint (push) Failing after 2m6s
tests / feature (8.4) (push) Successful in 1m3s
tests / feature (8.5) (push) Successful in 1m7s
tests / browser (safari, webkit) (push) Successful in 3m14s
tests / browser (chrome, chromium) (push) Successful in 2m9s
tests / browser (firefox, firefox) (push) Successful in 2m29s
<x-card> (filled, elevated, outlined), <x-list> and <x-list-item> (plain or M3 Expressive's segmented list), <x-divider>, <x-collapse> on <details>, <x-modal> on the native <dialog>, <x-drawer> as a side sheet or list-detail pane, and <x-bottom-sheet> with drag to dismiss. Dialogs and sheets bind to a Livewire flag or id and write back false or null on close, or use the surrounding Alpine scope. Rows open from anywhere on them through data-list-row and data-list-open. DesignGuard now also reports Blade directives written inside a component tag, where they do not compile. Browser test helpers wait for a complete document with Alpine and Livewire running: in Firefox, networkidle alone could return before a repeated visit had loaded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
e57063b0f4
commit
fef20a9178
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class OverlayProbe extends Component
|
||||
{
|
||||
public bool $confirming = false;
|
||||
|
||||
public ?int $deletingId = null;
|
||||
|
||||
public int $renders = 0;
|
||||
|
||||
public function touch(): void
|
||||
{
|
||||
$this->renders++;
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'BLADE'
|
||||
<div class="space-y-4 p-4">
|
||||
<p>confirming: <span id="confirming">{{ var_export($confirming, true) }}</span></p>
|
||||
<p>deleting: <span id="deleting">{{ var_export($deletingId, true) }}</span></p>
|
||||
<p>renders: <span id="renders">{{ $renders }}</span></p>
|
||||
|
||||
<x-button label="Confirm" wire:click="$set('confirming', true)" />
|
||||
<x-button label="Delete 7" wire:click="$set('deletingId', 7)" />
|
||||
|
||||
<x-modal wire:model="confirming" title="Are you sure?">
|
||||
<x-slot:actions>
|
||||
<x-button label="Re-render" wire:click="touch" />
|
||||
</x-slot:actions>
|
||||
</x-modal>
|
||||
|
||||
<x-modal wire:model="deletingId" title="Delete share?" />
|
||||
</div>
|
||||
BLADE;
|
||||
}
|
||||
}
|
||||
|
||||
function overlayProbe()
|
||||
{
|
||||
Livewire::component('overlay-probe', OverlayProbe::class);
|
||||
|
||||
Route::middleware('web')->get('/overlay-probe', fn () => Blade::render(<<<'BLADE'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<x-theme-script />
|
||||
@vite(config('livewire-material.showcase.vite'))
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="bg-surface">
|
||||
<livewire:overlay-probe />
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return visit('/overlay-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
}
|
||||
|
||||
function containment()
|
||||
{
|
||||
return visit('/material')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
}
|
||||
|
||||
it('writes back what closed means: false for a flag, null for an id', function () {
|
||||
$page = overlayProbe();
|
||||
|
||||
$page->click('button:has-text("Confirm")')
|
||||
->assertScript("[...document.querySelectorAll('dialog')].some((d) => d.open && d.textContent.includes('Are you sure?'))");
|
||||
|
||||
$page->click('dialog[open] button:has-text("Re-render")')
|
||||
->assertSeeIn('#renders', '1')
|
||||
->assertScript("[...document.querySelectorAll('dialog')].some((d) => d.open && d.textContent.includes('Are you sure?'))");
|
||||
|
||||
$page->keys('dialog[open] button:has-text("Re-render")', 'Escape')
|
||||
->assertScript("! [...document.querySelectorAll('dialog')].some((d) => d.open)")
|
||||
->assertSeeIn('#confirming', 'false');
|
||||
|
||||
$page->click('button:has-text("Delete 7")')
|
||||
->assertSeeIn('#deleting', '7')
|
||||
->assertScript("[...document.querySelectorAll('dialog')].some((d) => d.open && d.textContent.includes('Delete share?'))");
|
||||
|
||||
$page->script("document.querySelector('dialog[open]').dispatchEvent(new Event('cancel', { cancelable: true }))");
|
||||
|
||||
$page->assertSeeIn('#deleting', 'NULL');
|
||||
});
|
||||
|
||||
it('closes a showcase dialog on Escape and gives focus back to its trigger', function () {
|
||||
// Opened from the keyboard: Safari does not focus a button on click, so after a click there
|
||||
// is nothing for the dialog to hand focus back to.
|
||||
$page = containment();
|
||||
|
||||
$page->script("[...document.querySelectorAll('#containment button')].find((b) => b.textContent.trim() === 'Basic dialog').focus()");
|
||||
|
||||
$page->keys(':focus', 'Enter')
|
||||
->assertScript("document.querySelector('#containment dialog').open");
|
||||
|
||||
$page->keys('#containment dialog[open] button:has-text("Cancel")', 'Escape')
|
||||
->assertScript("! document.querySelector('#containment dialog').open")
|
||||
->assertScript("document.activeElement.textContent.trim() === 'Basic dialog'");
|
||||
});
|
||||
|
||||
it('slides a side sheet in, traps the page, and closes on the scrim', function () {
|
||||
$sheet = "document.querySelector('#containment aside[role=\"dialog\"]')";
|
||||
|
||||
$page = containment()
|
||||
->click('#containment button:has-text("Side sheet")')
|
||||
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
||||
->assertScript("document.querySelector('header').closest('[aria-hidden=\"true\"]') !== null");
|
||||
|
||||
$page->script("document.querySelector('[data-sheet] > [aria-hidden=\"true\"]').click()");
|
||||
|
||||
$page->assertScript("getComputedStyle({$sheet}).display === 'none'")
|
||||
->assertScript("document.querySelector('header').closest('[aria-hidden=\"true\"]') === null");
|
||||
});
|
||||
|
||||
it('dismisses a bottom sheet dragged down past a quarter of its height', function () {
|
||||
$sheet = "document.querySelector('#containment section[role=\"dialog\"]')";
|
||||
|
||||
$page = containment()
|
||||
->click('#containment button:has-text("Bottom sheet")')
|
||||
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
||||
->wait(0.5);
|
||||
|
||||
$page->script(<<<JS
|
||||
(() => {
|
||||
const handle = {$sheet}.querySelector('[data-drag-handle]');
|
||||
const box = handle.getBoundingClientRect();
|
||||
const x = box.x + box.width / 2, y = box.y + box.height / 2;
|
||||
handle.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: x, clientY: y, button: 0 }));
|
||||
window.dispatchEvent(new PointerEvent('pointermove', { clientX: x, clientY: y + 400 }));
|
||||
window.dispatchEvent(new PointerEvent('pointerup', { clientX: x, clientY: y + 400 }));
|
||||
})()
|
||||
JS);
|
||||
|
||||
$page->assertScript("getComputedStyle({$sheet}).display === 'none'");
|
||||
});
|
||||
|
||||
it('opens a row\'s opener from a press anywhere on the row, but not from its own buttons', function () {
|
||||
$page = containment();
|
||||
|
||||
$page->script("window.__opened = 0; document.querySelector('#containment [data-card][data-list-row] [data-list-open]').addEventListener('click', (event) => { event.preventDefault(); window.__opened++ })");
|
||||
|
||||
$page->click('#containment [data-card][data-list-row] p')
|
||||
->assertScript('window.__opened === 1');
|
||||
|
||||
$page->click('#containment [data-card][data-list-row] button:has-text("Copy link")')
|
||||
->assertScript('window.__opened === 1');
|
||||
});
|
||||
Reference in New Issue
Block a user