Rewrite the showcase frame without Tailwind

Plan step 38 (first batch): layout.blade.php, index.blade.php,
section.blade.php, shell.blade.php and the example component now
lay out through the layout components (pane, stack, row, grid,
surface) and their props, text through the md-* classes, and
everything else through showcase.css's data-md-showcase-* hooks — no
Tailwind utility remains in any of them. The head links the new
stylesheet route after @vite(...), not before: that entry opens the
document's first @layer statement, and linking the showcase's own
bundle ahead of it re-anchors the material layer before Tailwind's
own and lets its preflight's `padding: 0` beat the search view's
padding-top (found by a failing browser test). @vite(...) stays, its
Tailwind CSS entry included, only for the section views (still
Tailwind until the next batches) and the JS runtime; the config
comment says so.

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 03:55:25 +02:00
co-authored by Claude Sonnet 5
parent 62b09c45b5
commit 9c8b39600b
6 changed files with 167 additions and 107 deletions
+8 -3
View File
@@ -191,9 +191,14 @@ return [
|--------------------------------------------------------------------------
|
| Every component in every variant, rendered in the application's own
| scheme. Off unless the application runs locally. 'vite' names the entry
| points that import this package's CSS and JavaScript; the error pages
| load them too, showcase or not.
| scheme. Off unless the application runs locally. 'vite' names the
| application's own entry points, for its JavaScript (Livewire, Alpine);
| the error pages load them too, showcase or not. A CSS entry here is
| temporary — the showcase's own chrome no longer needs one (it serves
| its own bundle, ShowcaseAssetController), but the section views
| (resources/views/showcase/sections/*) are still Tailwind until step
| 38's last batch removes it, and an application without Tailwind of
| its own may still want one here for the error pages' build.
|
*/
@@ -18,17 +18,30 @@
}
@endphp
<div @if ($title) id="{{ \NoNameWeb\LivewireMaterial\Showcase\Sections::anchor($title) }}" @endif class="scroll-mt-24 space-y-4 rounded-corner-lg bg-surface-container p-4">
@if ($title)
<h3 class="type-title-md">{{ $title }}</h3>
@endif
<x-livewire-material::surface
:id="$title ? \NoNameWeb\LivewireMaterial\Showcase\Sections::anchor($title) : null"
padding="space200"
corner="lg"
data-md-showcase-example
>
<x-livewire-material::stack gap="space200">
@if ($title)
<h3 class="md-type-title-md">{{ $title }}</h3>
@endif
<div @class(['flex flex-wrap items-center gap-4' => ! $stack, 'space-y-4' => $stack])>
{!! \Illuminate\Support\Facades\Blade::render($code) !!}
</div>
@if ($stack)
<x-livewire-material::stack gap="space200">
{!! \Illuminate\Support\Facades\Blade::render($code) !!}
</x-livewire-material::stack>
@else
<x-livewire-material::row gap="space200" wrap align="center">
{!! \Illuminate\Support\Facades\Blade::render($code) !!}
</x-livewire-material::row>
@endif
<details>
<summary class="w-fit cursor-pointer rounded-corner-xs type-label-md text-on-surface-variant focus-ring">Blade</summary>
<pre class="mt-2 overflow-x-auto rounded-corner-sm bg-surface-container-highest p-3 type-body-sm"><code>{{ trim($code) }}</code></pre>
</details>
</div>
<details>
<summary class="md-type-label-md md-ink-variant md-focus-ring" data-md-showcase-code-summary>Blade</summary>
<pre class="md-type-body-sm" data-md-showcase-code><code>{{ trim($code) }}</code></pre>
</details>
</x-livewire-material::stack>
</x-livewire-material::surface>
+32 -30
View File
@@ -1,36 +1,38 @@
@extends('livewire-material::showcase.layout')
@section('content')
<div class="mx-auto w-full max-w-6xl space-y-12 pt-4 pb-16">
<div class="max-w-3xl space-y-3">
<h1 class="type-headline-lg">Livewire Material</h1>
<p class="type-title-lg">Material 3 Expressive for Laravel and Livewire.</p>
<p class="type-body-lg text-on-surface-variant">
Every token and component, rendered in this application's own scheme and theme. Pick a section in the
navigation, search for one above (or press <kbd class="rounded-corner-xs bg-surface-container-highest px-1.5 type-label-md">/</kbd>), or start below.
</p>
</div>
<x-livewire-material::pane width="wide" data-md-showcase-page>
<x-livewire-material::stack gap="space600">
<x-livewire-material::stack gap="space150">
<h1 class="md-type-headline-lg">Livewire Material</h1>
<p class="md-type-title-lg">Material 3 Expressive for Laravel and Livewire.</p>
<p class="md-type-body-lg md-ink-variant">
Every token and component, rendered in this application's own scheme and theme. Pick a section in the
navigation, search for one above (or press <kbd class="md-type-label-md" data-md-showcase-kbd>/</kbd>), or start below.
</p>
</x-livewire-material::stack>
@foreach (collect($sections)->groupBy('group', preserveKeys: true) as $group => $entries)
<section class="space-y-4" aria-labelledby="group-{{ \Illuminate\Support\Str::slug($group) }}">
<h2 id="group-{{ \Illuminate\Support\Str::slug($group) }}" class="type-title-lg">{{ $group }}</h2>
@foreach (collect($sections)->groupBy('group', preserveKeys: true) as $group => $entries)
<x-livewire-material::stack as="section" gap="space200" :aria-labelledby="'group-'.\Illuminate\Support\Str::slug($group)">
<h2 id="group-{{ \Illuminate\Support\Str::slug($group) }}" class="md-type-title-lg">{{ $group }}</h2>
<div class="grid grid-cols-1 gap-3 medium:grid-cols-2 expanded:grid-cols-3">
@foreach ($entries as $key => $entry)
<x-livewire-material::card variant="outlined" data-md-list-row wire:key="section-{{ $key }}">
<div class="flex items-start gap-4">
<span class="grid size-12 shrink-0 place-items-center rounded-corner-lg bg-secondary-container text-on-secondary-container">
<x-livewire-material::icon :name="$entry['icon']" />
</span>
<div class="min-w-0">
<a href="{{ route('livewire-material.section', $key) }}" wire:navigate data-md-list-open class="type-title-md">{{ $entry['title'] }}</a>
<p class="mt-1 type-body-md text-on-surface-variant">{{ $entry['description'] }}</p>
</div>
</div>
</x-livewire-material::card>
@endforeach
</div>
</section>
@endforeach
</div>
<x-livewire-material::grid :columns="['compact' => 1, 'medium' => 2, 'expanded' => 3]" gap="space200">
@foreach ($entries as $key => $entry)
<x-livewire-material::card variant="outlined" data-md-list-row wire:key="section-{{ $key }}">
<x-livewire-material::row gap="space200" align="start">
<span data-md-showcase-section-icon>
<x-livewire-material::icon :name="$entry['icon']" />
</span>
<x-livewire-material::stack gap="space50">
<a href="{{ route('livewire-material.section', $key) }}" wire:navigate data-md-list-open class="md-type-title-md">{{ $entry['title'] }}</a>
<p class="md-type-body-md md-ink-variant">{{ $entry['description'] }}</p>
</x-livewire-material::stack>
</x-livewire-material::row>
</x-livewire-material::card>
@endforeach
</x-livewire-material::grid>
</x-livewire-material::stack>
@endforeach
</x-livewire-material::stack>
</x-livewire-material::pane>
@endsection
+41 -23
View File
@@ -38,26 +38,37 @@
<x-livewire-material::theme-script />
{{-- Kept only for the section views (resources/views/showcase/sections/*), still Tailwind
until the next batches of step 38, and for the Livewire/Alpine runtime; its CSS entry
goes with them. It opens the document's first @layer statement (Workbench package.css's
header explains the trick), which the link below must follow, not precede — first would
re-anchor `material` ahead of Tailwind's own layers and let its preflight's `padding: 0`
beat the search view's padding-top. --}}
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
{{-- The showcase's own chrome: does not depend on the build above. --}}
<link rel="stylesheet" href="{{ \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::url() }}" />
</head>
<body class="bg-surface font-sans text-on-surface antialiased">
<body data-md-showcase>
<x-livewire-material::scaffold :destinations="$destinations" label="Showcase" rail-width="17rem">
<x-slot:brand>
<a href="{{ route('livewire-material.showcase') }}" wire:navigate class="block truncate rounded-corner-xs type-title-lg focus-ring">Livewire Material</a>
<a href="{{ route('livewire-material.showcase') }}" wire:navigate class="md-type-title-lg md-truncate md-focus-ring" data-md-showcase-brand>Livewire Material</a>
</x-slot:brand>
<x-slot:top>
<x-livewire-material::app-bar variant="search">
<x-slot:navigation>
<span class="medium:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="showcase-menu" /></span>
<x-livewire-material::stack as="span" hide-from="medium">
<x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="showcase-menu" />
</x-livewire-material::stack>
</x-slot:navigation>
{{-- The search looks through every section, example and component: the index is
fetched on first focus, and / or Ctrl+K (⌘K) reaches it from anywhere. Its names
keep clear of <x-search>'s own (`results`, `open`), which the slot also sees. --}}
<div
class="w-full"
data-md-showcase-search
x-data="{
query: '',
entries: null,
@@ -111,42 +122,49 @@
x-on:focus.once="load()"
x-on:keydown.enter.prevent="go($event, matches[0])"
>
<p x-show="query.trim() === ''" class="px-4 py-3 type-body-md text-on-surface-variant">
Type a component (<code>datepicker</code>), an example or a section. Press <kbd class="rounded-corner-xs bg-surface-container-highest px-1.5 type-label-md">/</kbd> to search from anywhere.
<p x-show="query.trim() === ''" class="md-type-body-md md-ink-variant" data-md-showcase-search-hint>
Type a component (<code>datepicker</code>), an example or a section. Press <kbd class="md-type-label-md" data-md-showcase-kbd>/</kbd> to search from anywhere.
</p>
<p x-cloak x-show="query.trim() !== '' && entries !== null && matches.length === 0" class="px-4 py-3 type-body-md text-on-surface-variant">
<p x-cloak x-show="query.trim() !== '' && entries !== null && matches.length === 0" class="md-type-body-md md-ink-variant" data-md-showcase-search-hint>
Nothing matches <span x-text="query.trim()"></span>.
</p>
<template x-for="result in matches" x-bind:key="result.url + result.title">
<a
x-bind:href="result.url"
x-on:click="go($event, result)"
class="state-layer focus-ring flex min-h-14 flex-col justify-center px-4 py-2 outline-none"
class="md-state-layer md-focus-ring"
data-showcase-result
data-md-showcase-search-result
>
<span class="truncate type-body-lg text-on-surface" x-text="result.title"></span>
<span class="truncate type-body-sm text-on-surface-variant" x-text="`${result.kind} · ${result.context}`"></span>
<span class="md-type-body-lg md-ink md-truncate" x-text="result.title"></span>
<span class="md-type-body-sm md-ink-variant md-truncate" x-text="`${result.kind} · ${result.context}`"></span>
</a>
</template>
</x-livewire-material::search>
</div>
<x-slot:actions>
{{-- With colour profiles, a preview of each on this page; nothing is stored. --}}
@if (($profiles = \NoNameWeb\LivewireMaterial\Support\Scheme::profiles()) !== [])
<x-livewire-material::menu label="Colour profile" position="bottom-end" data-test="showcase-profiles">
<x-slot:trigger>
<x-livewire-material::button icon="palette" tooltip="Colour profile" />
</x-slot:trigger>
<x-livewire-material::row gap="space100" align="center">
{{-- With colour profiles, a preview of each on this page; nothing is stored. --}}
@if (($profiles = \NoNameWeb\LivewireMaterial\Support\Scheme::profiles()) !== [])
<x-livewire-material::menu label="Colour profile" position="bottom-end" data-test="showcase-profiles">
<x-slot:trigger>
<x-livewire-material::button icon="palette" tooltip="Colour profile" />
</x-slot:trigger>
@foreach ($profiles as $name => $profile)
<x-livewire-material::menu-item :label="$profile['label']" x-on:click="$store.theme.previewScheme('{{ $name }}')" data-scheme-preview="{{ $name }}" />
@endforeach
</x-livewire-material::menu>
@endif
@foreach ($profiles as $name => $profile)
<x-livewire-material::menu-item :label="$profile['label']" x-on:click="$store.theme.previewScheme('{{ $name }}')" data-scheme-preview="{{ $name }}" />
@endforeach
</x-livewire-material::menu>
@endif
<span class="ms-2 me-3 max-expanded:hidden"><x-livewire-material::theme-toggle mode="picker" /></span>
<span class="expanded:hidden"><x-livewire-material::theme-toggle mode="cycle" /></span>
<x-livewire-material::stack as="span" hide-below="expanded">
<x-livewire-material::theme-toggle mode="picker" />
</x-livewire-material::stack>
<x-livewire-material::stack as="span" hide-from="expanded">
<x-livewire-material::theme-toggle mode="cycle" />
</x-livewire-material::stack>
</x-livewire-material::row>
</x-slot:actions>
</x-livewire-material::app-bar>
</x-slot:top>
+27 -20
View File
@@ -1,5 +1,6 @@
{{-- One section of the showcase on a page of its own, with the way on to the next. The page's
heading names the section, so the section's own heading is only for a screen reader. --}}
heading names the section, so the section's own heading is only for a screen reader
(`data-md-showcase-sections`'s own rule in resources/css/showcase.css). --}}
@extends('livewire-material::showcase.layout')
@@ -11,26 +12,32 @@
@endphp
@section('content')
<div class="mx-auto w-full max-w-6xl pt-4 pb-16">
<header class="mb-6 space-y-1">
<p class="type-label-lg text-on-surface-variant">{{ $sections[$section]['group'] }}</p>
<h1 class="type-headline-lg">{{ $sections[$section]['title'] }}</h1>
</header>
<x-livewire-material::pane width="wide" data-md-showcase-page>
<x-livewire-material::stack gap="space300">
<x-livewire-material::stack as="header" gap="space50">
<p class="md-type-label-lg md-ink-variant">{{ $sections[$section]['group'] }}</p>
<h1 class="md-type-headline-lg">{{ $sections[$section]['title'] }}</h1>
</x-livewire-material::stack>
<div class="space-y-16 [&>section>h2]:sr-only">
@include('livewire-material::showcase.sections.'.$section)
<x-livewire-material::stack gap="space800" data-md-showcase-sections>
@include('livewire-material::showcase.sections.'.$section)
<nav aria-label="Sections" class="flex flex-wrap items-center justify-between gap-4 border-t border-divider pt-6">
@if ($previous)
<x-livewire-material::button variant="text" icon="arrow_back" :label="$sections[$previous]['title']" :link="route('livewire-material.section', $previous)" data-test="previous-section" />
@else
<x-livewire-material::button variant="text" icon="arrow_back" label="Overview" :link="route('livewire-material.showcase')" data-test="previous-section" />
@endif
<x-livewire-material::stack gap="space300">
<x-livewire-material::divider decorative />
@if ($next)
<x-livewire-material::button variant="tonal" icon-right="arrow_forward" :label="$sections[$next]['title']" :link="route('livewire-material.section', $next)" data-test="next-section" />
@endif
</nav>
</div>
</div>
<x-livewire-material::row as="nav" aria-label="Sections" justify="between" wrap gap="space200">
@if ($previous)
<x-livewire-material::button variant="text" icon="arrow_back" :label="$sections[$previous]['title']" :link="route('livewire-material.section', $previous)" data-test="previous-section" />
@else
<x-livewire-material::button variant="text" icon="arrow_back" label="Overview" :link="route('livewire-material.showcase')" data-test="previous-section" />
@endif
@if ($next)
<x-livewire-material::button variant="tonal" icon-right="arrow_forward" :label="$sections[$next]['title']" :link="route('livewire-material.section', $next)" data-test="next-section" />
@endif
</x-livewire-material::row>
</x-livewire-material::stack>
</x-livewire-material::stack>
</x-livewire-material::stack>
</x-livewire-material::pane>
@endsection
+34 -19
View File
@@ -35,13 +35,20 @@
<x-livewire-material::theme-script />
{{-- Kept only for the Livewire/Alpine runtime here this page has no Tailwind class of its
own. The next batches of step 38 leave it to the sections that still need it. It opens
the document's first @layer statement (Workbench package.css's header explains the
trick), which the link below must follow, not precede see layout.blade.php's comment. --}}
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
{{-- The showcase's own chrome: does not depend on the build above. --}}
<link rel="stylesheet" href="{{ \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::url() }}" />
</head>
<body class="bg-surface font-sans text-on-surface antialiased">
<body data-md-showcase>
<x-livewire-material::scaffold :destinations="$destinations">
<x-slot:brand>
<a href="{{ route('livewire-material.showcase') }}" class="block truncate rounded-corner-xs type-title-lg focus-ring">Livewire Material</a>
<a href="{{ route('livewire-material.showcase') }}" class="md-type-title-lg md-truncate md-focus-ring" data-md-showcase-brand>Livewire Material</a>
</x-slot:brand>
<x-slot:rail-header>
@@ -58,27 +65,35 @@
</x-slot:actions>
<x-slot:top>
<header class="sticky top-0 z-20 flex h-16 items-center gap-1 bg-surface px-1 pt-[var(--material-safe-top,env(safe-area-inset-top))] medium:px-4">
<span class="medium:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-menu" /></span>
<h1 class="min-w-0 flex-1 truncate px-3 type-title-lg medium:px-0">{{ $current['title'] }}</h1>
<x-livewire-material::button icon="search" tooltip="Search" />
</header>
<x-livewire-material::app-bar :title="$current['title']">
<x-slot:navigation>
<x-livewire-material::stack as="span" hide-from="medium">
<x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-menu" />
</x-livewire-material::stack>
</x-slot:navigation>
<x-slot:actions>
<x-livewire-material::button icon="search" tooltip="Search" />
</x-slot:actions>
</x-livewire-material::app-bar>
</x-slot:top>
<div class="mx-auto w-full max-w-5xl space-y-4 pb-6">
<p class="type-body-md text-on-surface-variant" data-test="shell-page">This is the {{ strtolower($current['title']) }} page.</p>
<x-livewire-material::pane width="medium" data-md-showcase-page>
<x-livewire-material::stack gap="space200">
<p class="md-type-body-md md-ink-variant" data-test="shell-page">This is the {{ strtolower($current['title']) }} page.</p>
<x-livewire-material::list segmented>
@foreach (range(1, 14) as $index)
<x-livewire-material::list-item :title="$current['title'].' item '.$index" description="A line of supporting text for this item" :icon="$current['icon']" trailing="{{ $index }}h" wire:key="item-{{ $index }}" />
@endforeach
</x-livewire-material::list>
<x-livewire-material::list segmented>
@foreach (range(1, 14) as $index)
<x-livewire-material::list-item :title="$current['title'].' item '.$index" description="A line of supporting text for this item" :icon="$current['icon']" trailing="{{ $index }}h" wire:key="item-{{ $index }}" />
@endforeach
</x-livewire-material::list>
<div class="flex flex-wrap gap-2">
<x-livewire-material::button label="Show a toast" variant="tonal" x-data x-on:click="materialToast('Moved to archive', { action: { label: 'Undo', handler: () => {} } })" />
<x-livewire-material::button label="Back to the showcase" link="{{ route('livewire-material.section', 'navigation') }}" no-wire-navigate />
</div>
</div>
<x-livewire-material::row gap="space100" wrap>
<x-livewire-material::button label="Show a toast" variant="tonal" x-data x-on:click="materialToast('Moved to archive', { action: { label: 'Undo', handler: () => {} } })" />
<x-livewire-material::button label="Back to the showcase" link="{{ route('livewire-material.section', 'navigation') }}" no-wire-navigate />
</x-livewire-material::row>
</x-livewire-material::stack>
</x-livewire-material::pane>
</x-livewire-material::scaffold>
@livewireScripts