@protolabsai/design
Advanced tools
| import { describe, it } from "node:test"; | ||
| import assert from "node:assert/strict"; | ||
| import { tokens, light } from "./tokens.js"; | ||
| const CHART_KEYS = [ | ||
| "series1", | ||
| "series2", | ||
| "series3", | ||
| "series4", | ||
| "series5", | ||
| "series6", | ||
| "series7", | ||
| "series8", | ||
| "grid", | ||
| "axis", | ||
| ]; | ||
| const OKLCH_RE = /^oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s*\)$/; | ||
| function parseOklch(value, label) { | ||
| const m = value.match(OKLCH_RE); | ||
| assert.ok(m, `${label}: expected oklch(L C H), got "${value}"`); | ||
| return { l: Number(m[1]), c: Number(m[2]), h: Number(m[3]) }; | ||
| } | ||
| describe("tokens.color.chart", () => { | ||
| it("exists and has all 10 chart keys", () => { | ||
| assert.ok(tokens.color.chart, "tokens.color.chart must exist"); | ||
| for (const key of CHART_KEYS) { | ||
| assert.ok( | ||
| key in tokens.color.chart, | ||
| `tokens.color.chart.${key} is missing`, | ||
| ); | ||
| } | ||
| assert.equal( | ||
| Object.keys(tokens.color.chart).length, | ||
| CHART_KEYS.length, | ||
| "tokens.color.chart has unexpected extra keys", | ||
| ); | ||
| }); | ||
| describe("series (dark)", () => { | ||
| for (let i = 1; i <= 8; i++) { | ||
| const key = `series${i}`; | ||
| it(`${key} is a valid OKLCH color with L >= 0.65 on dark ground`, () => { | ||
| const oklch = parseOklch(tokens.color.chart[key], key); | ||
| assert.ok( | ||
| oklch.l >= 0.65, | ||
| `${key} luminance ${oklch.l} is below 0.65 — won't read on dark ground`, | ||
| ); | ||
| assert.ok( | ||
| oklch.c <= 0.16, | ||
| `${key} chroma ${oklch.c} is too high — low-chroma palette only`, | ||
| ); | ||
| }); | ||
| } | ||
| }); | ||
| it("grid is a valid rgba color on dark", () => { | ||
| const v = tokens.color.chart.grid; | ||
| assert.match( | ||
| v, | ||
| /^rgba\(/, | ||
| `chart.grid expected rgba(), got "${v}"`, | ||
| ); | ||
| }); | ||
| it("axis matches fgMuted on dark", () => { | ||
| assert.equal( | ||
| tokens.color.chart.axis, | ||
| tokens.color.fgMuted, | ||
| "chart.axis must equal fgMuted", | ||
| ); | ||
| }); | ||
| }); | ||
| describe("light.color.chart", () => { | ||
| it("exists and has all 10 chart keys", () => { | ||
| assert.ok(light.color.chart, "light.color.chart must exist"); | ||
| for (const key of CHART_KEYS) { | ||
| assert.ok( | ||
| key in light.color.chart, | ||
| `light.color.chart.${key} is missing`, | ||
| ); | ||
| } | ||
| assert.equal( | ||
| Object.keys(light.color.chart).length, | ||
| CHART_KEYS.length, | ||
| "light.color.chart has unexpected extra keys", | ||
| ); | ||
| }); | ||
| describe("series (light)", () => { | ||
| for (let i = 1; i <= 8; i++) { | ||
| const key = `series${i}`; | ||
| it(`${key} is a valid OKLCH color with L <= 0.60 on light ground`, () => { | ||
| const oklch = parseOklch(light.color.chart[key], key); | ||
| assert.ok( | ||
| oklch.l <= 0.60, | ||
| `${key} luminance ${oklch.l} is above 0.60 — may wash out on light ground`, | ||
| ); | ||
| assert.ok( | ||
| oklch.l >= 0.42, | ||
| `${key} luminance ${oklch.l} is below 0.42 — too dark for light ground`, | ||
| ); | ||
| assert.ok( | ||
| oklch.c <= 0.18, | ||
| `${key} chroma ${oklch.c} is too high — low-chroma palette only`, | ||
| ); | ||
| }); | ||
| } | ||
| }); | ||
| it("grid is a valid rgba color on light", () => { | ||
| const v = light.color.chart.grid; | ||
| assert.match( | ||
| v, | ||
| /^rgba\(/, | ||
| `chart.grid expected rgba(), got "${v}"`, | ||
| ); | ||
| }); | ||
| it("axis matches fgMuted on light", () => { | ||
| assert.equal( | ||
| light.color.chart.axis, | ||
| light.color.fgMuted, | ||
| "chart.axis must equal light fgMuted", | ||
| ); | ||
| }); | ||
| }); | ||
| describe("chart hue distinctiveness", () => { | ||
| it("dark series hues are spread across the spectrum (no two within 20° hue)", () => { | ||
| const hues = []; | ||
| for (let i = 1; i <= 8; i++) { | ||
| const oklch = parseOklch(tokens.color.chart[`series${i}`], `series${i}`); | ||
| hues.push(oklch.h); | ||
| } | ||
| for (let i = 0; i < hues.length; i++) { | ||
| for (let j = i + 1; j < hues.length; j++) { | ||
| const diff = Math.abs(hues[i] - hues[j]); | ||
| const circularDiff = Math.min(diff, 360 - diff); | ||
| assert.ok( | ||
| circularDiff >= 20, | ||
| `series${i + 1} (${hues[i]}°) and series${j + 1} (${hues[j]}°) are only ${circularDiff.toFixed(1)}° apart — not distinguishable enough`, | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| it("light series hues match dark series hues (same anchor)", () => { | ||
| for (let i = 1; i <= 8; i++) { | ||
| const key = `series${i}`; | ||
| const darkHue = parseOklch(tokens.color.chart[key], key).h; | ||
| const lightHue = parseOklch(light.color.chart[key], key).h; | ||
| assert.equal( | ||
| darkHue, | ||
| lightHue, | ||
| `${key} hue drift: dark=${darkHue}°, light=${lightHue}°`, | ||
| ); | ||
| } | ||
| }); | ||
| }); |
+40
-0
@@ -30,2 +30,12 @@ /* GENERATED from src/tokens.js by scripts/build.mjs — do not edit by hand. */ | ||
| --pl-color-status-info: oklch(0.68 0.12 230); | ||
| --pl-color-chart-series1: oklch(0.72 0.14 290); | ||
| --pl-color-chart-series2: oklch(0.70 0.12 250); | ||
| --pl-color-chart-series3: oklch(0.72 0.13 170); | ||
| --pl-color-chart-series4: oklch(0.74 0.14 135); | ||
| --pl-color-chart-series5: oklch(0.78 0.12 80); | ||
| --pl-color-chart-series6: oklch(0.70 0.14 30); | ||
| --pl-color-chart-series7: oklch(0.68 0.12 210); | ||
| --pl-color-chart-series8: oklch(0.70 0.13 320); | ||
| --pl-color-chart-grid: rgba(255, 255, 255, 0.06); | ||
| --pl-color-chart-axis: #a1a1aa; | ||
| --pl-gradient-brand: linear-gradient(135deg, var(--pl-color-accent) 0%, var(--pl-color-brand-indigo-bright) 50%, var(--pl-color-brand-indigo) 100%); | ||
@@ -89,2 +99,12 @@ --pl-gradient-brand-light: linear-gradient(135deg, var(--pl-color-accent) 0%, var(--pl-color-brand-indigo) 50%, var(--pl-color-brand-indigo-deep) 100%); | ||
| --pl-color-status-info: oklch(0.58 0.17 255); | ||
| --pl-color-chart-series1: oklch(0.52 0.16 290); | ||
| --pl-color-chart-series2: oklch(0.50 0.14 250); | ||
| --pl-color-chart-series3: oklch(0.52 0.15 170); | ||
| --pl-color-chart-series4: oklch(0.54 0.16 135); | ||
| --pl-color-chart-series5: oklch(0.58 0.14 80); | ||
| --pl-color-chart-series6: oklch(0.50 0.16 30); | ||
| --pl-color-chart-series7: oklch(0.48 0.14 210); | ||
| --pl-color-chart-series8: oklch(0.50 0.15 320); | ||
| --pl-color-chart-grid: rgba(0, 0, 0, 0.06); | ||
| --pl-color-chart-axis: #52525b; | ||
| --pl-gradient-brand: linear-gradient(135deg, var(--pl-color-accent) 0%, var(--pl-color-brand-indigo) 50%, var(--pl-color-brand-indigo-deep) 100%); | ||
@@ -118,2 +138,12 @@ --pl-shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.12); | ||
| --pl-color-status-info: oklch(0.58 0.17 255); | ||
| --pl-color-chart-series1: oklch(0.52 0.16 290); | ||
| --pl-color-chart-series2: oklch(0.50 0.14 250); | ||
| --pl-color-chart-series3: oklch(0.52 0.15 170); | ||
| --pl-color-chart-series4: oklch(0.54 0.16 135); | ||
| --pl-color-chart-series5: oklch(0.58 0.14 80); | ||
| --pl-color-chart-series6: oklch(0.50 0.16 30); | ||
| --pl-color-chart-series7: oklch(0.48 0.14 210); | ||
| --pl-color-chart-series8: oklch(0.50 0.15 320); | ||
| --pl-color-chart-grid: rgba(0, 0, 0, 0.06); | ||
| --pl-color-chart-axis: #52525b; | ||
| --pl-gradient-brand: linear-gradient(135deg, var(--pl-color-accent) 0%, var(--pl-color-brand-indigo) 50%, var(--pl-color-brand-indigo-deep) 100%); | ||
@@ -144,4 +174,14 @@ --pl-shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.12); | ||
| --pl-color-status-info: oklch(0.68 0.12 230); | ||
| --pl-color-chart-series1: oklch(0.72 0.14 290); | ||
| --pl-color-chart-series2: oklch(0.70 0.12 250); | ||
| --pl-color-chart-series3: oklch(0.72 0.13 170); | ||
| --pl-color-chart-series4: oklch(0.74 0.14 135); | ||
| --pl-color-chart-series5: oklch(0.78 0.12 80); | ||
| --pl-color-chart-series6: oklch(0.70 0.14 30); | ||
| --pl-color-chart-series7: oklch(0.68 0.12 210); | ||
| --pl-color-chart-series8: oklch(0.70 0.13 320); | ||
| --pl-color-chart-grid: rgba(255, 255, 255, 0.06); | ||
| --pl-color-chart-axis: #a1a1aa; | ||
| --pl-gradient-brand: linear-gradient(135deg, var(--pl-color-accent) 0%, var(--pl-color-brand-indigo-bright) 50%, var(--pl-color-brand-indigo) 100%); | ||
| --pl-shadow-popover: 0 8px 28px rgba(0, 0, 0, 0.5); | ||
| } |
+24
-0
@@ -32,2 +32,14 @@ { | ||
| "info": "oklch(0.68 0.12 230)" | ||
| }, | ||
| "chart": { | ||
| "series1": "oklch(0.72 0.14 290)", | ||
| "series2": "oklch(0.70 0.12 250)", | ||
| "series3": "oklch(0.72 0.13 170)", | ||
| "series4": "oklch(0.74 0.14 135)", | ||
| "series5": "oklch(0.78 0.12 80)", | ||
| "series6": "oklch(0.70 0.14 30)", | ||
| "series7": "oklch(0.68 0.12 210)", | ||
| "series8": "oklch(0.70 0.13 320)", | ||
| "grid": "rgba(255, 255, 255, 0.06)", | ||
| "axis": "#a1a1aa" | ||
| } | ||
@@ -100,2 +112,14 @@ }, | ||
| "info": "oklch(0.58 0.17 255)" | ||
| }, | ||
| "chart": { | ||
| "series1": "oklch(0.52 0.16 290)", | ||
| "series2": "oklch(0.50 0.14 250)", | ||
| "series3": "oklch(0.52 0.15 170)", | ||
| "series4": "oklch(0.54 0.16 135)", | ||
| "series5": "oklch(0.58 0.14 80)", | ||
| "series6": "oklch(0.50 0.16 30)", | ||
| "series7": "oklch(0.48 0.14 210)", | ||
| "series8": "oklch(0.50 0.15 320)", | ||
| "grid": "rgba(0, 0, 0, 0.06)", | ||
| "axis": "#52525b" | ||
| } | ||
@@ -102,0 +126,0 @@ }, |
+3
-2
| { | ||
| "name": "@protolabsai/design", | ||
| "version": "0.7.1", | ||
| "version": "0.7.2", | ||
| "publishConfig": { | ||
@@ -36,4 +36,5 @@ "access": "public", | ||
| "scripts": { | ||
| "build": "node scripts/build.mjs" | ||
| "build": "node scripts/build.mjs", | ||
| "test": "node --test src/tokens.test.mjs" | ||
| } | ||
| } |
+29
-0
@@ -63,2 +63,18 @@ /** | ||
| }, | ||
| // ── Charts — 8 categorical series + semantic helpers ── | ||
| // Luminance >= 0.65 so every swatch reads against the dark ground (#0a0a0c). | ||
| // series1 anchors on brand lavender; the rest span distinguishable hues. | ||
| // Low-chroma OKLCH throughout — never neon. | ||
| chart: { | ||
| series1: "oklch(0.72 0.14 290)", // brand lavender anchor | ||
| series2: "oklch(0.70 0.12 250)", // sky | ||
| series3: "oklch(0.72 0.13 170)", // teal | ||
| series4: "oklch(0.74 0.14 135)", // emerald | ||
| series5: "oklch(0.78 0.12 80)", // amber | ||
| series6: "oklch(0.70 0.14 30)", // warm rose | ||
| series7: "oklch(0.68 0.12 210)", // steel | ||
| series8: "oklch(0.70 0.13 320)", // violet | ||
| grid: "rgba(255, 255, 255, 0.06)", // subtle axis lines, ~border opacity | ||
| axis: "#a1a1aa", // tick text — matches fgMuted | ||
| }, | ||
| }, | ||
@@ -137,2 +153,15 @@ gradient: { | ||
| }, | ||
| // Light-ground chart — lower luminance so swatches read on white. | ||
| chart: { | ||
| series1: "oklch(0.52 0.16 290)", // brand lavender anchor | ||
| series2: "oklch(0.50 0.14 250)", // sky | ||
| series3: "oklch(0.52 0.15 170)", // teal | ||
| series4: "oklch(0.54 0.16 135)", // emerald | ||
| series5: "oklch(0.58 0.14 80)", // amber | ||
| series6: "oklch(0.50 0.16 30)", // warm rose | ||
| series7: "oklch(0.48 0.14 210)", // steel | ||
| series8: "oklch(0.50 0.15 320)", // violet | ||
| grid: "rgba(0, 0, 0, 0.06)", // subtle axis lines | ||
| axis: "#52525b", // tick text — matches light fgMuted | ||
| }, | ||
| }, | ||
@@ -139,0 +168,0 @@ gradient: { brand: tokens.gradient.brandLight }, |
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
1264819
0.71%15
7.14%845
40.37%2
-33.33%