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"); + } + } +});