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
116 lines
4.1 KiB
PHP
116 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace NoNameWeb\LivewireMaterial\Console;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Filesystem\Filesystem;
|
|
use Illuminate\Support\Facades\Process;
|
|
use JsonException;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
|
|
#[AsCommand(name: 'material:scheme')]
|
|
class SchemeCommand extends Command
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $signature = 'material:scheme
|
|
{seed : The source colour, as #rrggbb}
|
|
{--variant=tonal-spot : tonal-spot, vibrant, expressive, neutral, fidelity, content, monochrome, rainbow or fruit-salad}
|
|
{--contrast=0 : The contrast level, from -1 to 1}
|
|
{--success=#22a06b : The source of the success colour}
|
|
{--warning=#e2a400 : The source of the warning colour}
|
|
{--info=#1d7afc : The source of the info colour}
|
|
{--output= : The stylesheet to write, resources/css/material-scheme.css by default}';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $description = 'Generate the application\'s Material 3 colour scheme from a seed colour';
|
|
|
|
public function handle(Filesystem $files): int
|
|
{
|
|
$stylesheet = $this->option('output') ?: resource_path('css/material-scheme.css');
|
|
$data = preg_replace('/\.css$/', '', $stylesheet).'.json';
|
|
|
|
$result = Process::run([
|
|
config('livewire-material.node', 'node'),
|
|
__DIR__.'/../../resources/node/scheme.mjs',
|
|
json_encode([
|
|
'seed' => $this->argument('seed'),
|
|
'variant' => $this->option('variant'),
|
|
'contrast' => (float) $this->option('contrast'),
|
|
'success' => $this->option('success'),
|
|
'warning' => $this->option('warning'),
|
|
'info' => $this->option('info'),
|
|
]),
|
|
]);
|
|
|
|
if ($result->failed()) {
|
|
$this->components->error(trim($result->errorOutput()) ?: 'Node could not run the scheme generator. Is `node` installed and on the PATH?');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
try {
|
|
/** @var array{seed: string, variant: string, spec: string, contrast: float, light: array<string, string>, dark: array<string, string>} $scheme */
|
|
$scheme = json_decode($result->output(), true, flags: JSON_THROW_ON_ERROR);
|
|
} catch (JsonException) {
|
|
$this->components->error('The scheme generator answered with something other than JSON.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$files->ensureDirectoryExists(dirname($stylesheet));
|
|
$files->put($stylesheet, $this->stylesheet($scheme));
|
|
$files->put($data, json_encode($scheme, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
|
|
|
|
$this->components->info("Wrote {$stylesheet} and {$data}.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @param array{seed: string, variant: string, spec: string, contrast: float, light: array<string, string>, dark: array<string, string>} $scheme
|
|
*/
|
|
protected function stylesheet(array $scheme): string
|
|
{
|
|
$command = sprintf(
|
|
'php artisan material:scheme "%s" --variant=%s%s',
|
|
$scheme['seed'],
|
|
$scheme['variant'],
|
|
$scheme['contrast'] != 0 ? ' --contrast='.$scheme['contrast'] : '',
|
|
);
|
|
|
|
$block = fn (array $roles): string => collect($roles)
|
|
->map(fn (string $hex, string $role): string => " --md-sys-color-{$role}: {$hex};")
|
|
->implode("\n");
|
|
|
|
return <<<CSS
|
|
/*
|
|
* Material 3 colour roles, generated by Google's material-color-utilities (spec {$scheme['spec']}).
|
|
*
|
|
* {$command}
|
|
*
|
|
* Regenerate rather than editing a value: every pair here (a role and its on-role) carries
|
|
* M3's contrast guarantee only as generated. The head script sets data-theme before the
|
|
* first paint; the light block also stands without it.
|
|
*/
|
|
|
|
:root,
|
|
[data-theme='light'] {
|
|
color-scheme: light;
|
|
|
|
{$block($scheme['light'])}
|
|
}
|
|
|
|
[data-theme='dark'] {
|
|
color-scheme: dark;
|
|
|
|
{$block($scheme['dark'])}
|
|
}
|
|
|
|
CSS;
|
|
}
|
|
}
|