Add M3 error pages and the Markdown mail theme
tests / feature (8.5) (push) Successful in 1m8s
tests / browser (safari, webkit) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / lint (push) Successful in 1m5s
tests / feature (8.4) (push) Successful in 1m11s
tests / browser (chrome, chromium) (push) Failing after 6m9s

Error pages for 401 to 503 in an error-view root appended to view.paths,
with an inline fallback when the Vite build is missing, and a mail theme
rendered from the application's scheme JSON, with opt-in header and
message components. Completes Phase 9.

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 08:55:58 +02:00
co-authored by Claude Opus 5
parent ab692b66bb
commit 8f174520eb
29 changed files with 1595 additions and 2 deletions
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace NoNameWeb\LivewireMaterial\Support;
/**
* The colour scheme as data, for the places CSS custom properties cannot reach: a mail client
* resolves none, and an error page whose build is missing has no stylesheet to declare them.
*
* The data is the JSON `php artisan material:scheme` writes beside the stylesheet
* (`livewire-material.scheme`, resources/css/material-scheme.json by default). Without that
* file — or with one that is not a scheme — the package's own default applies, the same one
* tokens/scheme.css carries. A role the application's file lacks (a scheme generated before
* the role existed) is taken from the default, and a value that is not a #rrggbb hex is
* refused, so nothing but a colour ever reaches a stylesheet.
*
* Read on every call and never kept: a regenerated scheme applies at once, and a long-running
* worker holds nothing.
*/
class Scheme
{
/**
* @return array{light: array<string, string>, dark: array<string, string>}
*/
public static function load(?string $path = null): array
{
$default = static::read(dirname(__DIR__, 2).'/resources/css/tokens/scheme.json');
$scheme = static::read($path ?? (string) config('livewire-material.scheme'));
return [
'light' => [...$default['light'], ...$scheme['light']],
'dark' => [...$default['dark'], ...$scheme['dark']],
];
}
/**
* The light roles: what a mail wears.
*
* @return array<string, string>
*/
public static function light(?string $path = null): array
{
return static::load($path)['light'];
}
/**
* @return array{light: array<string, string>, dark: array<string, string>}
*/
protected static function read(string $path): array
{
$data = is_file($path) ? json_decode((string) file_get_contents($path), true) : null;
return [
'light' => static::roles($data['light'] ?? null),
'dark' => static::roles($data['dark'] ?? null),
];
}
/**
* @return array<string, string>
*/
protected static function roles(mixed $roles): array
{
if (! is_array($roles)) {
return [];
}
return array_filter(
$roles,
fn (mixed $hex, mixed $role): bool => is_string($role)
&& preg_match('/^[a-z-]+$/', $role) === 1
&& is_string($hex)
&& preg_match('/^#[0-9a-fA-F]{6}$/', $hex) === 1,
ARRAY_FILTER_USE_BOTH,
);
}
}