@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.
Files changed (70) hide show
  1. package/README.md +4 -3
  2. package/dist/alpha/generateAlphaScale.d.ts +5 -0
  3. package/dist/alpha/generateAlphaScale.js +34 -0
  4. package/dist/contrast/apca.d.ts +2 -0
  5. package/dist/contrast/apca.js +5 -0
  6. package/dist/contrast/onSolid.d.ts +6 -0
  7. package/dist/contrast/onSolid.js +28 -0
  8. package/dist/contrast/solveText.d.ts +2 -0
  9. package/dist/contrast/solveText.js +31 -0
  10. package/dist/createTheme.d.ts +34 -0
  11. package/dist/createTheme.js +88 -0
  12. package/dist/data/radixSeeds.d.ts +3 -0
  13. package/dist/data/radixSeeds.js +34 -0
  14. package/dist/diagnostics/analyzeScale.d.ts +2 -0
  15. package/dist/diagnostics/analyzeScale.js +7 -0
  16. package/dist/diagnostics/analyzeTheme.d.ts +2 -0
  17. package/dist/diagnostics/analyzeTheme.js +35 -0
  18. package/dist/diagnostics/warnings.d.ts +2 -0
  19. package/dist/diagnostics/warnings.js +20 -0
  20. package/dist/engine/curves.d.ts +9 -0
  21. package/dist/engine/curves.js +48 -0
  22. package/dist/engine/oklch.d.ts +8 -0
  23. package/dist/engine/oklch.js +40 -0
  24. package/dist/engine/templates.d.ts +14 -0
  25. package/dist/engine/templates.js +45 -0
  26. package/dist/exporters/selectColorMode.d.ts +2 -0
  27. package/dist/exporters/selectColorMode.js +19 -0
  28. package/dist/exporters/toCssVars.d.ts +12 -0
  29. package/dist/exporters/toCssVars.js +84 -0
  30. package/dist/exporters/toJson.d.ts +3 -0
  31. package/dist/exporters/toJson.js +25 -0
  32. package/dist/exporters/toReactNative.d.ts +30 -0
  33. package/dist/exporters/toReactNative.js +26 -0
  34. package/dist/exporters/toTailwind.d.ts +16 -0
  35. package/dist/exporters/toTailwind.js +90 -0
  36. package/dist/exporters/toTs.d.ts +3 -0
  37. package/dist/exporters/toTs.js +28 -0
  38. package/dist/generateScale.d.ts +48 -0
  39. package/dist/generateScale.js +274 -0
  40. package/dist/index.d.ts +17 -0
  41. package/{src/index.ts → dist/index.js} +0 -15
  42. package/dist/tokens/presetRadixLikeUi.d.ts +5 -0
  43. package/dist/tokens/presetRadixLikeUi.js +55 -0
  44. package/dist/types.d.ts +59 -0
  45. package/dist/types.js +1 -0
  46. package/package.json +19 -3
  47. package/.markdownlint.json +0 -4
  48. package/biome.json +0 -43
  49. package/src/alpha/generateAlphaScale.ts +0 -43
  50. package/src/contrast/apca.ts +0 -7
  51. package/src/contrast/onSolid.ts +0 -38
  52. package/src/contrast/solveText.ts +0 -49
  53. package/src/createTheme.ts +0 -130
  54. package/src/data/radixSeeds.ts +0 -37
  55. package/src/diagnostics/analyzeScale.ts +0 -6
  56. package/src/diagnostics/analyzeTheme.ts +0 -54
  57. package/src/diagnostics/warnings.ts +0 -25
  58. package/src/engine/curves.ts +0 -64
  59. package/src/engine/oklch.ts +0 -53
  60. package/src/engine/templates.ts +0 -58
  61. package/src/exporters/selectColorMode.ts +0 -25
  62. package/src/exporters/toCssVars.ts +0 -116
  63. package/src/exporters/toJson.ts +0 -31
  64. package/src/exporters/toReactNative.ts +0 -39
  65. package/src/exporters/toTailwind.ts +0 -110
  66. package/src/exporters/toTs.ts +0 -34
  67. package/src/generateScale.ts +0 -163
  68. package/src/tokens/presetRadixLikeUi.ts +0 -75
  69. package/src/types.ts +0 -63
  70. 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
+ }
@@ -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.0",
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
  }
@@ -1,4 +0,0 @@
1
- {
2
- "MD029": false,
3
- "MD013": false
4
- }
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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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,6 +0,0 @@
1
- import type { Scale, ScaleDiagnostics } from "../types.js";
2
-
3
- export function analyzeScale(scale: Scale): ScaleDiagnostics {
4
- const outOfGamutCount = scale.meta?.outOfGamutCount ?? 0;
5
- return { outOfGamutCount };
6
- }
@@ -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
- }
@@ -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
- }