Add a colour spec choice to material:scheme
The scheme script always generated with material-color-utilities' 2025 spec.
An application whose palette was generated with M3's original 2021 colour
(ReStride) could not reproduce it. The script now takes `spec` ('2025' by
default, or '2021') and refuses anything else, and the command takes
`--spec`, validated before Node runs.
Colour profiles may set their own `spec`, `success`, `warning` and `info`;
without them the command's options apply. The stylesheet header writes out
every option that differs from its default, so the recorded command
regenerates the file, and a profile whose spec differs from the header's
names it. Default output is byte-for-byte unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
This commit is contained in:
co-authored by
Claude Opus 5
parent
db179e6b4e
commit
3a7aa96ec7
@@ -18,6 +18,7 @@ class SchemeCommand extends Command
|
||||
protected $signature = 'material:scheme
|
||||
{seed? : The source colour, as #rrggbb; without it, every profile in livewire-material.profiles}
|
||||
{--variant=tonal-spot : tonal-spot, vibrant, expressive, neutral, fidelity, content, monochrome, rainbow or fruit-salad}
|
||||
{--spec=2025 : The colour spec: 2025 (M3 Expressive) or 2021 (M3 as it first shipped)}
|
||||
{--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}
|
||||
@@ -29,19 +30,44 @@ class SchemeCommand extends Command
|
||||
*/
|
||||
protected $description = 'Generate the application\'s Material 3 colour scheme from a seed colour, or every configured colour profile';
|
||||
|
||||
/**
|
||||
* The colour specs Google's colour utilities know. 2025 is M3 Expressive's colour; the library
|
||||
* falls back to 2021 by itself for the variants 2025 does not define.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected const SPECS = ['2021', '2025'];
|
||||
|
||||
public function handle(Filesystem $files): int
|
||||
{
|
||||
$stylesheet = $this->option('output') ?: resource_path('css/material-scheme.css');
|
||||
$data = preg_replace('/\.css$/', '', $stylesheet).'.json';
|
||||
$spec = (string) $this->option('spec');
|
||||
|
||||
if (! in_array($spec, self::SPECS, true)) {
|
||||
$this->components->error("Unknown spec \"{$spec}\". Use one of: ".implode(', ', self::SPECS).'.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (filled($this->argument('seed'))) {
|
||||
$scheme = $this->generate((string) $this->argument('seed'), (string) $this->option('variant'), (float) $this->option('contrast'));
|
||||
$input = [
|
||||
'seed' => (string) $this->argument('seed'),
|
||||
'variant' => (string) $this->option('variant'),
|
||||
'spec' => $spec,
|
||||
'contrast' => (float) $this->option('contrast'),
|
||||
'success' => (string) $this->option('success'),
|
||||
'warning' => (string) $this->option('warning'),
|
||||
'info' => (string) $this->option('info'),
|
||||
];
|
||||
|
||||
$scheme = $this->generate($input);
|
||||
|
||||
if ($scheme === null) {
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
return $this->write($files, $stylesheet, $data, $this->stylesheet($scheme), $scheme);
|
||||
return $this->write($files, $stylesheet, $data, $this->stylesheet($scheme, $input), $scheme);
|
||||
}
|
||||
|
||||
$profiles = config('livewire-material.profiles');
|
||||
@@ -61,7 +87,16 @@ class SchemeCommand extends Command
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$scheme = $this->generate((string) ($profile['seed'] ?? ''), (string) ($profile['variant'] ?? 'tonal-spot'), (float) ($profile['contrast'] ?? 0), "Profile \"{$name}\": ");
|
||||
// A profile's own spec and state colours win; without them, the command's options apply.
|
||||
$scheme = $this->generate([
|
||||
'seed' => (string) ($profile['seed'] ?? ''),
|
||||
'variant' => (string) ($profile['variant'] ?? 'tonal-spot'),
|
||||
'spec' => (string) ($profile['spec'] ?? $spec),
|
||||
'contrast' => (float) ($profile['contrast'] ?? 0),
|
||||
'success' => (string) ($profile['success'] ?? $this->option('success')),
|
||||
'warning' => (string) ($profile['warning'] ?? $this->option('warning')),
|
||||
'info' => (string) ($profile['info'] ?? $this->option('info')),
|
||||
], "Profile \"{$name}\": ");
|
||||
|
||||
if ($scheme === null) {
|
||||
return self::FAILURE;
|
||||
@@ -83,21 +118,15 @@ class SchemeCommand extends Command
|
||||
/**
|
||||
* One scheme from Google's colour utilities, or null once the reason has been shown.
|
||||
*
|
||||
* @param array{seed: string, variant: string, spec: string, contrast: float, success: string, warning: string, info: string} $input
|
||||
* @return array{seed: string, variant: string, spec: string, contrast: float, light: array<string, string>, dark: array<string, string>}|null
|
||||
*/
|
||||
protected function generate(string $seed, string $variant, float $contrast, string $context = ''): ?array
|
||||
protected function generate(array $input, string $context = ''): ?array
|
||||
{
|
||||
$result = Process::run([
|
||||
config('livewire-material.node', 'node'),
|
||||
__DIR__.'/../../resources/node/scheme.mjs',
|
||||
json_encode([
|
||||
'seed' => $seed,
|
||||
'variant' => $variant,
|
||||
'contrast' => $contrast,
|
||||
'success' => $this->option('success'),
|
||||
'warning' => $this->option('warning'),
|
||||
'info' => $this->option('info'),
|
||||
]),
|
||||
json_encode($input),
|
||||
]);
|
||||
|
||||
if ($result->failed()) {
|
||||
@@ -130,15 +159,26 @@ class SchemeCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* The stylesheet, headed by the command that regenerates it: every option that differs from
|
||||
* its default is written out.
|
||||
*
|
||||
* @param array{seed: string, variant: string, spec: string, contrast: float, light: array<string, string>, dark: array<string, string>} $scheme
|
||||
* @param array{seed: string, variant: string, spec: string, contrast: float, success: string, warning: string, info: string} $input
|
||||
*/
|
||||
protected function stylesheet(array $scheme): string
|
||||
protected function stylesheet(array $scheme, array $input): string
|
||||
{
|
||||
$states = collect(['success', 'warning', 'info'])
|
||||
->reject(fn (string $state): bool => strtolower($input[$state]) === strtolower((string) $this->getDefinition()->getOption($state)->getDefault()))
|
||||
->map(fn (string $state): string => sprintf(' --%s="%s"', $state, strtolower($input[$state])))
|
||||
->implode('');
|
||||
|
||||
$command = sprintf(
|
||||
'php artisan material:scheme "%s" --variant=%s%s',
|
||||
'php artisan material:scheme "%s" --variant=%s%s%s%s',
|
||||
$scheme['seed'],
|
||||
$scheme['variant'],
|
||||
$input['spec'] !== '2025' ? ' --spec='.$input['spec'] : '',
|
||||
$scheme['contrast'] != 0 ? ' --contrast='.$scheme['contrast'] : '',
|
||||
$states,
|
||||
);
|
||||
|
||||
$blocks = $this->blocks([':root', "[data-theme='light']"], ["[data-theme='dark']"], $scheme);
|
||||
@@ -171,10 +211,11 @@ class SchemeCommand extends Command
|
||||
{
|
||||
$list = collect($profiles)
|
||||
->map(fn (array $profile, string $name): string => sprintf(
|
||||
' * %-12s %s, %s%s',
|
||||
' * %-12s %s, %s%s%s',
|
||||
$name,
|
||||
$profile['seed'],
|
||||
$profile['variant'],
|
||||
$profile['spec'] !== $profiles[$default]['spec'] ? ', spec '.$profile['spec'] : '',
|
||||
$profile['contrast'] != 0 ? ', contrast '.$profile['contrast'] : '',
|
||||
))
|
||||
->implode("\n");
|
||||
|
||||
Reference in New Issue
Block a user