Write the showcase examples without showcase-only hooks

Plan step 38 review: the component sections' code samples, which an
application copies, carried 60 data-md-showcase-* hooks that only
showcase.css draws (a sized loading indicator, the plain badge's own
colours, a bordered bar frame, the navigation demos' frames, a table's
scroll wrapper...), so a copied sample did nothing in the application.
They now write what an application has: <x-surface outlined corner>
for a frame and a line, <x-row> for the FAB menu's corner, and an
inline style from the tokens for a size, a scroll box or a colour,
as the Containment batch already did. The side sheet demo takes
align="stretch", which holds in both of <x-row stack-below>'s modes,
instead of a hook that undid align="start" once stacked. The menu
icon colours go through icon-class with two unlayered application-like
classes, which the sample's comment names, and the fields' and date
picker's widths are inline styles now that style reaches their roots.
The radio hint said "from sm"; it is medium.

showcase.css keeps only the frame and the foundation specimens; its
header describes the file instead of the batches that wrote it. The
tests reject a showcase hook or a class outside md-* and the named
application classes in any example, a showcase.css selector outside
its own hooks, and a hook no showcase view renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 06:21:18 +02:00
co-authored by Claude Opus 5
parent cd47aa4b0f
commit ebdcdb82b5
15 changed files with 152 additions and 374 deletions
+32 -286
View File
@@ -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 `<x-row>` 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: `<x-input>` forwards `style` to the raw `<input>`, not the field wrapper that is
* actually sized, and `<x-datepicker>` 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). `<x-slider>`
* 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 — `<x-row>`'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);
}