Add the Material 3 Expressive foundation
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m0s
tests / feature (8.5) (push) Successful in 1m1s
tests / browser (safari, webkit) (push) Successful in 1m59s
tests / browser (chrome, chromium) (push) Successful in 1m49s
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m0s
tests / feature (8.5) (push) Successful in 1m1s
tests / browser (safari, webkit) (push) Successful in 1m59s
tests / browser (chrome, chromium) (push) Successful in 1m49s
tests / browser (firefox, firefox) (push) Successful in 1m54s
Colour, shape, type, elevation and motion as tokens and Tailwind utilities; `php artisan material:scheme`, which generates an app's colour roles with Google's material-color-utilities (spec 2025); the theme head script with light, dark and system and its Alpine store; Google Sans Flex; every Material Symbol (4,135, outlined and filled) drawn by <x-icon> without blade-icons; all 35 M3 Expressive shapes, ported from androidx, as <x-shape>; the x-figure directive; the Toasts concern; DesignGuard for applications' tests; and a showcase with every token, both themes side by side and an icon search. Colour utilities are `@theme inline`, so a section with its own data-theme repaints; without it they resolve once on :root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
9b53891a8e
commit
b48e879254
@@ -1,12 +1,16 @@
|
||||
@extends('livewire-material::showcase.layout')
|
||||
|
||||
@section('content')
|
||||
<main class="mx-auto max-w-5xl px-4 py-10">
|
||||
<h1 class="text-3xl">Livewire Material</h1>
|
||||
|
||||
<p class="mt-2">
|
||||
Every component, in every variant, colour, size and state, in this application's own scheme.
|
||||
Sections appear here as the components land.
|
||||
<main class="mx-auto max-w-6xl space-y-16 px-4 py-10">
|
||||
<p class="max-w-3xl type-body-lg text-on-surface-variant">
|
||||
Every token and component, in this application's own scheme. Components appear here as they land.
|
||||
</p>
|
||||
|
||||
@include('livewire-material::showcase.sections.colour')
|
||||
@include('livewire-material::showcase.sections.type')
|
||||
@include('livewire-material::showcase.sections.shape')
|
||||
@include('livewire-material::showcase.sections.elevation')
|
||||
@include('livewire-material::showcase.sections.motion')
|
||||
@include('livewire-material::showcase.sections.icons')
|
||||
</main>
|
||||
@endsection
|
||||
|
||||
@@ -7,10 +7,37 @@
|
||||
|
||||
<title>@yield('title', 'Livewire Material')</title>
|
||||
|
||||
<x-theme-script />
|
||||
|
||||
@vite(config('livewire-material.showcase.vite'))
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="min-h-screen antialiased">
|
||||
<body class="min-h-screen bg-surface font-sans text-on-surface antialiased">
|
||||
<header class="sticky top-0 z-10 border-b border-divider bg-surface-container">
|
||||
<div class="mx-auto flex max-w-6xl flex-wrap items-center gap-x-6 gap-y-2 px-4 py-3">
|
||||
<a href="{{ route('livewire-material.showcase') }}" class="type-title-lg">Livewire Material</a>
|
||||
|
||||
<nav class="flex flex-wrap gap-x-4 gap-y-1 type-label-lg text-on-surface-variant" aria-label="Sections">
|
||||
@foreach (['colour' => 'Colour', 'type' => 'Type', 'shape' => 'Shape', 'elevation' => 'Elevation', 'motion' => 'Motion', 'icons' => 'Icons'] as $anchor => $section)
|
||||
<a href="#{{ $anchor }}" class="rounded-corner-xs hover:text-on-surface focus-ring">{{ $section }}</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="ms-auto flex rounded-corner-full border border-outline" role="group" aria-label="Theme" x-data>
|
||||
@foreach (['light' => 'Light', 'dark' => 'Dark', 'system' => 'System'] as $choice => $label)
|
||||
<button
|
||||
type="button"
|
||||
data-test="theme-{{ $choice }}"
|
||||
class="state-layer focus-ring px-4 py-1.5 type-label-lg first:rounded-s-corner-full last:rounded-e-corner-full"
|
||||
x-on:click="$store.theme.set('{{ $choice }}')"
|
||||
x-bind:class="$store.theme.choice === '{{ $choice }}' && 'bg-secondary-container text-on-secondary-container'"
|
||||
x-bind:aria-pressed="($store.theme.choice === '{{ $choice }}').toString()"
|
||||
>{{ $label }}</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@yield('content')
|
||||
|
||||
@livewireScripts
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
@php
|
||||
$groups = [
|
||||
'Primary' => ['bg-primary', 'bg-on-primary', 'bg-primary-container', 'bg-on-primary-container', 'bg-inverse-primary', 'bg-primary-fixed', 'bg-primary-fixed-dim', 'bg-on-primary-fixed', 'bg-on-primary-fixed-variant'],
|
||||
'Secondary' => ['bg-secondary', 'bg-on-secondary', 'bg-secondary-container', 'bg-on-secondary-container', 'bg-secondary-fixed', 'bg-secondary-fixed-dim', 'bg-on-secondary-fixed', 'bg-on-secondary-fixed-variant'],
|
||||
'Tertiary' => ['bg-tertiary', 'bg-on-tertiary', 'bg-tertiary-container', 'bg-on-tertiary-container', 'bg-tertiary-fixed', 'bg-tertiary-fixed-dim', 'bg-on-tertiary-fixed', 'bg-on-tertiary-fixed-variant'],
|
||||
'Error' => ['bg-error', 'bg-on-error', 'bg-error-container', 'bg-on-error-container', 'bg-inverse-error'],
|
||||
'Success' => ['bg-success', 'bg-on-success', 'bg-success-container', 'bg-on-success-container', 'bg-inverse-success'],
|
||||
'Warning' => ['bg-warning', 'bg-on-warning', 'bg-warning-container', 'bg-on-warning-container', 'bg-inverse-warning'],
|
||||
'Info' => ['bg-info', 'bg-on-info', 'bg-info-container', 'bg-on-info-container', 'bg-inverse-info'],
|
||||
'Surface' => ['bg-surface', 'bg-surface-dim', 'bg-surface-bright', 'bg-surface-container-lowest', 'bg-surface-container-low', 'bg-surface-container', 'bg-surface-container-high', 'bg-surface-container-highest', 'bg-on-surface', 'bg-on-surface-variant', 'bg-inverse-surface', 'bg-inverse-on-surface', 'bg-outline', 'bg-outline-variant'],
|
||||
'Ink and lines' => ['bg-body', 'bg-meta', 'bg-quiet', 'bg-structure', 'bg-chrome', 'bg-divider'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<section id="colour" class="scroll-mt-24 space-y-6">
|
||||
<h2 class="type-headline-md">Colour</h2>
|
||||
|
||||
<p class="max-w-3xl type-body-md text-on-surface-variant">
|
||||
Every role as <code>bg-*</code>, <code>text-*</code> and <code>border-*</code>, generated by
|
||||
<code>php artisan material:scheme</code>. Both themes side by side, whatever the page is showing.
|
||||
</p>
|
||||
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
@foreach (['light', 'dark'] as $theme)
|
||||
<div data-theme="{{ $theme }}" class="space-y-6 rounded-corner-lg bg-surface p-4 text-on-surface">
|
||||
<h3 class="type-title-md capitalize">{{ $theme }}</h3>
|
||||
|
||||
@foreach ($groups as $group => $roles)
|
||||
<div class="space-y-2">
|
||||
<h4 class="type-label-lg text-on-surface-variant">{{ $group }}</h4>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
@foreach ($roles as $role)
|
||||
<div class="flex items-center gap-2">
|
||||
<span @class(['size-8 shrink-0 rounded-corner-sm border border-outline-variant', $role])></span>
|
||||
<span class="min-w-0 truncate type-body-sm">{{ \Illuminate\Support\Str::after($role, 'bg-') }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,15 @@
|
||||
<section id="elevation" class="scroll-mt-24 space-y-6">
|
||||
<h2 class="type-headline-md">Elevation</h2>
|
||||
|
||||
<p class="max-w-3xl type-body-md text-on-surface-variant">
|
||||
Five shadow levels as <code>shadow-elevation-*</code>, for what floats over content.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-6 rounded-corner-lg bg-surface-container-low p-6 sm:grid-cols-5">
|
||||
@foreach (['shadow-elevation-1', 'shadow-elevation-2', 'shadow-elevation-3', 'shadow-elevation-4', 'shadow-elevation-5'] as $shadow)
|
||||
<div @class(['flex h-24 items-center justify-center rounded-corner-md bg-surface-container-high', $shadow])>
|
||||
<code class="type-label-md">{{ $shadow }}</code>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,47 @@
|
||||
<section
|
||||
id="icons"
|
||||
class="scroll-mt-24 space-y-6"
|
||||
x-data="{
|
||||
names: {{ json_encode(\NoNameWeb\LivewireMaterial\Support\SvgFile::symbolNames()) }},
|
||||
base: '{{ rtrim(route('livewire-material.showcase', [], false), '/') }}/symbols',
|
||||
query: '',
|
||||
filled: false,
|
||||
get matches() {
|
||||
const query = this.query.trim().toLowerCase().replaceAll(' ', '_');
|
||||
|
||||
return (query === '' ? this.names : this.names.filter((name) => name.includes(query))).slice(0, 120);
|
||||
},
|
||||
}"
|
||||
>
|
||||
<h2 class="type-headline-md">Icons</h2>
|
||||
|
||||
<p class="max-w-3xl type-body-md text-on-surface-variant">
|
||||
Every Material Symbol (Rounded, 400, grade 0, 24px) as <code><x-icon name="…" /></code>, outlined or <code>filled</code>.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<label class="flex min-w-72 flex-1 items-center gap-2 rounded-corner-xs border border-outline px-3 py-2 focus-within:outline-3 focus-within:outline-secondary">
|
||||
<x-icon name="search" class="size-5 text-on-surface-variant" />
|
||||
<input type="search" x-model.debounce.150ms="query" placeholder="Search {{ number_format(count(\NoNameWeb\LivewireMaterial\Support\SvgFile::symbolNames())) }} symbols" class="w-full bg-transparent type-body-lg outline-none" />
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 type-label-lg">
|
||||
<input type="checkbox" x-model="filled" class="size-4 accent-primary" />
|
||||
Filled
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<ul class="grid grid-cols-[repeat(auto-fill,minmax(7.5rem,1fr))] gap-2">
|
||||
<template x-for="name in matches" x-bind:key="name">
|
||||
<li class="flex flex-col items-center gap-2 rounded-corner-md bg-surface-container p-3 text-center">
|
||||
<span
|
||||
class="size-6 bg-current"
|
||||
x-bind:style="{
|
||||
mask: `url('${base}/${filled ? 'filled' : 'outlined'}/${name}.svg') center / contain no-repeat`,
|
||||
}"
|
||||
></span>
|
||||
<code class="w-full break-all type-label-sm text-on-surface-variant" x-text="name"></code>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -0,0 +1,35 @@
|
||||
<section id="motion" class="scroll-mt-24 space-y-6" x-data="{ moved: false, figure: 1204 }">
|
||||
<h2 class="type-headline-md">Motion</h2>
|
||||
|
||||
<p class="max-w-3xl type-body-md text-on-surface-variant">
|
||||
M3 Expressive's springs as <code>ease-spatial-*</code> and <code>ease-effects-*</code>, each with its
|
||||
<code>--md-sys-motion-*-duration</code>. Reduced motion makes all of them instant.
|
||||
</p>
|
||||
|
||||
<button type="button" class="state-layer focus-ring rounded-corner-full bg-primary px-6 py-2.5 type-label-lg text-on-primary" x-on:click="moved = ! moved">
|
||||
Move
|
||||
</button>
|
||||
|
||||
<div class="space-y-3 rounded-corner-lg bg-surface-container p-4">
|
||||
@foreach (['spatial-fast', 'spatial-default', 'spatial-slow', 'effects-fast', 'effects-default', 'effects-slow'] as $spring)
|
||||
<div class="grid grid-cols-[9rem_1fr] items-center gap-4">
|
||||
<code class="type-label-md text-on-surface-variant">ease-{{ $spring }}</code>
|
||||
<div class="h-8">
|
||||
<div
|
||||
class="size-8 rounded-corner-full bg-tertiary"
|
||||
style="transition: translate var(--md-sys-motion-{{ $spring }}-duration) var(--md-sys-motion-{{ $spring }})"
|
||||
x-bind:style="moved && { translate: '16rem' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4 rounded-corner-lg bg-surface-container p-4">
|
||||
<p class="type-emphasized-display-sm tabular-nums" x-figure x-text="figure.toLocaleString('en') + ' files'">1,204 files</p>
|
||||
<button type="button" class="state-layer focus-ring rounded-corner-full px-4 py-2 type-label-lg text-primary" x-on:click="figure = Math.round(Math.random() * 5000)">
|
||||
Change the figure
|
||||
</button>
|
||||
<code class="type-label-md text-on-surface-variant">x-figure</code>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,25 @@
|
||||
<section id="shape" class="scroll-mt-24 space-y-6">
|
||||
<h2 class="type-headline-md">Shape</h2>
|
||||
|
||||
<p class="max-w-3xl type-body-md text-on-surface-variant">
|
||||
The corner scale as <code>rounded-corner-*</code>, and M3 Expressive's shapes as <code><x-shape name="…" /></code>.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-5">
|
||||
@foreach (['rounded-corner-none', 'rounded-corner-xs', 'rounded-corner-sm', 'rounded-corner-md', 'rounded-corner-lg', 'rounded-corner-lg-increased', 'rounded-corner-xl', 'rounded-corner-xl-increased', 'rounded-corner-xxl', 'rounded-corner-full'] as $corner)
|
||||
<div class="space-y-2">
|
||||
<div @class(['h-20 bg-primary-container', $corner])></div>
|
||||
<code class="type-label-md text-on-surface-variant">{{ $corner }}</code>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4 sm:grid-cols-5 lg:grid-cols-7">
|
||||
@foreach (\NoNameWeb\LivewireMaterial\Support\SvgFile::shapeNames() as $shape)
|
||||
<div class="space-y-2 text-center">
|
||||
<x-shape :name="$shape" class="mx-auto size-20 text-secondary-container" />
|
||||
<code class="type-label-md text-on-surface-variant">{{ $shape }}</code>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,37 @@
|
||||
@php
|
||||
$styles = [
|
||||
['type-display-lg', 'type-emphasized-display-lg'],
|
||||
['type-display-md', 'type-emphasized-display-md'],
|
||||
['type-display-sm', 'type-emphasized-display-sm'],
|
||||
['type-headline-lg', 'type-emphasized-headline-lg'],
|
||||
['type-headline-md', 'type-emphasized-headline-md'],
|
||||
['type-headline-sm', 'type-emphasized-headline-sm'],
|
||||
['type-title-lg', 'type-emphasized-title-lg'],
|
||||
['type-title-md', 'type-emphasized-title-md'],
|
||||
['type-title-sm', 'type-emphasized-title-sm'],
|
||||
['type-body-lg', 'type-emphasized-body-lg'],
|
||||
['type-body-md', 'type-emphasized-body-md'],
|
||||
['type-body-sm', 'type-emphasized-body-sm'],
|
||||
['type-label-lg', 'type-emphasized-label-lg'],
|
||||
['type-label-md', 'type-emphasized-label-md'],
|
||||
['type-label-sm', 'type-emphasized-label-sm'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<section id="type" class="scroll-mt-24 space-y-6">
|
||||
<h2 class="type-headline-md">Type</h2>
|
||||
|
||||
<p class="max-w-3xl type-body-md text-on-surface-variant">
|
||||
M3's typescale in Google Sans Flex, each style regular and emphasized: <code>type-title-lg</code>, <code>type-emphasized-title-lg</code>.
|
||||
</p>
|
||||
|
||||
<div class="divide-y divide-divider rounded-corner-lg bg-surface-container">
|
||||
@foreach ($styles as [$regular, $emphasized])
|
||||
<div class="grid gap-2 p-4 md:grid-cols-[12rem_1fr_1fr] md:items-baseline">
|
||||
<code class="type-label-md text-on-surface-variant">{{ $regular }}</code>
|
||||
<p @class([$regular, 'truncate'])>Share files, safely</p>
|
||||
<p @class([$emphasized, 'truncate'])>Share files, safely</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user