Add buttons, menus and the rest of M3 Expressive's actions
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s

<x-button> (label buttons, icon buttons and toggles in five sizes, with
filled, tonal, outlined, elevated and text variants in any colour role),
<x-tooltip>, <x-menu> with items, groups and separators, <x-button-group>,
<x-group> as a connected button group, <x-split-button>, <x-fab>,
<x-fab-menu> and <x-loading>. Sizes, colours and shapes come from
androidx Compose Material 3's tokens; the loading indicator ports its
Morph into SVG + SMIL.

Menus follow WAI-ARIA's menu button pattern on popovers placed by CSS
anchor positioning. Browser tests run in Chromium, Firefox and WebKit.
The showcase fetches the icon names on demand: inlined, they tripped
Pest's test server into HTTP 431s under Firefox.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
Andreas Reinhold / reini
2026-09-13 06:09:28 +02:00
co-authored by Claude Opus 5
parent b48e879254
commit cd64f4f371
46 changed files with 2968 additions and 36 deletions
@@ -2,18 +2,25 @@
namespace NoNameWeb\LivewireMaterial\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Illuminate\View\ComponentAttributeBag;
use InvalidArgumentException;
use NoNameWeb\LivewireMaterial\Support\SvgFile;
/**
* One Material Symbol as an SVG file, for the showcase's icon search: drawing the matches as
* CSS masks keeps 4,000 names searchable without rendering 4,000 inline SVGs.
* Material Symbols for the showcase's icon search: the list of names, fetched when the section
* comes into view rather than inlined into the page, and each symbol as an SVG file, drawn as a
* CSS mask, so 4,000 names stay searchable without 4,000 inline SVGs.
*/
class ShowcaseSymbolController
{
public function __invoke(string $style, string $name): Response
public function index(): JsonResponse
{
return response()->json(SvgFile::symbolNames())->setMaxAge(86400)->setPublic();
}
public function show(string $style, string $name): Response
{
abort_unless(in_array($style, ['outlined', 'filled'], true), 404);
+7 -1
View File
@@ -64,7 +64,13 @@ class LivewireMaterialServiceProvider extends ServiceProvider
*/
protected function registerShowcase(): void
{
if (! config('livewire-material.showcase.enabled') || $this->app->routesAreCached()) {
if (! config('livewire-material.showcase.enabled')) {
return;
}
Blade::anonymousComponentPath(__DIR__.'/../resources/views/showcase/components', 'showcase');
if ($this->app->routesAreCached()) {
return;
}
+8
View File
@@ -45,6 +45,14 @@ class SvgFile
return static::render($path, $attributes);
}
/**
* M3 Expressive's loading indicator: the morphing, rotating shape, or the shape at rest.
*/
public static function loadingIndicator(bool $animated, ComponentAttributeBag $attributes): HtmlString
{
return static::render(static::directory('svg/loading-indicator/'.($animated ? 'indeterminate' : 'static').'.svg'), $attributes);
}
/**
* The names of every Material Symbol the package ships.
*