From 067626c52e2e1c7f449707316bca4efeb2bd2c2a Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Tue, 15 Sep 2026 05:34:06 +0200 Subject: [PATCH] Pare down the showcase's unlayered sizing classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 — forwards `style` to the raw , not the field wrapper that is sized, and forwards neither, only `class`, `wire:key` and `x-model` — so .showcase-w-sm and .showcase-w-xs stay. 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 Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/css/showcase.css | 32 ++++++----- .../views/showcase/sections/sliders.blade.php | 10 ++-- tests/Feature/StylesheetsTest.php | 56 +++++++++++++++++++ 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/resources/css/showcase.css b/resources/css/showcase.css index a24ead25..320b67c9 100644 --- a/resources/css/showcase.css +++ b/resources/css/showcase.css @@ -27,11 +27,7 @@ * indicator, a bounded box for the FAB menu, a narrow chip row that forces its own scroll * affordance, a scrolling table wrapper, the legend above a checkbox grid, a bordered demo card, * a small circular avatar and a compact search bar, and the two ink colours a menu item's `icon-class` - * needs beyond the fixed `md-ink-*` set. A handful of call sites (``, ``, - * ``) forward only `class` and `style` to the element that needs sizing, never a data - * attribute, so a few widths below are plain `.showcase-*` classes instead of `data-md-showcase-*` - * hooks — the same "caller's class" mechanism any application would reach for, deliberately not - * named `md-*` since they are not part of the fixed set text.css declares. + * needs beyond the fixed `md-ink-*` set. * * Batch 4 (the last: containment, carousel, bars, navigation, pages) adds the standard side * sheet demo's own breakpoint (a `` beats its own `stack-below` alignment back to `stretch` @@ -42,6 +38,18 @@ * own horizontal-scroll wrapper, `rail`, `rail-wide`) with their filler "page" and content panes. * Every per-item carousel colour stays the caller's own inline `style`, the same mechanism as the * colour swatch above. + * + * Of the three call sites batch 3 found, two still cannot take a `style` for the width their + * demos need: `` forwards `style` to the raw ``, not the field wrapper that is + * actually sized, and `` forwards neither — its root takes only `class`, `wire:key` + * and `x-model`. `.showcase-w-sm`, `.showcase-w-xs` stay plain unlayered classes for that reason, + * the same "caller's class" mechanism any application would reach for, deliberately not named + * `md-*` since they are not part of the fixed set text.css declares. `.showcase-w-narrow` stays + * one too, but to make a point on purpose (fields.blade.php's "Narrower" input): a small unlayered + * rule from an application's own stylesheet beats the package's own layered default, so it has to + * be a real class, not an inline `style` (which would win trivially and prove nothing). `` + * does forward `style` to its root, so its vertical demo's height became one + * (resources/views/showcase/sections/sliders.blade.php), and `.showcase-slider-vertical` is gone. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @@ -553,9 +561,8 @@ } } -/* A handful of call sites (, , ) forward only `class` and - `style` to the element that needs sizing, so these few widths are the caller's own classes - rather than data-md-showcase-* hooks — see the file header. */ +/* Two call sites still cannot take a `style` for the width their demo needs — see the file + header — so these stay plain unlayered classes. */ .showcase-w-sm { width: 192px; } @@ -564,12 +571,9 @@ width: 144px; } +/* Deliberately unlayered, to make the point fields.blade.php's hint names: an application's own + rule beats the package's layered default even though both set the same property. An inline + `style` would win regardless of layers, which would not demonstrate that. */ .showcase-w-narrow { max-width: 320px; } - -/* forwards only class, style and wire:key to its wrapper, which the label and hint - share with the track — so the vertical demo's shared height is a class too. */ -.showcase-slider-vertical { - height: 256px; -} diff --git a/resources/views/showcase/sections/sliders.blade.php b/resources/views/showcase/sections/sliders.blade.php index 950cd6d4..2d730310 100644 --- a/resources/views/showcase/sections/sliders.blade.php +++ b/resources/views/showcase/sections/sliders.blade.php @@ -25,11 +25,11 @@ 'Vertical' => <<<'BLADE' {{-- M3 Expressive's second orientation. A vertical slider needs a height: it is on the wrapper, which the label and hint share. --}} - - - - - + + + + + BLADE, 'Colours and value labels' => <<<'BLADE' diff --git a/tests/Feature/StylesheetsTest.php b/tests/Feature/StylesheetsTest.php index c896455e..44928f7b 100644 --- a/tests/Feature/StylesheetsTest.php +++ b/tests/Feature/StylesheetsTest.php @@ -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"); + } + } +});