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
+118
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* The source of resources/node/scheme.mjs, which `php artisan material:scheme` runs.
|
||||
*
|
||||
* Rebuild with `npm run build:scheme` after changing this file or upgrading
|
||||
* @material/material-color-utilities; the bundle is committed so applications need
|
||||
* nothing but `node`. (The published library imports without file extensions, which
|
||||
* plain Node refuses, so it cannot be run unbundled anyway.)
|
||||
*
|
||||
* Input: one JSON argument — {seed, variant, contrast, success, warning, info}.
|
||||
* Output: JSON on stdout — {seed, variant, spec, contrast, light: {role: hex}, dark: {role: hex}}.
|
||||
*/
|
||||
import {
|
||||
argbFromHex,
|
||||
customColor,
|
||||
hexFromArgb,
|
||||
Hct,
|
||||
MaterialDynamicColors,
|
||||
SchemeContent,
|
||||
SchemeExpressive,
|
||||
SchemeFidelity,
|
||||
SchemeFruitSalad,
|
||||
SchemeMonochrome,
|
||||
SchemeNeutral,
|
||||
SchemeRainbow,
|
||||
SchemeTonalSpot,
|
||||
SchemeVibrant,
|
||||
} from '@material/material-color-utilities'
|
||||
|
||||
const VARIANTS = {
|
||||
'tonal-spot': SchemeTonalSpot,
|
||||
vibrant: SchemeVibrant,
|
||||
expressive: SchemeExpressive,
|
||||
neutral: SchemeNeutral,
|
||||
fidelity: SchemeFidelity,
|
||||
content: SchemeContent,
|
||||
monochrome: SchemeMonochrome,
|
||||
rainbow: SchemeRainbow,
|
||||
'fruit-salad': SchemeFruitSalad,
|
||||
}
|
||||
|
||||
// Key colours seed the palettes; they are not roles a stylesheet paints with.
|
||||
const NOT_ROLES = /_palette_key_color$/
|
||||
|
||||
function fail(message) {
|
||||
process.stderr.write(`${message}\n`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let input
|
||||
|
||||
try {
|
||||
input = JSON.parse(process.argv[2] ?? '')
|
||||
} catch {
|
||||
fail('Expected one JSON argument: {"seed": "#rrggbb", "variant": "tonal-spot", ...}')
|
||||
}
|
||||
|
||||
const hex = /^#[0-9a-f]{6}$/i
|
||||
const Scheme = VARIANTS[input.variant]
|
||||
|
||||
if (!hex.test(input.seed ?? '')) fail(`The seed must be a #rrggbb colour, "${input.seed}" given.`)
|
||||
if (!Scheme) fail(`Unknown variant "${input.variant}". Use one of: ${Object.keys(VARIANTS).join(', ')}.`)
|
||||
|
||||
for (const state of ['success', 'warning', 'info']) {
|
||||
if (!hex.test(input[state] ?? '')) fail(`The ${state} colour must be a #rrggbb colour, "${input[state]}" given.`)
|
||||
}
|
||||
|
||||
const contrast = Number(input.contrast ?? 0)
|
||||
|
||||
if (!(contrast >= -1 && contrast <= 1)) fail(`The contrast level must be between -1 and 1, "${input.contrast}" given.`)
|
||||
|
||||
const source = Hct.fromInt(argbFromHex(input.seed))
|
||||
const colors = new MaterialDynamicColors()
|
||||
|
||||
function roles(isDark) {
|
||||
// The 2025 spec is M3 Expressive's colour; the library falls back to 2021 for the
|
||||
// variants the new spec does not define (fidelity, content, monochrome, …).
|
||||
const scheme = new Scheme(source, isDark, contrast, '2025')
|
||||
const out = {}
|
||||
|
||||
for (const color of colors.allColors) {
|
||||
if (color && !NOT_ROLES.test(color.name)) {
|
||||
out[color.name.replaceAll('_', '-')] = hexFromArgb(color.getArgb(scheme))
|
||||
}
|
||||
}
|
||||
|
||||
return { scheme, out }
|
||||
}
|
||||
|
||||
const light = roles(false)
|
||||
const dark = roles(true)
|
||||
|
||||
// success, warning and info are M3 custom colours, harmonisation off: harmonising pulls
|
||||
// each hue towards the seed, and a state has to stay recognisable whatever the brand is.
|
||||
for (const state of ['success', 'warning', 'info']) {
|
||||
const group = customColor(argbFromHex(input.seed), { name: state, value: argbFromHex(input[state]), blend: false })
|
||||
|
||||
for (const [theme, colours] of [['light', light.out], ['dark', dark.out]]) {
|
||||
colours[state] = hexFromArgb(group[theme].color)
|
||||
colours[`on-${state}`] = hexFromArgb(group[theme].onColor)
|
||||
colours[`${state}-container`] = hexFromArgb(group[theme].colorContainer)
|
||||
colours[`on-${state}-container`] = hexFromArgb(group[theme].onColorContainer)
|
||||
}
|
||||
}
|
||||
|
||||
// A state colour drawn on the inverse surface (a snackbar's icon) is the other theme's.
|
||||
for (const state of ['error', 'success', 'warning', 'info']) {
|
||||
light.out[`inverse-${state}`] = dark.out[state]
|
||||
dark.out[`inverse-${state}`] = light.out[state]
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify({
|
||||
seed: input.seed.toLowerCase(),
|
||||
variant: input.variant,
|
||||
spec: light.scheme.specVersion,
|
||||
contrast,
|
||||
light: light.out,
|
||||
dark: dark.out,
|
||||
}))
|
||||
Reference in New Issue
Block a user