mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Analyze color components (#732)
Also refactored existing color scripts (moved to separate directory, extracted utils to separate file)
This commit is contained in:
@@ -1 +1 @@
|
||||
export { default as default } from '../../src/styles/color/palettes-analyzed.js';
|
||||
export { default as default } from '../../src/styles/color/scripts/palettes-analyzed.js';
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* More later.
|
||||
*/
|
||||
import rawPalettes from './palettes.js';
|
||||
import { clamp } from './util.js';
|
||||
|
||||
// Default accent tint if all chromas are 0, but also the tint accent colors will be nudged towards (see chromaTolerance)
|
||||
const DEFAULT_ACCENT = 60;
|
||||
@@ -58,9 +59,5 @@ for (let paletteId in palettes) {
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(min, value, max) {
|
||||
return Math.min(Math.max(min, value), max);
|
||||
}
|
||||
|
||||
export default palettes;
|
||||
export { rawPalettes };
|
||||
@@ -6,11 +6,9 @@
|
||||
import Color from 'colorjs.io';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { PALETTE_DIR } from './util.js';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export const paletteFiles = fs.readdirSync(__dirname).filter(file => file.endsWith('.css'));
|
||||
export const paletteFiles = fs.readdirSync(PALETTE_DIR + '/').filter(file => file.endsWith('.css'));
|
||||
export const declarationRegex =
|
||||
/^\s*--wa-color-(?<hue>[a-z]+)-(?<level>[0-9]+):\s*(?<color>.+?)\s*(\/\*.+?\*\/)?\s*;$/gm;
|
||||
export const rawCSS = {};
|
||||
@@ -55,7 +53,7 @@ function parse(contents, file) {
|
||||
const palettes = {};
|
||||
|
||||
for (let file of paletteFiles) {
|
||||
let css = fs.readFileSync(path.join(__dirname, file), 'utf8');
|
||||
let css = fs.readFileSync(path.join(PALETTE_DIR, file), 'utf8');
|
||||
rawCSS[file] = css;
|
||||
let tokens = parse(css, file);
|
||||
let paletteId = file.replace(/\.css$/, '');
|
||||
98
src/styles/color/scripts/ranges.js
Normal file
98
src/styles/color/scripts/ranges.js
Normal file
@@ -0,0 +1,98 @@
|
||||
// Run via node ranges.js to analyze all palettes
|
||||
// or node ranges.js <paletteId> to analyze a single palette
|
||||
import palettes from './palettes-analyzed.js';
|
||||
import { toPrecision } from './util.js';
|
||||
|
||||
let paletteId = process.argv[2];
|
||||
|
||||
/**
|
||||
* Each "test" consists of the following params to analyze:
|
||||
* - component: The color component to analyze (h, c, l)
|
||||
* - label: The label to display in the console
|
||||
* - by: The grouping to analyze by (tint, hue)
|
||||
* - levels: The number of tints from the core color to include in the analysis.
|
||||
* Examples: undefined for all tints, 0 for the core color only, 10 for the core color and ±10 from it.
|
||||
*/
|
||||
let tests = [
|
||||
{ component: 'h', label: 'Hue', by: 'hue', levels: paletteId ? undefined : 10 },
|
||||
{ component: 'c', label: 'Chroma', by: 'tint' },
|
||||
{ component: 'l', label: 'L', by: 'tint' },
|
||||
];
|
||||
|
||||
if (!paletteId) {
|
||||
tests.push({ component: 'h', label: 'Core Hue', by: 'hue', levels: 0 });
|
||||
}
|
||||
|
||||
const tints = ['95', '90', '80', '70', '60', '50', '40', '30', '20', '10', '05'];
|
||||
const hues = ['red', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'gray'];
|
||||
|
||||
function analyzePalette(scales, results, { component, levels, by = 'tint' }) {
|
||||
for (let hue in scales) {
|
||||
let colors = scales[hue];
|
||||
let key = colors.maxChromaTint;
|
||||
let resultsByHue = by === 'hue' ? results[hue] : results;
|
||||
|
||||
for (let tint of tints) {
|
||||
let color = colors[tint];
|
||||
let value = color[component];
|
||||
let resultsByTint = by === 'tint' ? resultsByHue[tint] : resultsByHue;
|
||||
|
||||
if (levels === undefined || Math.abs(tint - key) <= levels) {
|
||||
if (resultsByTint.min > value) resultsByTint.min = value;
|
||||
if (resultsByTint.max < value) resultsByTint.max = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function analyze(options = {}) {
|
||||
let results = {};
|
||||
let keys = options.by === 'hue' ? hues : tints;
|
||||
|
||||
for (let key of keys) {
|
||||
results[key] = { min: Infinity, max: -Infinity };
|
||||
}
|
||||
|
||||
if (paletteId) {
|
||||
analyzePalette(palettes[paletteId], results, options);
|
||||
} else {
|
||||
for (let paletteId in palettes) {
|
||||
analyzePalette(palettes[paletteId], results, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Add extent & mid, make numbers easier to read
|
||||
for (let key of keys) {
|
||||
let info = results[key];
|
||||
if (options.component === 'h') {
|
||||
// Fixup hues crossing 0
|
||||
if (Math.abs(info.max - info.min) > 180) {
|
||||
info.min += 360;
|
||||
|
||||
if (info.min > info.max) {
|
||||
[info.min, info.max] = [info.max, info.min];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info.extent = info.max - info.min;
|
||||
info.mid = (info.min + info.max) / 2;
|
||||
|
||||
for (let prop in info) {
|
||||
info[prop] = toPrecision(info[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
let label = `${options.label || options.component} ranges`;
|
||||
console.log(label + (options.levels !== undefined ? ` (±${options.levels} from core tint)` : '') + ':');
|
||||
console.table(results);
|
||||
}
|
||||
|
||||
if (paletteId) {
|
||||
// Analyze a single palette
|
||||
console.log(`Analyzing palette '${paletteId}'`);
|
||||
}
|
||||
|
||||
for (let test of tests) {
|
||||
analyze(test);
|
||||
}
|
||||
@@ -6,23 +6,14 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import palettes, { rawPalettes } from './palettes-analyzed.js';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
import { PALETTE_DIR, formatComparison, hueToChalk } from './util.js';
|
||||
|
||||
const selector = paletteId =>
|
||||
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${paletteId}`].join(
|
||||
',\n',
|
||||
);
|
||||
|
||||
// Default accent tint if all chromas are 0, but also the tint accent colors will be nudged towards (see chromaTolerance)
|
||||
const defaultAccent = 60;
|
||||
|
||||
// Min and max allowed tints
|
||||
const minAccentTint = 40;
|
||||
const maxAccentTint = 90;
|
||||
|
||||
// Used for formatting warnings
|
||||
const paletteIdMaxChars = Object.keys(palettes).reduce((max, id) => Math.max(max, id.length), 0);
|
||||
const hueMaxChars = Object.keys(palettes.default).reduce((max, id) => Math.max(max, id.length), 0);
|
||||
@@ -80,53 +71,10 @@ for (let paletteId in palettes) {
|
||||
let indent = ' ';
|
||||
css = `${selector(paletteId)} {\n${css.trimEnd().replace(/^(?=\S)/gm, indent)}\n}\n`;
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, paletteId + '.css'), css, 'utf8');
|
||||
fs.writeFileSync(path.join(PALETTE_DIR, paletteId + '.css'), css, 'utf8');
|
||||
}
|
||||
|
||||
console.info(
|
||||
`🎨 Wrote ${Object.keys(palettes).length} palette files.` +
|
||||
(issueCount > 0 ? ` ${chalk.bold(issueCount)} issues found across ${chalk.bold(issuePaletteCount)} palettes.` : ''),
|
||||
);
|
||||
|
||||
/**
|
||||
* Format a comparison by rounding numbers to the lowest number of significant digits that still shows a difference.
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatComparison(a, b) {
|
||||
let op = a < b ? '<' : '>';
|
||||
|
||||
for (let i = 1; i < 10; i++) {
|
||||
let roundedA = a.toPrecision(i);
|
||||
let roundedB = b.toPrecision(i);
|
||||
|
||||
if (roundedA !== roundedB) {
|
||||
return `${roundedA} ${op} ${roundedB}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${a} ${op} ${b}`;
|
||||
}
|
||||
|
||||
function hueToChalk(hue) {
|
||||
let ret;
|
||||
|
||||
if (hue in chalk) {
|
||||
ret = chalk[hue];
|
||||
}
|
||||
switch (hue) {
|
||||
case 'indigo':
|
||||
ret = chalk.hex('#8a8beb');
|
||||
break;
|
||||
case 'purple':
|
||||
ret = chalk.hex('#a94dc6');
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
return ret.bold;
|
||||
}
|
||||
|
||||
return chalk.bold;
|
||||
}
|
||||
59
src/styles/color/scripts/util.js
Normal file
59
src/styles/color/scripts/util.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
let url = new URL('.', import.meta.url);
|
||||
// One level up
|
||||
url.pathname = path.join(url.pathname, '..');
|
||||
export const PALETTE_DIR = fileURLToPath(url);
|
||||
|
||||
export function clamp(min, value, max) {
|
||||
return Math.min(Math.max(min, value), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a comparison by rounding numbers to the lowest number of significant digits that still shows a difference.
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatComparison(a, b) {
|
||||
let op = a < b ? '<' : '>';
|
||||
|
||||
for (let i = 1; i < 10; i++) {
|
||||
let roundedA = a.toPrecision(i);
|
||||
let roundedB = b.toPrecision(i);
|
||||
|
||||
if (roundedA !== roundedB) {
|
||||
return `${roundedA} ${op} ${roundedB}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${a} ${op} ${b}`;
|
||||
}
|
||||
|
||||
export function hueToChalk(hue) {
|
||||
let ret;
|
||||
|
||||
if (hue in chalk) {
|
||||
ret = chalk[hue];
|
||||
}
|
||||
switch (hue) {
|
||||
case 'indigo':
|
||||
ret = chalk.hex('#8a8beb');
|
||||
break;
|
||||
case 'purple':
|
||||
ret = chalk.hex('#a94dc6');
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
return ret.bold;
|
||||
}
|
||||
|
||||
return chalk.bold;
|
||||
}
|
||||
|
||||
export function toPrecision(value, precision = 2) {
|
||||
return +Number(value).toPrecision(precision);
|
||||
}
|
||||
Reference in New Issue
Block a user