diff --git a/resources/css/showcase.css b/resources/css/showcase.css index 320b67c9..22819aab 100644 --- a/resources/css/showcase.css +++ b/resources/css/showcase.css @@ -1,55 +1,28 @@ /* - * The showcase's own chrome: what its frame needs beyond the layout components, the text classes - * and the shared interaction classes (plan step 38) — a brand wordmark, the search box's own - * geometry, a small icon avatar, an example's card and its code sample. Everything else the frame - * draws is a layout component with its props, or a `md-*` text class; this file is only what those - * two do not cover. + * The showcase's own chrome: what its pages draw beyond the layout components, the `md-*` text + * classes and the shared interaction classes (plan step 38) — the rail's wordmark, the search box + * and its results, a keyboard key in running text, an example's card and its code sample, and the + * foundation pages' specimens: a colour swatch, a type-scale row, a corner swatch, an elevation + * tile, a motion track and its dot, an icon search result and the Layout page's breakpoint cards. * - * In `material.components`: the showcase is one more thing drawn over the foundation and the - * layout components, exactly where `components/error-page.css` sits for the same reason (a whole - * page's own chrome, not a reusable package component, but still something an application's own - * unlayered CSS should outrank without a specificity fight). Keyed on `data-md-showcase-*` hooks, - * never a bare element selector, since this file is always bundled together with `all.css` - * (`ShowcaseAssetController`) and must not restyle anything outside the showcase's own pages. + * Only the showcase's own pages use it. The examples do not: an example's code is what an + * application copies, so it writes only what an application has — the components and their props, + * the layout components, the text classes, and an inline `style` from the `--md-sys-*` tokens where + * a demo needs a size, a frame or a colour of its own. * - * No imports: batch 1 (the frame — layout, index, section, shell, the example component) uses no - * selector this file needs to draw over, only tokens already in `all.css`, which the bundle always - * carries ahead of this file. A later batch that needs a component's own hook imports its - * stylesheet here, as every other package file does. + * In `material.components`: the showcase is one more page drawn over the foundation and the layout + * components, where `components/error-page.css` sits for the same reason, and an application's + * unlayered CSS still outranks it. Keyed on `data-md-showcase-*` hooks, never a bare element + * selector, since `ShowcaseAssetController` bundles this file with `all.css` and it must restyle + * nothing outside those hooks. It imports nothing: every token and layout rule it builds on is in + * `all.css`, ahead of it in the bundle. Values are tokens, apart from a specimen's own size; its + * media queries are M3's breakpoints, in px. * - * Batch 2 (the foundation and layout sections) adds the swatches, specimens and demo boxes those - * pages draw beyond a layout component and a `md-*` text class: a colour swatch (its own fill is - * the role it shows, so that one declaration is the caller's inline `style`, never a class), a - * type-scale row, a shape swatch, an elevation tile, a motion track and its moving dot, an icon - * search result and the Layout page's breakpoint cards. - * - * Batch 3 (the component sections) adds each section's own demo geometry: a sized loading - * 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. - * - * 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` - * once collapsed to a column, which an `align` the row mode needs would otherwise leave - * shrink-wrapped), a bordered app-bar/toolbar frame (plain, `visible` for the search bar whose - * docked suggestions must not clip, `rounded` for the large-screen docked toolbar), a narrow - * scrollable-tabs box, and the navigation bar and rail demo frames (`bar`, `bar-wide` inside its - * 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. + * Two kinds of rule sit outside the layers on purpose, as an application's own classes would: + * `.showcase-w-narrow` proves the point its example makes (Text fields, "Narrower": an unlayered + * rule from the call site beats the package's layered default, which an inline `style` would win + * regardless of layers, proving nothing), and `.showcase-ink-*` are the colours the Menus example + * passes to `icon-class`, the application's own class that prop takes. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @@ -331,244 +304,6 @@ color: var(--md-sys-color-on-primary-container); } } - - /* Buttons: the loading indicator sized and coloured beyond its 48px primary default, and the - bounded box a FAB menu opens into (M3 anchors it to a scaffold's own corner; this is only - room enough to show the menu open). */ - [data-md-showcase-loading='lg'] { - width: 96px; - height: 96px; - color: var(--md-sys-color-tertiary); - } - - [data-md-showcase-loading='sm'] { - width: 32px; - height: 32px; - } - - [data-md-showcase-fab-menu-demo] { - display: flex; - align-items: flex-end; - justify-content: flex-end; - height: 288px; - } - - /* Menus: an item's icon in a colour beyond the fixed md-ink-* set (icon-class covers the - roles that set already has, md-ink-info among them). */ - [data-md-showcase-icon-color='tertiary'] [data-md-icon] { - color: var(--md-sys-color-tertiary); - } - - [data-md-showcase-icon-color='secondary'] [data-md-icon] { - color: var(--md-sys-color-secondary); - } - - /* Communication: the "plain" badge's own colours (that variant draws none, by design, so the - caller paints it), a focus ring's corner on a plain span standing in for a control, and the - illustration's two decorative colours beyond currentColor. */ - [data-md-showcase-badge-plain] { - background-color: var(--md-sys-color-primary-fixed); - color: var(--md-sys-color-on-primary-fixed); - } - - [data-md-showcase-focus-demo] { - border-radius: var(--md-sys-shape-corner-xs); - } - - [data-md-showcase-illustration='container'] { - fill: var(--md-sys-color-primary-container); - } - - [data-md-showcase-illustration='accent'] { - fill: var(--md-sys-color-tertiary); - } - - [data-md-showcase-illustration='on-accent'] { - fill: var(--md-sys-color-on-tertiary); - } - - /* Progress: one circular indicator drawn larger than its 48px default, to show the ring - scales with its box. */ - [data-md-showcase-progress-lg] { - width: 96px; - height: 96px; - } - - /* Chips: a row narrow enough that its set has to scroll, to show the fade and the arrow - buttons M3 asks an overflowing row for. */ - [data-md-showcase-chip-scroll] { - width: 100%; - max-width: 384px; - } - - /* Text fields: the legend above a checkbox grid, a bordered demo card at two widths (a form, - a checkbox group), a small circular avatar for a search bar's trailing slot, and the - compact search-icon-button bar M3 uses as a secondary entry point. */ - [data-md-showcase-legend] { - margin-bottom: var(--md-sys-measurement-space200); - } - - [data-md-showcase-demo-card] { - width: 100%; - } - - [data-md-showcase-demo-card][data-md-showcase-size='md'] { - max-width: 576px; - } - - [data-md-showcase-demo-card][data-md-showcase-size='lg'] { - max-width: 672px; - } - - [data-md-showcase-avatar] { - display: grid; - place-items: center; - width: var(--md-sys-measurement-space400); - height: var(--md-sys-measurement-space400); - border-radius: var(--md-sys-shape-corner-full); - background-color: var(--md-sys-color-primary-container); - color: var(--md-sys-color-on-primary-container); - } - - [data-md-showcase-search-bar] { - display: flex; - align-items: center; - gap: var(--md-sys-measurement-space100); - border-radius: var(--md-sys-shape-corner-full); - background-color: var(--md-sys-color-surface-container-high); - padding: var(--md-sys-measurement-space50) var(--md-sys-measurement-space100); - } - - [data-md-showcase-search-bar-title] { - flex: 1 1 auto; - } - - /* Data: the table's own horizontal scroll stays the caller's, as table.css's header says. */ - [data-md-showcase-table-scroll] { - width: 100%; - overflow-x: auto; - } - - /* Containment: the standard side sheet demo is a row from `expanded` (840px), M3's own - condition for a co-planar sheet, and a column below it — ``'s own `stack-below` - stacks it there, but leaves the `align="start"` the row layout needs (so the sheet does not - stretch to the text column's height) in place, which would otherwise shrink-wrap both - columns instead of stacking them full width. */ - @media (width < 840px) { - [data-md-showcase-side-sheet-demo] { - align-items: stretch; - } - } - - /* Carousel: an item's own colour is the caller's inline `style` (its role pair is chosen per - item), the same mechanism as the colour swatch above; this only sizes and centres it. */ - [data-md-showcase-carousel-swatch] { - display: grid; - place-items: center; - width: 100%; - height: 100%; - } - - /* Bars: a bordered frame around one app-bar or docked-toolbar demo, matching the rounding a - real scaffold would clip its bar to. `visible` lets the search bar's docked suggestions - overflow it instead of clipping; `rounded` is the large-screen docked toolbar, which has no - edge of its own and only gains its padding from `expanded` (840px), M3's own condition for - the rounded (rather than square) docked toolbar. */ - [data-md-showcase-bar-frame] { - width: 100%; - overflow: hidden; - border-radius: var(--md-sys-shape-corner-lg); - border: 1px solid var(--md-sys-color-outline-variant); - } - - [data-md-showcase-bar-frame='visible'] { - overflow: visible; - } - - [data-md-showcase-bar-frame='rounded'] { - border: 0; - background-color: var(--md-sys-color-surface); - } - - @media (width >= 840px) { - [data-md-showcase-bar-frame='rounded'] { - padding: var(--md-sys-measurement-space200); - } - } - - /* Bars: the scrollable-tabs demo needs a width narrow enough to force its own scroll. */ - [data-md-showcase-tabs-narrow] { - max-width: 384px; - } - - /* Navigation: a bounded frame around one navigation-bar or navigation-rail demo. `bar` and - `bar-wide` stack the bar under a "the page" filler the way a scaffold does; `bar-wide` drops - its own edge because its horizontal-scroll wrapper (`data-md-showcase-nav-scroll`) already - draws one, and is wide enough (600px) that a bar showing icon beside label never wraps. - `rail` and `rail-wide` sit the rail beside a content pane the way a scaffold does; `rail-wide` - is full width, for the two examples with real body text beside the rail. */ - [data-md-showcase-nav-demo='bar'] { - display: flex; - flex-direction: column; - height: 224px; - overflow: hidden; - border-radius: var(--md-sys-shape-corner-lg); - border: 1px solid var(--md-sys-color-outline-variant); - background-color: var(--md-sys-color-surface); - } - - [data-md-showcase-nav-demo='bar-wide'] { - display: flex; - flex-direction: column; - height: 224px; - min-width: 600px; - background-color: var(--md-sys-color-surface); - } - - [data-md-showcase-nav-demo='rail'] { - display: flex; - height: 576px; - overflow: hidden; - border-radius: var(--md-sys-shape-corner-lg); - border: 1px solid var(--md-sys-color-outline-variant); - } - - [data-md-showcase-nav-demo='rail-wide'] { - display: flex; - width: 100%; - height: 480px; - overflow: hidden; - border-radius: var(--md-sys-shape-corner-lg); - border: 1px solid var(--md-sys-color-outline-variant); - } - - [data-md-showcase-nav-scroll] { - overflow-x: auto; - border-radius: var(--md-sys-shape-corner-lg); - border: 1px solid var(--md-sys-color-outline-variant); - } - - [data-md-showcase-nav-page] { - flex: 1 1 auto; - padding: var(--md-sys-measurement-space200); - } - - [data-md-showcase-nav-content] { - flex: 1 1 auto; - min-width: 0; - background-color: var(--md-sys-color-surface-container-low); - padding: var(--md-sys-measurement-space300); - } -} - -/* 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; -} - -.showcase-w-xs { - width: 144px; } /* Deliberately unlayered, to make the point fields.blade.php's hint names: an application's own @@ -577,3 +312,14 @@ .showcase-w-narrow { max-width: 320px; } + +/* An application's own icon colours, for menus.blade.php's `icon-class` example: unlayered, as the + menu item's header says a caller's class is, so it paints the icon over the item's ink (and a + disabled item's icon still stays disabled). */ +.showcase-ink-tertiary { + color: var(--md-sys-color-tertiary); +} + +.showcase-ink-secondary { + color: var(--md-sys-color-secondary); +} diff --git a/resources/views/showcase/sections/bars.blade.php b/resources/views/showcase/sections/bars.blade.php index 7cff9537..b78ed6da 100644 --- a/resources/views/showcase/sections/bars.blade.php +++ b/resources/views/showcase/sections/bars.blade.php @@ -2,7 +2,7 @@ $examples = [ 'Top app bars' => <<<'BLADE' -
+ @@ -10,10 +10,10 @@ -
+ {{-- Resize the window: two icon buttons below 600px, four from there, the rest in "More options". --}} -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ - AR + AR -
+
BLADE, 'Toolbars' => <<<'BLADE' @@ -78,16 +78,16 @@ -
+ -
+ -
+ @@ -95,10 +95,10 @@ -
+ {{-- M3's docked toolbar on a large screen: rounded from 840px, its groups of controls divided. Narrower, it is the square docked bar. --}} -
+ @@ -108,7 +108,7 @@ -
+
BLADE, 'Primary tabs' => <<<'BLADE' @@ -134,7 +134,7 @@ Plan and invoices. -
+
January's shares. diff --git a/resources/views/showcase/sections/buttons.blade.php b/resources/views/showcase/sections/buttons.blade.php index 89c91b13..95b6c5cf 100644 --- a/resources/views/showcase/sections/buttons.blade.php +++ b/resources/views/showcase/sections/buttons.blade.php @@ -60,8 +60,8 @@ 'Loading indicator' => <<<'BLADE' - - + + BLADE, 'Button groups: standard (press a button) and connected' => <<<'BLADE' @@ -161,13 +161,13 @@ BLADE, 'FAB menu' => <<<'BLADE' -
+ -
+ BLADE, ]; @endphp diff --git a/resources/views/showcase/sections/carousel.blade.php b/resources/views/showcase/sections/carousel.blade.php index 8d6a3348..2483198a 100644 --- a/resources/views/showcase/sections/carousel.blade.php +++ b/resources/views/showcase/sections/carousel.blade.php @@ -13,7 +13,7 @@ ['Heart', 'heart', 'background-color: var(--md-sys-color-inverse-surface); color: var(--md-sys-color-inverse-primary);'], ] as [$name, $shape, $colours]) -
+
@@ -30,7 +30,7 @@ ['Ghost', 'ghostish', 'background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary);'], ] as [$name, $shape, $colours]) -
+
@@ -48,7 +48,7 @@ ['oval', 'background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary);'], ] as [$shape, $colours]) -
+
@@ -66,7 +66,7 @@ ['16/9', 'very-sunny', 'background-color: var(--md-sys-color-secondary); color: var(--md-sys-color-on-secondary);'], ] as [$aspect, $shape, $colours]) -
+
@@ -81,7 +81,7 @@ ['Night', 'puffy-diamond', 'background: linear-gradient(to bottom right, var(--md-sys-color-inverse-surface), var(--md-sys-color-primary)); color: var(--md-sys-color-inverse-primary);'], ] as [$name, $shape, $colours]) -
+
diff --git a/resources/views/showcase/sections/chips.blade.php b/resources/views/showcase/sections/chips.blade.php index 6255fd36..d997bb30 100644 --- a/resources/views/showcase/sections/chips.blade.php +++ b/resources/views/showcase/sections/chips.blade.php @@ -53,7 +53,7 @@ BLADE, 'A set that scrolls' => <<<'BLADE' {{-- M3 asks an overflowing chip row for a visible affordance: the edge it can still scroll towards fades, and on a mouse a button sits over it. --}} -
+
diff --git a/resources/views/showcase/sections/communication.blade.php b/resources/views/showcase/sections/communication.blade.php index 9fe933eb..ce8f8efe 100644 --- a/resources/views/showcase/sections/communication.blade.php +++ b/resources/views/showcase/sections/communication.blade.php @@ -13,7 +13,7 @@ Pro - + BLADE, 'Snackbars' => <<<'BLADE' @@ -33,7 +33,7 @@

