Pare down the showcase's unlayered sizing classes

Plan step 38 (last batch): only two of the previous batch's three
call sites actually need a plain unlayered class for their demo width
— <x-input> forwards `style` to the raw <input>, not the field wrapper
that is sized, and <x-datepicker> forwards neither, only `class`,
`wire:key` and `x-model` — so .showcase-w-sm and .showcase-w-xs stay.
<x-slider> does forward `style` to its root, so its vertical demo's
shared height is one now instead of the .showcase-slider-vertical
class. .showcase-w-narrow stays a class on purpose rather than an
inline style: fields.blade.php's "Narrower" input demonstrates an
application's own unlayered rule beating the package's layered
default, and an inline style would win regardless of layers, proving
nothing. The header explains all three.

StylesheetsTest gains showcase.css's own shape check — the same rule
every package stylesheet passes (header, layer statement, plain
imports, no Tailwind directive, breakpoints only at M3's four) — with
its three documented unlayered exceptions as the one allowed gap.

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-15 05:34:06 +02:00
co-authored by Claude Sonnet 5
parent 42009f3b0c
commit 067626c52e
3 changed files with 79 additions and 19 deletions
+56
View File
@@ -666,3 +666,59 @@ it('builds the package in a Workbench entry of its own, with the material layers
'workbench/resources/js/app.js',
]);
});
it('shapes showcase.css like a package stylesheet, with its documented unlayered exceptions', function () {
$path = __DIR__.'/../../resources/css/showcase.css';
$css = File::get($path);
$items = stylesheetItems($css);
expect($css)->toStartWith('/*', 'showcase.css has no header comment')
->and($items[0]['statement'] ?? null)->toBe(MATERIAL_LAYER_STATEMENT, 'showcase.css does not open with the layer statement')
->and(stylesheetWithoutComments($css))
->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b/')
->not->toMatch('/--(?:theme|spacing|alpha)\(|\btheme\(|[\'"]tailwindcss[\'"]/');
$blocks = false;
$unlayered = [];
foreach (array_slice($items, 1) as $item) {
if (isset($item['prelude'])) {
$blocks = true;
if ($item['prelude'] === '@layer material.components') {
expect($item['body'])->not->toContain('@layer');
} else {
$unlayered[$item['prelude']] = true;
}
continue;
}
expect($blocks)->toBeFalse("`{$item['statement']}` comes after a block")
->and($item['statement'])->toMatch('/^@import ([\'"])\.{1,2}\/[\w.\/-]+\.css\1;$/', "`{$item['statement']}` is not a plain import");
}
// showcase.css has no imports of its own yet (its header says why); the day it needs one, this
// still holds because it is a plain @import assertion above, not a fixed empty list here.
expect($unlayered)->toBe([
// Deliberately unlayered — see the file's header and .showcase-w-narrow's own comment.
'.showcase-w-sm' => true,
'.showcase-w-xs' => true,
'.showcase-w-narrow' => true,
]);
preg_match_all('/@media\b([^{]*)\{/', stylesheetWithoutComments($css), $matches);
foreach ($matches[1] as $query) {
$feature = trim($query);
expect($feature)->not->toMatch('/\b(?:min|max)-(?:width|height)\b/', "{$feature} is not written as a range");
preg_match_all('/(\d*\.?\d+)([a-z%]*)/i', $feature, $lengths, PREG_SET_ORDER);
foreach ($lengths as [, $number, $unit]) {
expect($unit)->toBe('px', $feature)
->and(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$feature} is not at an M3 breakpoint");
}
}
});