Add the stack layout component and the layout foundation
Plan step 35: <x-stack gap align>, the first of M3's layout components, with what they all share - src/Support/Layout.php reading the props into data-md-* attributes, the spacing-token gap and padding rules, the hide-below/hide-from rules in material.visibility, the stylesheet checks for resources/css/layout, the browser test file and the skill's Layout group. The Workbench puts the material layers above Tailwind's preflight, which would otherwise zero every layout padding and margin. 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
6be9f35c98
commit
b677e60876
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/**
|
||||
* The layout components at M3's breakpoints (plan step 35): each on a page of its own, measured at
|
||||
* the pixel either side of the breakpoint it changes at. The window is resized after the page has
|
||||
* loaded, which is what a person does; a media query answers at once, and a script listening to
|
||||
* one (resources/js/layout.js) hears a change event.
|
||||
*/
|
||||
function layoutPage(string $body, int $width, int $height = 900, string $dir = 'ltr'): mixed
|
||||
{
|
||||
// Until the Workbench leaves Tailwind, its build turns `:dir(rtl)` into a list of `:lang()`s, so a
|
||||
// right-to-left page also says which language it is in.
|
||||
$lang = $dir === 'rtl' ? 'ar' : 'en';
|
||||
$path = '/layout-probe/'.md5($body.$dir);
|
||||
|
||||
Route::middleware('web')->get($path, fn () => Blade::render(<<<BLADE
|
||||
<!DOCTYPE html>
|
||||
<html dir="{$dir}" lang="{$lang}">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<x-theme-script />
|
||||
@vite(config('livewire-material.showcase.vite'))
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body>
|
||||
{$body}
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return layoutReady(visit($path)->resize($width, $height));
|
||||
}
|
||||
|
||||
function layoutReady(mixed $page): mixed
|
||||
{
|
||||
return $page->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
}
|
||||
|
||||
/** A computed style of the element matching the selector. */
|
||||
function layoutStyle(string $selector, string $property): string
|
||||
{
|
||||
return "getComputedStyle(document.querySelector('{$selector}')).{$property}";
|
||||
}
|
||||
|
||||
/** A side of the element's box, rounded to the pixel. */
|
||||
function layoutRect(string $selector, string $side): string
|
||||
{
|
||||
return "Math.round(document.querySelector('{$selector}').getBoundingClientRect().{$side})";
|
||||
}
|
||||
|
||||
/** How many column tracks a grid has. */
|
||||
function layoutColumns(string $selector): string
|
||||
{
|
||||
return layoutStyle($selector, 'gridTemplateColumns').".split(' ').length";
|
||||
}
|
||||
|
||||
it('spaces a stack with its token, never with its parent\'s, and hides it from a breakpoint over its own display', function () {
|
||||
$body = '<x-stack id="outer" gap="space300"><x-stack id="inner"><p>One</p><p>Two</p></x-stack><p>Three</p></x-stack>'
|
||||
.'<x-stack id="hidden-from" hide-from="medium"><p>Compact only</p></x-stack>';
|
||||
|
||||
layoutPage($body, 599)
|
||||
->assertScript(layoutStyle('#outer', 'flexDirection')." === 'column'")
|
||||
->assertScript(layoutStyle('#outer', 'rowGap')." === '24px'")
|
||||
->assertScript(layoutStyle('#inner', 'rowGap')." === '0px'")
|
||||
->assertScript(layoutStyle('#hidden-from', 'display')." === 'flex'");
|
||||
|
||||
layoutPage($body, 600)
|
||||
->assertScript(layoutStyle('#hidden-from', 'display')." === 'none'");
|
||||
});
|
||||
Reference in New Issue
Block a user