A description makes the snackbar 68px, M3's two-line height; below medium the action wraps under the text. - Press Alt + G to move the focus to a snackbar that has an action. + Press Alt + G to move the focus to a snackbar that has an action.

BLADE, @@ -41,7 +41,7 @@ - holiday-photos.zip + holiday-photos.zip BLADE, 'Rich tooltips' => <<<'BLADE' @@ -89,11 +89,11 @@ diff --git a/resources/views/showcase/sections/containment.blade.php b/resources/views/showcase/sections/containment.blade.php index fa423845..f1ce12c7 100644 --- a/resources/views/showcase/sections/containment.blade.php +++ b/resources/views/showcase/sections/containment.blade.php @@ -188,7 +188,7 @@
BLADE, 'A standard side sheet: co-planar from expanded, modal below' => <<<'BLADE' - +

diff --git a/resources/views/showcase/sections/data.blade.php b/resources/views/showcase/sections/data.blade.php index 84ff292c..a39c5bbf 100644 --- a/resources/views/showcase/sections/data.blade.php +++ b/resources/views/showcase/sections/data.blade.php @@ -1,7 +1,7 @@ @php $examples = [ 'Data table' => <<<'BLADE' -

+
diff --git a/resources/views/showcase/sections/fields.blade.php b/resources/views/showcase/sections/fields.blade.php index d60ac771..5cd39ab5 100644 --- a/resources/views/showcase/sections/fields.blade.php +++ b/resources/views/showcase/sections/fields.blade.php @@ -35,8 +35,8 @@ - - + + @@ -94,9 +94,9 @@ BLADE, 'A checkbox group at expanded' => <<<'BLADE' {{-- M3: from expanded (840px), gather related checkboxes into a contained region instead of one long column. --}} - +
- Tell me about + Tell me about @@ -112,7 +112,7 @@ 'Radio buttons' => <<<'BLADE' - + BLADE, 'Switches' => <<<'BLADE' @@ -156,7 +156,7 @@ - AR + AR
@@ -189,9 +189,9 @@
{{-- M3's search icon button: search as a secondary action, expanding into the full-screen view. --}} -
+ - Shares + Shares @@ -200,11 +200,11 @@ No shares match. -
+
BLADE, 'A form' => <<<'BLADE' - + diff --git a/resources/views/showcase/sections/menus.blade.php b/resources/views/showcase/sections/menus.blade.php index bb690b37..82d7aa8d 100644 --- a/resources/views/showcase/sections/menus.blade.php +++ b/resources/views/showcase/sections/menus.blade.php @@ -125,15 +125,16 @@ BLADE, 'Icons in their own colour' => <<<'BLADE' + {{-- icon-class takes the application's own class, unlayered so it wins over the item's ink: here .showcase-ink-tertiary { color: var(--md-sys-color-tertiary) }. A disabled item's icon stays disabled. --}} - - + + - + BLADE, ]; diff --git a/resources/views/showcase/sections/navigation.blade.php b/resources/views/showcase/sections/navigation.blade.php index 3e76b7d0..9aa09828 100644 --- a/resources/views/showcase/sections/navigation.blade.php +++ b/resources/views/showcase/sections/navigation.blade.php @@ -4,22 +4,22 @@

Compact: icon over label

-
-
The page
+ +

The page

-
+

From 600px wide: icon beside label, centred

-
-
-
The page
+ +
+

The page

@@ -27,26 +27,26 @@
-
+

Hide on scroll: scroll the page down, then up

-
-
The bar follows the window's scrolling, not this box's: scroll the showcase down and it leaves, scroll up and it springs back. It stays while a snackbar or a bottom sheet is on screen.
+ +

The bar follows the window's scrolling, not this box's: scroll the showcase down and it leaves, scroll up and it springs back. It stays while a snackbar or a bottom sheet is on screen.

-
+

Tall (80px): icon over label whatever the width

-
-
-
The page
+ +
+

The page

@@ -54,13 +54,13 @@
-
+ BLADE, 'Collapsed, narrow (centred) and expanded rails' => <<<'BLADE' -
+ @@ -78,9 +78,9 @@ -
+ -
+ @@ -94,9 +94,9 @@ -
+ -
+ @@ -114,11 +114,11 @@ -
+
BLADE, 'Collapsible rail' => <<<'BLADE' -
+ Mail @@ -133,13 +133,13 @@ -
+ The menu button collapses and expands the rail, and the FAB morphs into an extended FAB with it. The choice is remembered and applied before the next page paints — every collapsible rail follows it, <x-scaffold>'s from expanded (840px) too. -
-
+ + BLADE, 'Rail that hides when collapsed' => <<<'BLADE' -
+ Studio @@ -150,11 +150,13 @@ - -

M3's other expanded behaviour: collapsing this rail takes it out of the layout instead of narrowing it to 96px, so an immersive page gets the whole window. The only way back is a menu button of the application's own — its own button then docks it again.

- -
-
+ + +

M3's other expanded behaviour: collapsing this rail takes it out of the layout instead of narrowing it to 96px, so an immersive page gets the whole window. The only way back is a menu button of the application's own — its own button then docks it again.

+ +
+
+ BLADE, ]; @endphp diff --git a/resources/views/showcase/sections/pickers.blade.php b/resources/views/showcase/sections/pickers.blade.php index e03866d4..a2767cea 100644 --- a/resources/views/showcase/sections/pickers.blade.php +++ b/resources/views/showcase/sections/pickers.blade.php @@ -64,7 +64,7 @@ - + diff --git a/resources/views/showcase/sections/progress.blade.php b/resources/views/showcase/sections/progress.blade.php index 2b48701d..1698c976 100644 --- a/resources/views/showcase/sections/progress.blade.php +++ b/resources/views/showcase/sections/progress.blade.php @@ -20,7 +20,7 @@ - + BLADE, 'Colours' => <<<'BLADE' diff --git a/tests/Feature/ShowcaseTest.php b/tests/Feature/ShowcaseTest.php index 5d70a7c8..ca30e334 100644 --- a/tests/Feature/ShowcaseTest.php +++ b/tests/Feature/ShowcaseTest.php @@ -132,7 +132,19 @@ it('writes every example as an application writes it', function () { foreach ($examples as [$file, $code]) { // The example component rewrites unprefixed tags to a configured prefix; a namespaced tag // would show an application a form it does not write, and escape that rewrite. - expect($code)->not->toContain('x-livewire-material::', "{$file} writes a namespaced tag in an example"); + expect(str_contains($code, 'x-livewire-material::'))->toBeFalse("{$file} writes a namespaced tag in an example"); + + // Nor a hook only showcase.css draws, which would do nothing in the application's page. + expect(str_contains($code, 'data-md-showcase'))->toBeFalse("{$file} writes a showcase hook in an example"); + + // A class is a text or interaction class, or one of the application's own that + // showcase.css stands in for, each named in its header. + preg_match_all('/class="([^"]*)"/', $code, $classes); + + foreach (preg_split('/\s+/', implode(' ', $classes[1]), -1, PREG_SPLIT_NO_EMPTY) as $class) { + expect(str_starts_with($class, 'md-') || in_array($class, ['showcase-w-narrow', 'showcase-ink-tertiary', 'showcase-ink-secondary'], true)) + ->toBeTrue("{$file} writes the class `{$class}` in an example"); + } } }); diff --git a/tests/Feature/StylesheetsTest.php b/tests/Feature/StylesheetsTest.php index 44928f7b..537d590d 100644 --- a/tests/Feature/StylesheetsTest.php +++ b/tests/Feature/StylesheetsTest.php @@ -698,15 +698,32 @@ it('shapes showcase.css like a package stylesheet, with its documented unlayered ->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 + // showcase.css has no imports of its own (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, + // Deliberately unlayered, as an application's own classes are — see the file's header. '.showcase-w-narrow' => true, + '.showcase-ink-tertiary' => true, + '.showcase-ink-secondary' => true, ]); + // Bundled with all.css on every showcase page, so it styles its own hooks and classes only, + // and each of those is still drawn by some showcase view. + preg_match_all('/([^{};]+)\{/', preg_replace('/@(?:layer|media)\b[^{]*\{/', '', stylesheetWithoutComments($css)), $preludes); + $views = collect(File::allFiles(__DIR__.'/../../resources/views/showcase'))->map(fn ($file): string => $file->getContents())->implode("\n"); + + foreach ($preludes[1] as $prelude) { + foreach (array_map('trim', explode(',', $prelude)) as $selector) { + expect($selector)->toMatch('/^(?:\[data-md-showcase[\w-]*[\]=]|\.showcase-[\w-]+)/', "showcase.css selects `{$selector}`, outside the showcase's own hooks"); + } + } + + preg_match_all('/data-md-showcase(?:-\w+)*|\.showcase(?:-\w+)+/', stylesheetWithoutComments($css), $hooks); + + foreach (array_unique($hooks[0]) as $hook) { + expect(str_contains($views, ltrim($hook, '.')))->toBeTrue("showcase.css draws `{$hook}`, which no showcase view renders"); + } + preg_match_all('/@media\b([^{]*)\{/', stylesheetWithoutComments($css), $matches); foreach ($matches[1] as $query) {