mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-11 20:08:56 +00:00
Show contrast ratios in contrast pair tables
This commit is contained in:
1
docs/_data/palettes.js
Normal file
1
docs/_data/palettes.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as default } from '../../src/styles/color/palettes.js';
|
||||
@@ -15,15 +15,24 @@
|
||||
<tr>
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
{% for tint_bg in tints -%}
|
||||
{% for tint_fg in tints | reverse -%}
|
||||
|
||||
{% if (tint_fg - tint_bg) | abs == difference %}
|
||||
<td>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})">
|
||||
{{ tint_fg }} on {{ tint_bg }}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% set color_bg = palettes[paletteId][hue][tint_bg] %}
|
||||
{% for tint_fg in tints | reverse -%}
|
||||
{% set color_fg = palettes[paletteId][hue][tint_fg] %}
|
||||
{% if (tint_fg - tint_bg) | abs == difference %}
|
||||
<td>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})">
|
||||
{% set contrast_wcag = '' %}
|
||||
{% if color_fg and color_bg %}
|
||||
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
|
||||
{% endif %}
|
||||
{% if contrast_wcag %}
|
||||
{{ contrast_wcag | number({maximumSignificantDigits: 2}) }}
|
||||
{% else %}
|
||||
{{ tint_fg }} on {{ tint_bg }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
</tr>
|
||||
|
||||
@@ -61,6 +61,16 @@ A difference of `40` ensures a minimum **3:1** contrast ratio, suitable for larg
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
|
||||
This also goes for a difference of `45`:
|
||||
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 45 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
|
||||
### Level 2
|
||||
|
||||
A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for normal text (AA) and large text (AAA)
|
||||
@@ -69,6 +79,15 @@ A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for no
|
||||
{% set difference = 50 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
|
||||
This also goes for a difference of `55`:
|
||||
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 55 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
### Level 3
|
||||
|
||||
@@ -78,6 +97,15 @@ A difference of `60` ensures a minimum **7:1** contrast ratio, suitable for all
|
||||
{% set difference = 60 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
|
||||
This also goes for a difference of `65`:
|
||||
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 65 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
## How to use this palette
|
||||
|
||||
|
||||
@@ -105,6 +105,23 @@ export function deepValue(obj, key) {
|
||||
return key.reduce((subObj, property) => subObj?.[property], obj);
|
||||
}
|
||||
|
||||
export function number(value, options) {
|
||||
if (typeof value !== 'number' && isNaN(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let lang = options?.lang ?? 'en';
|
||||
if (options?.lang) {
|
||||
delete options.lang;
|
||||
}
|
||||
|
||||
if (!options || Object.keys(options).length === 0) {
|
||||
options = { maximumSignificantDigits: 3 };
|
||||
}
|
||||
|
||||
return Number(value).toLocaleString(lang, options);
|
||||
}
|
||||
|
||||
export function isNumeric(value) {
|
||||
return typeof value === 'number' || (typeof value === 'string' && !isNaN(value));
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"create": "plop --plopfile scripts/plop/plopfile.js",
|
||||
"test": "web-test-runner --group default",
|
||||
"test:component": "web-test-runner -- --watch --group",
|
||||
"test:contrast": "cd src/styles/color && node contrast.test.js",
|
||||
"test:watch": "web-test-runner --watch --group default",
|
||||
"prettier": "prettier --check --log-level=warn .",
|
||||
"prettier:fix": "prettier --write --log-level=warn .",
|
||||
|
||||
@@ -1,34 +1,7 @@
|
||||
// Get a list of all CSS files in repo
|
||||
import chalk from 'chalk';
|
||||
import Color from 'colorjs.io';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
const paletteFiles = fs.readdirSync(__dirname).filter(file => file.endsWith('.css'));
|
||||
|
||||
function parse(contents) {
|
||||
// Regex for each declaration
|
||||
let regex = /^\s*--wa-color-(?<hue>[a-z]+)-(?<level>[0-9]+):\s*(?<color>[^;]+)\s*;$/gm;
|
||||
let matches = [...contents.matchAll(regex)];
|
||||
|
||||
if (matches.length === 0) {
|
||||
throw new Error('Cound not extract colors');
|
||||
}
|
||||
|
||||
let ret = {};
|
||||
|
||||
for (let match of matches) {
|
||||
let { hue, level, color } = match.groups;
|
||||
ret[hue] ??= {};
|
||||
level = level.replace(/^0+/, ''); // Leading zeroes throw off sorting
|
||||
ret[hue][level] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
import palettes from './palettes.js';
|
||||
|
||||
let targetContrasts = {
|
||||
40: 3,
|
||||
@@ -38,21 +11,17 @@ let targetContrasts = {
|
||||
|
||||
let result = { pass: 0, fail: 0, invalid: 0 };
|
||||
|
||||
for (let file of paletteFiles) {
|
||||
let css = fs.readFileSync(path.join(__dirname, file), 'utf8');
|
||||
let filePrefix = chalk.dim(`[${file}]`);
|
||||
let tokens = parse(css);
|
||||
for (let paletteId in palettes) {
|
||||
const tokens = palettes[paletteId];
|
||||
let prefix = chalk.dim(`[${paletteId}]`);
|
||||
|
||||
for (let hue in tokens) {
|
||||
let tints = tokens[hue];
|
||||
|
||||
for (let tint = 10; tint <= 50; tint += 10) {
|
||||
let color;
|
||||
let color = tints[tint];
|
||||
|
||||
try {
|
||||
color = new Color(tints[tint]);
|
||||
} catch (e) {
|
||||
console.error(`[${file}] Invalid color ${hue}-${tint}: ${tints[tint]}`);
|
||||
if (!(color instanceof Color)) {
|
||||
result.invalid++;
|
||||
continue;
|
||||
}
|
||||
@@ -64,15 +33,10 @@ for (let file of paletteFiles) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let color2;
|
||||
try {
|
||||
color2 = new Color(tints[tint2]);
|
||||
} catch (e) {
|
||||
if (tint2 > 50) {
|
||||
// If 50, we'll look at it at some point as color1
|
||||
console.error(`${filePrefix} Invalid color ${hue}-${tint2}: ${tints[tint2]}`);
|
||||
result.invalid++;
|
||||
}
|
||||
let color2 = tints[tint2];
|
||||
|
||||
if (!(color2 instanceof Color)) {
|
||||
result.invalid++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -84,7 +48,7 @@ for (let file of paletteFiles) {
|
||||
result.fail++;
|
||||
console.log(
|
||||
chalk.red(
|
||||
`${filePrefix} WCAG 2.1 contrast between ${hue}-${tint} and ${hue}-${tint2} is ${contrast.toLocaleString('en')} < ${targetContrast}`,
|
||||
`${prefix} WCAG 2.1 contrast between ${hue}-${tint} and ${hue}-${tint2} is ${contrast.toLocaleString('en')} < ${targetContrast}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
63
src/styles/color/palettes.js
Normal file
63
src/styles/color/palettes.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Export data on all color tokens from all palettes
|
||||
*/
|
||||
|
||||
// Get a list of all CSS files in repo
|
||||
import Color from 'colorjs.io';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export const paletteFiles = fs.readdirSync(__dirname).filter(file => file.endsWith('.css'));
|
||||
export const declarationRegex = /^\s*--wa-color-(?<hue>[a-z]+)-(?<level>[0-9]+):\s*(?<color>[^;]+)\s*;$/gm;
|
||||
|
||||
function parse(contents, file) {
|
||||
// Regex for each declaration
|
||||
const matches = [...contents.matchAll(declarationRegex)];
|
||||
|
||||
if (matches.length === 0) {
|
||||
throw new Error('Cound not extract colors');
|
||||
}
|
||||
|
||||
const ret = {};
|
||||
|
||||
for (let match of matches) {
|
||||
let { hue, level, color } = match.groups;
|
||||
ret[hue] ??= {};
|
||||
|
||||
// Attempt to convert color to Color object, fall back to string if this fails
|
||||
// This will happen for e.g. colors defined via color-mix()
|
||||
try {
|
||||
color = new Color(color);
|
||||
} catch (e) {
|
||||
console.warn(`[${file}] Unparseable color ${hue}-${level}: ${color}`);
|
||||
}
|
||||
|
||||
if (level.startsWith('0')) {
|
||||
// Leading zeroes throw off sorting, add both properties
|
||||
// NOTE: Ideally one of the two would be added as non-enumerable, but then we cannot access it via 11ty data
|
||||
ret[hue][level] = color;
|
||||
|
||||
// Drop leading zeroes
|
||||
level = level.replace(/^0+/, '');
|
||||
}
|
||||
|
||||
ret[hue][level] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const palettes = {};
|
||||
|
||||
for (let file of paletteFiles) {
|
||||
let css = fs.readFileSync(path.join(__dirname, file), 'utf8');
|
||||
let tokens = parse(css, file);
|
||||
let paletteId = file.replace(/\.css$/, '');
|
||||
|
||||
palettes[paletteId] = tokens;
|
||||
}
|
||||
|
||||
export default palettes;
|
||||
Reference in New Issue
Block a user