@clhaas/palette-kit 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/alpha/generateAlphaScale.d.ts +5 -0
- package/dist/alpha/generateAlphaScale.js +34 -0
- package/dist/contrast/apca.d.ts +2 -0
- package/dist/contrast/apca.js +5 -0
- package/dist/contrast/onSolid.d.ts +6 -0
- package/dist/contrast/onSolid.js +28 -0
- package/dist/contrast/solveText.d.ts +2 -0
- package/dist/contrast/solveText.js +31 -0
- package/dist/createTheme.d.ts +34 -0
- package/dist/createTheme.js +88 -0
- package/dist/data/radixSeeds.d.ts +3 -0
- package/dist/data/radixSeeds.js +34 -0
- package/dist/diagnostics/analyzeScale.d.ts +2 -0
- package/dist/diagnostics/analyzeScale.js +7 -0
- package/dist/diagnostics/analyzeTheme.d.ts +2 -0
- package/dist/diagnostics/analyzeTheme.js +35 -0
- package/dist/diagnostics/warnings.d.ts +2 -0
- package/dist/diagnostics/warnings.js +20 -0
- package/dist/engine/curves.d.ts +9 -0
- package/dist/engine/curves.js +48 -0
- package/dist/engine/oklch.d.ts +8 -0
- package/dist/engine/oklch.js +40 -0
- package/dist/engine/templates.d.ts +14 -0
- package/dist/engine/templates.js +45 -0
- package/dist/exporters/selectColorMode.d.ts +2 -0
- package/dist/exporters/selectColorMode.js +19 -0
- package/dist/exporters/toCssVars.d.ts +12 -0
- package/dist/exporters/toCssVars.js +84 -0
- package/dist/exporters/toJson.d.ts +3 -0
- package/dist/exporters/toJson.js +25 -0
- package/dist/exporters/toReactNative.d.ts +30 -0
- package/dist/exporters/toReactNative.js +26 -0
- package/dist/exporters/toTailwind.d.ts +16 -0
- package/dist/exporters/toTailwind.js +90 -0
- package/dist/exporters/toTs.d.ts +3 -0
- package/dist/exporters/toTs.js +28 -0
- package/dist/generateScale.d.ts +48 -0
- package/dist/generateScale.js +274 -0
- package/dist/index.d.ts +17 -0
- package/{src/index.ts → dist/index.js} +0 -15
- package/dist/tokens/presetRadixLikeUi.d.ts +5 -0
- package/dist/tokens/presetRadixLikeUi.js +55 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.js +1 -0
- package/package.json +19 -3
- package/.markdownlint.json +0 -4
- package/biome.json +0 -43
- package/src/alpha/generateAlphaScale.ts +0 -43
- package/src/contrast/apca.ts +0 -7
- package/src/contrast/onSolid.ts +0 -38
- package/src/contrast/solveText.ts +0 -49
- package/src/createTheme.ts +0 -130
- package/src/data/radixSeeds.ts +0 -37
- package/src/diagnostics/analyzeScale.ts +0 -6
- package/src/diagnostics/analyzeTheme.ts +0 -54
- package/src/diagnostics/warnings.ts +0 -25
- package/src/engine/curves.ts +0 -64
- package/src/engine/oklch.ts +0 -53
- package/src/engine/templates.ts +0 -58
- package/src/exporters/selectColorMode.ts +0 -25
- package/src/exporters/toCssVars.ts +0 -116
- package/src/exporters/toJson.ts +0 -31
- package/src/exporters/toReactNative.ts +0 -39
- package/src/exporters/toTailwind.ts +0 -110
- package/src/exporters/toTs.ts +0 -34
- package/src/generateScale.ts +0 -163
- package/src/tokens/presetRadixLikeUi.ts +0 -75
- package/src/types.ts +0 -63
- package/tsconfig.json +0 -14
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const tokenMap = [
|
|
2
|
+
{ token: "bg.app", slot: "neutral", step: 1 },
|
|
3
|
+
{ token: "bg.subtle", slot: "neutral", step: 2 },
|
|
4
|
+
{ token: "surface.card", slot: "neutral", step: 2 },
|
|
5
|
+
{ token: "surface.raised", slot: "neutral", step: 3 },
|
|
6
|
+
{ token: "component.bg", slot: "neutral", step: 3 },
|
|
7
|
+
{ token: "component.bgHover", slot: "neutral", step: 4 },
|
|
8
|
+
{ token: "component.bgActive", slot: "neutral", step: 5 },
|
|
9
|
+
{ token: "border.subtle", slot: "neutral", step: 6 },
|
|
10
|
+
{ token: "border.default", slot: "neutral", step: 7 },
|
|
11
|
+
{ token: "border.strong", slot: "neutral", step: 8 },
|
|
12
|
+
{ token: "text.secondary", slot: "neutral", step: 11 },
|
|
13
|
+
{ token: "text.primary", slot: "neutral", step: 12 },
|
|
14
|
+
{ token: "text.disabled", slot: "neutral", step: 10 },
|
|
15
|
+
{ token: "focus.ring", slot: "accent", step: 8 },
|
|
16
|
+
{ token: "accent.solid", slot: "accent", step: 9 },
|
|
17
|
+
{ token: "accent.solidHover", slot: "accent", step: 10 },
|
|
18
|
+
{ token: "accent.border", slot: "accent", step: 7 },
|
|
19
|
+
{ token: "accent.subtle", slot: "accent", step: 3 },
|
|
20
|
+
{ token: "accent.subtleHover", slot: "accent", step: 4 },
|
|
21
|
+
{ token: "status.success.solidBg", slot: "success", step: 9, required: false },
|
|
22
|
+
{ token: "status.success.solidHover", slot: "success", step: 10, required: false },
|
|
23
|
+
{ token: "status.success.subtleBg", slot: "success", step: 3, required: false },
|
|
24
|
+
{ token: "status.success.border", slot: "success", step: 7, required: false },
|
|
25
|
+
{ token: "status.success.text", slot: "success", step: 11, required: false },
|
|
26
|
+
{ token: "status.success.textStrong", slot: "success", step: 12, required: false },
|
|
27
|
+
{ token: "status.warning.solidBg", slot: "warning", step: 9, required: false },
|
|
28
|
+
{ token: "status.warning.solidHover", slot: "warning", step: 10, required: false },
|
|
29
|
+
{ token: "status.warning.subtleBg", slot: "warning", step: 3, required: false },
|
|
30
|
+
{ token: "status.warning.border", slot: "warning", step: 7, required: false },
|
|
31
|
+
{ token: "status.warning.text", slot: "warning", step: 11, required: false },
|
|
32
|
+
{ token: "status.warning.textStrong", slot: "warning", step: 12, required: false },
|
|
33
|
+
{ token: "status.danger.solidBg", slot: "danger", step: 9, required: false },
|
|
34
|
+
{ token: "status.danger.solidHover", slot: "danger", step: 10, required: false },
|
|
35
|
+
{ token: "status.danger.subtleBg", slot: "danger", step: 3, required: false },
|
|
36
|
+
{ token: "status.danger.border", slot: "danger", step: 7, required: false },
|
|
37
|
+
{ token: "status.danger.text", slot: "danger", step: 11, required: false },
|
|
38
|
+
{ token: "status.danger.textStrong", slot: "danger", step: 12, required: false },
|
|
39
|
+
];
|
|
40
|
+
export function buildPresetTokens(scales) {
|
|
41
|
+
const light = {};
|
|
42
|
+
const dark = {};
|
|
43
|
+
for (const { token, slot, step, required } of tokenMap) {
|
|
44
|
+
const scale = scales[slot];
|
|
45
|
+
if (!scale) {
|
|
46
|
+
if (required === false) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Missing scale for slot: ${slot}`);
|
|
50
|
+
}
|
|
51
|
+
light[token] = scale.light[step];
|
|
52
|
+
dark[token] = scale.dark[step];
|
|
53
|
+
}
|
|
54
|
+
return { light, dark };
|
|
55
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type Step = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
|
2
|
+
export type ColorHex = `#${string}`;
|
|
3
|
+
export type ColorP3 = string;
|
|
4
|
+
export type RadixSeedName = string;
|
|
5
|
+
export type TemplateId = "neutral" | "warm" | "cool";
|
|
6
|
+
export type ColorSource = {
|
|
7
|
+
source: "seed";
|
|
8
|
+
value: ColorHex;
|
|
9
|
+
} | {
|
|
10
|
+
source: "radix";
|
|
11
|
+
name: RadixSeedName;
|
|
12
|
+
};
|
|
13
|
+
export type ScaleDiagnostics = {
|
|
14
|
+
outOfGamutCount: number;
|
|
15
|
+
outOfP3GamutCount?: number;
|
|
16
|
+
anchorSteps?: {
|
|
17
|
+
light: Step;
|
|
18
|
+
dark: Step;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type Scale = {
|
|
22
|
+
light: Record<Step, ColorHex>;
|
|
23
|
+
dark: Record<Step, ColorHex>;
|
|
24
|
+
p3?: {
|
|
25
|
+
light: Record<Step, ColorP3>;
|
|
26
|
+
dark: Record<Step, ColorP3>;
|
|
27
|
+
};
|
|
28
|
+
meta?: ScaleDiagnostics;
|
|
29
|
+
};
|
|
30
|
+
export type ScaleColorMode = Omit<Scale, "light" | "dark"> & {
|
|
31
|
+
light: Record<Step, string>;
|
|
32
|
+
dark: Record<Step, string>;
|
|
33
|
+
};
|
|
34
|
+
export type AlphaScale = {
|
|
35
|
+
light: Record<Step, ColorHex>;
|
|
36
|
+
dark: Record<Step, ColorHex>;
|
|
37
|
+
};
|
|
38
|
+
export type ThemeDiagnostics = {
|
|
39
|
+
contrast: Record<string, number>;
|
|
40
|
+
outOfGamutCount: number;
|
|
41
|
+
warnings?: string[];
|
|
42
|
+
};
|
|
43
|
+
export type Theme = {
|
|
44
|
+
scales: Record<string, Scale>;
|
|
45
|
+
tokens: {
|
|
46
|
+
light: Record<string, ColorHex>;
|
|
47
|
+
dark: Record<string, ColorHex>;
|
|
48
|
+
};
|
|
49
|
+
alpha?: AlphaScale;
|
|
50
|
+
diagnostics?: ThemeDiagnostics;
|
|
51
|
+
};
|
|
52
|
+
export type ThemeColorMode = Omit<Theme, "scales"> & {
|
|
53
|
+
scales: Record<string, ScaleColorMode>;
|
|
54
|
+
};
|
|
55
|
+
export type OklchColor = {
|
|
56
|
+
l: number;
|
|
57
|
+
c: number;
|
|
58
|
+
h: number;
|
|
59
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clhaas/palette-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Easy way to create the color palette of your app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Claus Haas",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "index.js",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
9
16
|
"publishConfig": {
|
|
10
17
|
"access": "public"
|
|
11
18
|
},
|
|
12
19
|
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
13
23
|
"test": "vitest run",
|
|
14
24
|
"test:watch": "vitest",
|
|
15
25
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
16
26
|
"typecheck:tests": "tsc -p tsconfig.test.json --noEmit",
|
|
17
|
-
"lint:biome": "biome check
|
|
27
|
+
"lint:biome": "biome check --unsafe --write",
|
|
18
28
|
"lint:md": "markdownlint \"**/*.md\" --ignore node_modules",
|
|
19
29
|
"lint": "npm run lint:biome && npm run lint:md && npm run typecheck && npm run typecheck:tests",
|
|
20
30
|
"update": "npx npm-check-updates -i"
|
|
21
31
|
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
22
37
|
"dependencies": {
|
|
23
38
|
"apca-w3": "^0.1.9",
|
|
24
39
|
"colorjs.io": "^0.6.0"
|
|
@@ -27,6 +42,7 @@
|
|
|
27
42
|
"@biomejs/biome": "^2.3.11",
|
|
28
43
|
"@types/apca-w3": "^0.1.3",
|
|
29
44
|
"markdownlint-cli": "^0.47.0",
|
|
45
|
+
"tsx": "^4.21.0",
|
|
30
46
|
"typescript": "^5.9.3",
|
|
31
47
|
"vitest": "^4.0.16"
|
|
32
48
|
}
|
package/.markdownlint.json
DELETED
package/biome.json
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
|
3
|
-
"formatter": {
|
|
4
|
-
"enabled": true,
|
|
5
|
-
"indentStyle": "space",
|
|
6
|
-
"indentWidth": 2,
|
|
7
|
-
"lineWidth": 100
|
|
8
|
-
},
|
|
9
|
-
"linter": {
|
|
10
|
-
"enabled": true,
|
|
11
|
-
"rules": {
|
|
12
|
-
"recommended": true
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": {
|
|
16
|
-
"ignoreUnknown": true,
|
|
17
|
-
"includes": [
|
|
18
|
-
"**/*.js",
|
|
19
|
-
"**/*.ts",
|
|
20
|
-
"**/*.tsx",
|
|
21
|
-
"**/*.css",
|
|
22
|
-
"**/*.scss",
|
|
23
|
-
"**/*.html",
|
|
24
|
-
"**/*.json",
|
|
25
|
-
"**/*.md",
|
|
26
|
-
"!**/supabase/functions",
|
|
27
|
-
"!**/node_modules",
|
|
28
|
-
"!**/dist",
|
|
29
|
-
"!**/build",
|
|
30
|
-
"!**/coverage",
|
|
31
|
-
"!**/ios",
|
|
32
|
-
"!**/android",
|
|
33
|
-
"!**/.git/",
|
|
34
|
-
"!**/.vscode",
|
|
35
|
-
"!**/*.d.ts",
|
|
36
|
-
"!**/*.spec.ts",
|
|
37
|
-
"!**/*.test.ts",
|
|
38
|
-
"!**/.github",
|
|
39
|
-
"!**/examples",
|
|
40
|
-
"!**/tests"
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import Color from "colorjs.io";
|
|
2
|
-
import type { AlphaScale, ColorHex, Step } from "../types.js";
|
|
3
|
-
|
|
4
|
-
const steps: Step[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
5
|
-
const alphaCurve: Record<Step, number> = {
|
|
6
|
-
1: 0.05,
|
|
7
|
-
2: 0.1,
|
|
8
|
-
3: 0.15,
|
|
9
|
-
4: 0.2,
|
|
10
|
-
5: 0.3,
|
|
11
|
-
6: 0.4,
|
|
12
|
-
7: 0.5,
|
|
13
|
-
8: 0.6,
|
|
14
|
-
9: 0.7,
|
|
15
|
-
10: 0.8,
|
|
16
|
-
11: 0.9,
|
|
17
|
-
12: 0.95,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
function mixWithAlpha(foreground: ColorHex, alpha: number): ColorHex {
|
|
21
|
-
const color = new Color(foreground).to("srgb");
|
|
22
|
-
const [r, g, b] = color.coords;
|
|
23
|
-
const hex = new Color({ space: "srgb", coords: [r, g, b], alpha }).toString({
|
|
24
|
-
format: "hex",
|
|
25
|
-
});
|
|
26
|
-
return hex as ColorHex;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function generateAlphaScale(
|
|
30
|
-
base: ColorHex,
|
|
31
|
-
_background: { light: ColorHex; dark: ColorHex },
|
|
32
|
-
): AlphaScale {
|
|
33
|
-
const light: Record<Step, ColorHex> = {} as Record<Step, ColorHex>;
|
|
34
|
-
const dark: Record<Step, ColorHex> = {} as Record<Step, ColorHex>;
|
|
35
|
-
|
|
36
|
-
for (const step of steps) {
|
|
37
|
-
const alpha = alphaCurve[step];
|
|
38
|
-
light[step] = mixWithAlpha(base, alpha);
|
|
39
|
-
dark[step] = mixWithAlpha(base, alpha);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return { light, dark };
|
|
43
|
-
}
|
package/src/contrast/apca.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { calcAPCA } from "apca-w3";
|
|
2
|
-
import type { ColorHex } from "../types.js";
|
|
3
|
-
|
|
4
|
-
export function apcaContrast(foreground: ColorHex, background: ColorHex): number {
|
|
5
|
-
const contrast = Number(calcAPCA(foreground, background));
|
|
6
|
-
return Number(contrast.toFixed(2));
|
|
7
|
-
}
|
package/src/contrast/onSolid.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { ColorHex } from "../types.js";
|
|
2
|
-
import { apcaContrast } from "./apca.js";
|
|
3
|
-
|
|
4
|
-
const white: ColorHex = "#ffffff";
|
|
5
|
-
const black: ColorHex = "#000000";
|
|
6
|
-
|
|
7
|
-
const alphaLevels = {
|
|
8
|
-
primary: 0.92,
|
|
9
|
-
secondary: 0.72,
|
|
10
|
-
disabled: 0.48,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function withAlpha(hex: ColorHex, alpha: number): ColorHex {
|
|
14
|
-
const normalized = hex.replace("#", "");
|
|
15
|
-
const alphaHex = Math.round(alpha * 255)
|
|
16
|
-
.toString(16)
|
|
17
|
-
.padStart(2, "0");
|
|
18
|
-
return `#${normalized}${alphaHex}` as ColorHex;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function chooseTextColor(background: ColorHex): ColorHex {
|
|
22
|
-
const whiteScore = Math.abs(apcaContrast(white, background));
|
|
23
|
-
const blackScore = Math.abs(apcaContrast(black, background));
|
|
24
|
-
return whiteScore >= blackScore ? white : black;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function onSolidTextTokens(background: ColorHex): {
|
|
28
|
-
primary: ColorHex;
|
|
29
|
-
secondary: ColorHex;
|
|
30
|
-
disabled: ColorHex;
|
|
31
|
-
} {
|
|
32
|
-
const base = chooseTextColor(background);
|
|
33
|
-
return {
|
|
34
|
-
primary: withAlpha(base, alphaLevels.primary),
|
|
35
|
-
secondary: withAlpha(base, alphaLevels.secondary),
|
|
36
|
-
disabled: withAlpha(base, alphaLevels.disabled),
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import Color from "colorjs.io";
|
|
2
|
-
import { compressToSrgb, oklchToHex } from "../engine/oklch.js";
|
|
3
|
-
import type { ColorHex, OklchColor } from "../types.js";
|
|
4
|
-
import { apcaContrast } from "./apca.js";
|
|
5
|
-
|
|
6
|
-
function clamp(value: number, min: number, max: number): number {
|
|
7
|
-
return Math.min(max, Math.max(min, value));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function adjustLightness(
|
|
11
|
-
oklch: OklchColor,
|
|
12
|
-
background: ColorHex,
|
|
13
|
-
target: number,
|
|
14
|
-
maxIterations = 24,
|
|
15
|
-
): OklchColor {
|
|
16
|
-
const bg = new Color(background).to("oklch");
|
|
17
|
-
const bgL = bg.coords[0] ?? 0;
|
|
18
|
-
const direction = bgL > 0.5 ? -1 : 1;
|
|
19
|
-
let current = { ...oklch };
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < maxIterations; i += 1) {
|
|
22
|
-
const contrast = Math.abs(apcaContrast(oklchToHex(current), background));
|
|
23
|
-
if (contrast >= target) {
|
|
24
|
-
return current;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
current = {
|
|
28
|
-
...current,
|
|
29
|
-
l: clamp(current.l + direction * 0.02, 0, 1),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return current;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function adjustTextColor(
|
|
37
|
-
foreground: ColorHex,
|
|
38
|
-
background: ColorHex,
|
|
39
|
-
target: number,
|
|
40
|
-
): ColorHex {
|
|
41
|
-
const fg = new Color(foreground).to("oklch");
|
|
42
|
-
const [l, c, h] = fg.coords;
|
|
43
|
-
let candidate: OklchColor = { l: l ?? 0, c: c ?? 0, h: h ?? 0 };
|
|
44
|
-
|
|
45
|
-
candidate = adjustLightness(candidate, background, target);
|
|
46
|
-
candidate = compressToSrgb(candidate);
|
|
47
|
-
|
|
48
|
-
return oklchToHex(candidate);
|
|
49
|
-
}
|
package/src/createTheme.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { generateAlphaScale } from "./alpha/generateAlphaScale.js";
|
|
2
|
-
import { onSolidTextTokens } from "./contrast/onSolid.js";
|
|
3
|
-
import { adjustTextColor } from "./contrast/solveText.js";
|
|
4
|
-
import { analyzeTheme } from "./diagnostics/analyzeTheme.js";
|
|
5
|
-
import { generateScale } from "./generateScale.js";
|
|
6
|
-
import { buildPresetTokens } from "./tokens/presetRadixLikeUi.js";
|
|
7
|
-
import type { AlphaScale, ColorHex, ColorSource, Scale, Theme } from "./types.js";
|
|
8
|
-
|
|
9
|
-
export type TokenOverrides = {
|
|
10
|
-
light?: Record<string, ColorHex>;
|
|
11
|
-
dark?: Record<string, ColorHex>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type CreateThemeOptions = {
|
|
15
|
-
neutral: ColorSource;
|
|
16
|
-
accent: ColorSource;
|
|
17
|
-
semantic?: {
|
|
18
|
-
success?: ColorSource;
|
|
19
|
-
warning?: ColorSource;
|
|
20
|
-
danger?: ColorSource;
|
|
21
|
-
};
|
|
22
|
-
extras?: Record<string, ColorSource>;
|
|
23
|
-
tokens?: { preset?: "radix-like-ui"; overrides?: TokenOverrides };
|
|
24
|
-
alpha?: {
|
|
25
|
-
enabled?: boolean;
|
|
26
|
-
background?: { light?: ColorHex; dark?: ColorHex };
|
|
27
|
-
};
|
|
28
|
-
contrast?: {
|
|
29
|
-
textPrimary?: number;
|
|
30
|
-
textSecondary?: number;
|
|
31
|
-
};
|
|
32
|
-
p3?: boolean;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export function createTheme(options: CreateThemeOptions): Theme {
|
|
36
|
-
const includeP3 = options.p3 ?? false;
|
|
37
|
-
const scales: Record<string, Scale> = {
|
|
38
|
-
neutral: generateScale({ source: options.neutral, p3: includeP3 }),
|
|
39
|
-
accent: generateScale({ source: options.accent, p3: includeP3 }),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
if (options.semantic?.success) {
|
|
43
|
-
scales.success = generateScale({ source: options.semantic.success, p3: includeP3 });
|
|
44
|
-
}
|
|
45
|
-
if (options.semantic?.warning) {
|
|
46
|
-
scales.warning = generateScale({ source: options.semantic.warning, p3: includeP3 });
|
|
47
|
-
}
|
|
48
|
-
if (options.semantic?.danger) {
|
|
49
|
-
scales.danger = generateScale({ source: options.semantic.danger, p3: includeP3 });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (options.extras) {
|
|
53
|
-
for (const [key, source] of Object.entries(options.extras)) {
|
|
54
|
-
scales[key] = generateScale({ source, p3: includeP3 });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const preset = options.tokens?.preset ?? "radix-like-ui";
|
|
59
|
-
const tokens = preset === "radix-like-ui" ? buildPresetTokens(scales) : { light: {}, dark: {} };
|
|
60
|
-
|
|
61
|
-
const lightBg = tokens.light["bg.app"];
|
|
62
|
-
const darkBg = tokens.dark["bg.app"];
|
|
63
|
-
const textPrimaryTarget = options.contrast?.textPrimary ?? 75;
|
|
64
|
-
const textSecondaryTarget = options.contrast?.textSecondary ?? 60;
|
|
65
|
-
|
|
66
|
-
if (lightBg && tokens.light["text.primary"]) {
|
|
67
|
-
tokens.light["text.primary"] = adjustTextColor(
|
|
68
|
-
tokens.light["text.primary"],
|
|
69
|
-
lightBg,
|
|
70
|
-
textPrimaryTarget,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
if (lightBg && tokens.light["text.secondary"]) {
|
|
74
|
-
tokens.light["text.secondary"] = adjustTextColor(
|
|
75
|
-
tokens.light["text.secondary"],
|
|
76
|
-
lightBg,
|
|
77
|
-
textSecondaryTarget,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
if (darkBg && tokens.dark["text.primary"]) {
|
|
81
|
-
tokens.dark["text.primary"] = adjustTextColor(
|
|
82
|
-
tokens.dark["text.primary"],
|
|
83
|
-
darkBg,
|
|
84
|
-
textPrimaryTarget,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
if (darkBg && tokens.dark["text.secondary"]) {
|
|
88
|
-
tokens.dark["text.secondary"] = adjustTextColor(
|
|
89
|
-
tokens.dark["text.secondary"],
|
|
90
|
-
darkBg,
|
|
91
|
-
textSecondaryTarget,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const accentScale = scales.accent;
|
|
96
|
-
const lightOnSolid = onSolidTextTokens(accentScale.light[9]);
|
|
97
|
-
const darkOnSolid = onSolidTextTokens(accentScale.dark[9]);
|
|
98
|
-
|
|
99
|
-
tokens.light["onSolid.primary"] = lightOnSolid.primary;
|
|
100
|
-
tokens.light["onSolid.secondary"] = lightOnSolid.secondary;
|
|
101
|
-
tokens.light["onSolid.disabled"] = lightOnSolid.disabled;
|
|
102
|
-
tokens.dark["onSolid.primary"] = darkOnSolid.primary;
|
|
103
|
-
tokens.dark["onSolid.secondary"] = darkOnSolid.secondary;
|
|
104
|
-
tokens.dark["onSolid.disabled"] = darkOnSolid.disabled;
|
|
105
|
-
|
|
106
|
-
if (options.tokens?.overrides?.light) {
|
|
107
|
-
Object.assign(tokens.light, options.tokens.overrides.light);
|
|
108
|
-
}
|
|
109
|
-
if (options.tokens?.overrides?.dark) {
|
|
110
|
-
Object.assign(tokens.dark, options.tokens.overrides.dark);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let alpha: AlphaScale | undefined;
|
|
114
|
-
if (options.alpha?.enabled !== false) {
|
|
115
|
-
const background = {
|
|
116
|
-
light: options.alpha?.background?.light ?? "#ffffff",
|
|
117
|
-
dark: options.alpha?.background?.dark ?? "#111111",
|
|
118
|
-
} as const;
|
|
119
|
-
alpha = generateAlphaScale(accentScale.light[9], background);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const diagnostics = analyzeTheme({ scales, tokens, alpha });
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
scales,
|
|
126
|
-
tokens,
|
|
127
|
-
alpha,
|
|
128
|
-
diagnostics,
|
|
129
|
-
};
|
|
130
|
-
}
|
package/src/data/radixSeeds.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { ColorHex, RadixSeedName } from "../types.js";
|
|
2
|
-
|
|
3
|
-
export const radixSeeds: Record<RadixSeedName, ColorHex> = {
|
|
4
|
-
amber: "#ffc53d",
|
|
5
|
-
blue: "#0090ff",
|
|
6
|
-
bronze: "#a18072",
|
|
7
|
-
brown: "#ad7f58",
|
|
8
|
-
crimson: "#e93d82",
|
|
9
|
-
cyan: "#00a2c7",
|
|
10
|
-
gold: "#978365",
|
|
11
|
-
grass: "#46a758",
|
|
12
|
-
gray: "#8d8d8d",
|
|
13
|
-
green: "#30a46c",
|
|
14
|
-
indigo: "#3e63dd",
|
|
15
|
-
iris: "#5b5bd6",
|
|
16
|
-
jade: "#29a383",
|
|
17
|
-
lime: "#bdee63",
|
|
18
|
-
mauve: "#8e8c99",
|
|
19
|
-
mint: "#86ead4",
|
|
20
|
-
olive: "#898e87",
|
|
21
|
-
orange: "#f76b15",
|
|
22
|
-
pink: "#d6409f",
|
|
23
|
-
plum: "#ab4aba",
|
|
24
|
-
purple: "#8e4ec6",
|
|
25
|
-
red: "#e5484d",
|
|
26
|
-
ruby: "#e54666",
|
|
27
|
-
sage: "#868e8b",
|
|
28
|
-
sand: "#8d8d86",
|
|
29
|
-
sky: "#7ce2fe",
|
|
30
|
-
slate: "#8b8d98",
|
|
31
|
-
teal: "#12a594",
|
|
32
|
-
tomato: "#e54d2e",
|
|
33
|
-
violet: "#6e56cf",
|
|
34
|
-
yellow: "#ffe629",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const radixSeedNames = Object.keys(radixSeeds).sort();
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { apcaContrast } from "../contrast/apca.js";
|
|
2
|
-
import type { Theme, ThemeDiagnostics } from "../types.js";
|
|
3
|
-
import { analyzeWarnings } from "./warnings.js";
|
|
4
|
-
|
|
5
|
-
export function analyzeTheme(theme: Theme): ThemeDiagnostics {
|
|
6
|
-
const contrast: Record<string, number> = {};
|
|
7
|
-
|
|
8
|
-
const lightBg = theme.tokens.light["bg.app"];
|
|
9
|
-
const darkBg = theme.tokens.dark["bg.app"];
|
|
10
|
-
|
|
11
|
-
if (lightBg) {
|
|
12
|
-
if (theme.tokens.light["text.primary"]) {
|
|
13
|
-
contrast["light.text.primary"] = apcaContrast(theme.tokens.light["text.primary"], lightBg);
|
|
14
|
-
}
|
|
15
|
-
if (theme.tokens.light["text.secondary"]) {
|
|
16
|
-
contrast["light.text.secondary"] = apcaContrast(
|
|
17
|
-
theme.tokens.light["text.secondary"],
|
|
18
|
-
lightBg,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (darkBg) {
|
|
24
|
-
if (theme.tokens.dark["text.primary"]) {
|
|
25
|
-
contrast["dark.text.primary"] = apcaContrast(theme.tokens.dark["text.primary"], darkBg);
|
|
26
|
-
}
|
|
27
|
-
if (theme.tokens.dark["text.secondary"]) {
|
|
28
|
-
contrast["dark.text.secondary"] = apcaContrast(theme.tokens.dark["text.secondary"], darkBg);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (theme.tokens.light["onSolid.primary"] && theme.tokens.light["accent.solid"]) {
|
|
33
|
-
contrast["light.onSolid.primary"] = apcaContrast(
|
|
34
|
-
theme.tokens.light["onSolid.primary"],
|
|
35
|
-
theme.tokens.light["accent.solid"],
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (theme.tokens.dark["onSolid.primary"] && theme.tokens.dark["accent.solid"]) {
|
|
40
|
-
contrast["dark.onSolid.primary"] = apcaContrast(
|
|
41
|
-
theme.tokens.dark["onSolid.primary"],
|
|
42
|
-
theme.tokens.dark["accent.solid"],
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let outOfGamutCount = 0;
|
|
47
|
-
for (const scale of Object.values(theme.scales)) {
|
|
48
|
-
outOfGamutCount += scale.meta?.outOfGamutCount ?? 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const warnings = analyzeWarnings(theme);
|
|
52
|
-
|
|
53
|
-
return { contrast, outOfGamutCount, warnings };
|
|
54
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import Color from "colorjs.io";
|
|
2
|
-
import type { Theme } from "../types.js";
|
|
3
|
-
|
|
4
|
-
export function analyzeWarnings(theme: Theme): string[] {
|
|
5
|
-
const warnings: string[] = [];
|
|
6
|
-
|
|
7
|
-
const accent = theme.scales.accent?.light?.[9];
|
|
8
|
-
if (accent) {
|
|
9
|
-
const { coords } = new Color(accent).to("oklch");
|
|
10
|
-
const l = coords[0] ?? 0;
|
|
11
|
-
const c = coords[1] ?? 0;
|
|
12
|
-
|
|
13
|
-
if (c < 0.03) {
|
|
14
|
-
warnings.push("Accent seed has very low chroma; the palette may look gray.");
|
|
15
|
-
}
|
|
16
|
-
if (l < 0.2) {
|
|
17
|
-
warnings.push("Accent seed is very dark; light mode solids may lack contrast.");
|
|
18
|
-
}
|
|
19
|
-
if (l > 0.9) {
|
|
20
|
-
warnings.push("Accent seed is very light; dark mode solids may lack contrast.");
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return warnings;
|
|
25
|
-
}
|
package/src/engine/curves.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { Step } from "../types.js";
|
|
2
|
-
|
|
3
|
-
export type CurveConfig = {
|
|
4
|
-
lightness?: Partial<Record<Step, number>>;
|
|
5
|
-
chroma?: Partial<Record<Step, number>>;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const steps: Step[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
9
|
-
|
|
10
|
-
const defaultLightness: Record<Step, number> = {
|
|
11
|
-
1: 0.3,
|
|
12
|
-
2: 0.3,
|
|
13
|
-
3: 0.6,
|
|
14
|
-
4: 0.6,
|
|
15
|
-
5: 0.6,
|
|
16
|
-
6: 0.85,
|
|
17
|
-
7: 0.85,
|
|
18
|
-
8: 0.85,
|
|
19
|
-
9: 1,
|
|
20
|
-
10: 1,
|
|
21
|
-
11: 1,
|
|
22
|
-
12: 1,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const defaultChroma: Record<Step, number> = {
|
|
26
|
-
1: 0.2,
|
|
27
|
-
2: 0.2,
|
|
28
|
-
3: 0.6,
|
|
29
|
-
4: 0.6,
|
|
30
|
-
5: 0.6,
|
|
31
|
-
6: 0.8,
|
|
32
|
-
7: 0.8,
|
|
33
|
-
8: 0.8,
|
|
34
|
-
9: 1,
|
|
35
|
-
10: 1,
|
|
36
|
-
11: 0.7,
|
|
37
|
-
12: 0.7,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export function resolveCurves(curves?: CurveConfig): {
|
|
41
|
-
lightness: Record<Step, number>;
|
|
42
|
-
chroma: Record<Step, number>;
|
|
43
|
-
} {
|
|
44
|
-
const lightness = { ...defaultLightness };
|
|
45
|
-
const chroma = { ...defaultChroma };
|
|
46
|
-
|
|
47
|
-
if (curves?.lightness) {
|
|
48
|
-
for (const step of steps) {
|
|
49
|
-
if (curves.lightness[step] !== undefined) {
|
|
50
|
-
lightness[step] = curves.lightness[step] as number;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (curves?.chroma) {
|
|
56
|
-
for (const step of steps) {
|
|
57
|
-
if (curves.chroma[step] !== undefined) {
|
|
58
|
-
chroma[step] = curves.chroma[step] as number;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { lightness, chroma };
|
|
64
|
-
}
|