@clhaas/palette-kit 0.1.0 → 0.1.1

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 (69) hide show
  1. package/dist/alpha/generateAlphaScale.d.ts +5 -0
  2. package/dist/alpha/generateAlphaScale.js +34 -0
  3. package/dist/contrast/apca.d.ts +2 -0
  4. package/dist/contrast/apca.js +5 -0
  5. package/dist/contrast/onSolid.d.ts +6 -0
  6. package/dist/contrast/onSolid.js +28 -0
  7. package/dist/contrast/solveText.d.ts +2 -0
  8. package/dist/contrast/solveText.js +31 -0
  9. package/dist/createTheme.d.ts +32 -0
  10. package/dist/createTheme.js +75 -0
  11. package/dist/data/radixSeeds.d.ts +3 -0
  12. package/dist/data/radixSeeds.js +34 -0
  13. package/dist/diagnostics/analyzeScale.d.ts +2 -0
  14. package/dist/diagnostics/analyzeScale.js +4 -0
  15. package/dist/diagnostics/analyzeTheme.d.ts +2 -0
  16. package/dist/diagnostics/analyzeTheme.js +35 -0
  17. package/dist/diagnostics/warnings.d.ts +2 -0
  18. package/dist/diagnostics/warnings.js +20 -0
  19. package/dist/engine/curves.d.ts +9 -0
  20. package/dist/engine/curves.js +48 -0
  21. package/dist/engine/oklch.d.ts +8 -0
  22. package/dist/engine/oklch.js +40 -0
  23. package/dist/engine/templates.d.ts +14 -0
  24. package/dist/engine/templates.js +45 -0
  25. package/dist/exporters/selectColorMode.d.ts +2 -0
  26. package/dist/exporters/selectColorMode.js +19 -0
  27. package/dist/exporters/toCssVars.d.ts +12 -0
  28. package/dist/exporters/toCssVars.js +84 -0
  29. package/dist/exporters/toJson.d.ts +3 -0
  30. package/dist/exporters/toJson.js +25 -0
  31. package/dist/exporters/toReactNative.d.ts +30 -0
  32. package/dist/exporters/toReactNative.js +26 -0
  33. package/dist/exporters/toTailwind.d.ts +16 -0
  34. package/dist/exporters/toTailwind.js +90 -0
  35. package/dist/exporters/toTs.d.ts +3 -0
  36. package/dist/exporters/toTs.js +28 -0
  37. package/dist/generateScale.d.ts +15 -0
  38. package/dist/generateScale.js +96 -0
  39. package/{src/index.ts → dist/index.d.ts} +1 -15
  40. package/dist/index.js +15 -0
  41. package/dist/tokens/presetRadixLikeUi.d.ts +5 -0
  42. package/dist/tokens/presetRadixLikeUi.js +55 -0
  43. package/dist/types.d.ts +55 -0
  44. package/dist/types.js +1 -0
  45. package/package.json +18 -2
  46. package/.markdownlint.json +0 -4
  47. package/biome.json +0 -43
  48. package/src/alpha/generateAlphaScale.ts +0 -43
  49. package/src/contrast/apca.ts +0 -7
  50. package/src/contrast/onSolid.ts +0 -38
  51. package/src/contrast/solveText.ts +0 -49
  52. package/src/createTheme.ts +0 -130
  53. package/src/data/radixSeeds.ts +0 -37
  54. package/src/diagnostics/analyzeScale.ts +0 -6
  55. package/src/diagnostics/analyzeTheme.ts +0 -54
  56. package/src/diagnostics/warnings.ts +0 -25
  57. package/src/engine/curves.ts +0 -64
  58. package/src/engine/oklch.ts +0 -53
  59. package/src/engine/templates.ts +0 -58
  60. package/src/exporters/selectColorMode.ts +0 -25
  61. package/src/exporters/toCssVars.ts +0 -116
  62. package/src/exporters/toJson.ts +0 -31
  63. package/src/exporters/toReactNative.ts +0 -39
  64. package/src/exporters/toTailwind.ts +0 -110
  65. package/src/exporters/toTs.ts +0 -34
  66. package/src/generateScale.ts +0 -163
  67. package/src/tokens/presetRadixLikeUi.ts +0 -75
  68. package/src/types.ts +0 -63
  69. package/tsconfig.json +0 -14
@@ -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
- }
@@ -1,53 +0,0 @@
1
- import Color from "colorjs.io";
2
-
3
- import type { ColorHex, ColorP3, OklchColor } from "../types.js";
4
-
5
- export function hexToOklch(hex: ColorHex): OklchColor {
6
- const color = new Color(hex).to("oklch");
7
- const [l, c, h] = color.coords;
8
- return { l: l ?? 0, c: c ?? 0, h: h ?? 0 };
9
- }
10
-
11
- export function oklchToHex(oklch: OklchColor): ColorHex {
12
- const color = new Color("oklch", [oklch.l, oklch.c, oklch.h]);
13
- return color.to("srgb").toString({ format: "hex" }) as ColorHex;
14
- }
15
-
16
- export function inSrgbGamut(oklch: OklchColor): boolean {
17
- const color = new Color("oklch", [oklch.l, oklch.c, oklch.h]);
18
- return color.inGamut("srgb");
19
- }
20
-
21
- export function compressToSrgb(oklch: OklchColor): OklchColor {
22
- let current = { ...oklch };
23
- let iterations = 0;
24
-
25
- while (!inSrgbGamut(current) && current.c > 0 && iterations < 40) {
26
- current = { ...current, c: current.c * 0.95 };
27
- iterations += 1;
28
- }
29
-
30
- return current;
31
- }
32
-
33
- export function inP3Gamut(oklch: OklchColor): boolean {
34
- const color = new Color("oklch", [oklch.l, oklch.c, oklch.h]);
35
- return color.inGamut("p3");
36
- }
37
-
38
- export function compressToP3(oklch: OklchColor): OklchColor {
39
- let current = { ...oklch };
40
- let iterations = 0;
41
-
42
- while (!inP3Gamut(current) && current.c > 0 && iterations < 40) {
43
- current = { ...current, c: current.c * 0.95 };
44
- iterations += 1;
45
- }
46
-
47
- return current;
48
- }
49
-
50
- export function oklchToP3(oklch: OklchColor): ColorP3 {
51
- const color = new Color("oklch", [oklch.l, oklch.c, oklch.h]);
52
- return color.to("p3").toString({ format: "color" }) as ColorP3;
53
- }
@@ -1,58 +0,0 @@
1
- import type { OklchColor, Step, TemplateId } from "../types.js";
2
-
3
- const steps: Step[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
4
-
5
- const lightnessLight = [0.99, 0.975, 0.95, 0.92, 0.89, 0.84, 0.77, 0.68, 0.58, 0.5, 0.4, 0.3];
6
- const lightnessDark = [0.12, 0.14, 0.18, 0.22, 0.26, 0.32, 0.4, 0.48, 0.58, 0.66, 0.76, 0.86];
7
-
8
- const chromaNeutral = [
9
- 0.01, 0.012, 0.016, 0.02, 0.024, 0.03, 0.04, 0.05, 0.055, 0.045, 0.035, 0.028,
10
- ];
11
- const chromaWarm = [0.02, 0.03, 0.05, 0.08, 0.12, 0.16, 0.18, 0.2, 0.22, 0.19, 0.12, 0.08];
12
- const chromaCool = [0.02, 0.03, 0.05, 0.075, 0.1, 0.14, 0.17, 0.19, 0.21, 0.18, 0.11, 0.08];
13
-
14
- const baseHue: Record<TemplateId, number> = {
15
- neutral: 250,
16
- warm: 40,
17
- cool: 220,
18
- };
19
-
20
- function buildTemplate(
21
- lightness: number[],
22
- chroma: number[],
23
- hue: number,
24
- ): Record<Step, OklchColor> {
25
- const output = {} as Record<Step, OklchColor>;
26
- for (let i = 0; i < steps.length; i += 1) {
27
- const step = steps[i];
28
- output[step] = {
29
- l: lightness[i],
30
- c: chroma[i],
31
- h: hue,
32
- };
33
- }
34
- return output;
35
- }
36
-
37
- export const templates = {
38
- light: {
39
- neutral: buildTemplate(lightnessLight, chromaNeutral, baseHue.neutral),
40
- warm: buildTemplate(lightnessLight, chromaWarm, baseHue.warm),
41
- cool: buildTemplate(lightnessLight, chromaCool, baseHue.cool),
42
- },
43
- dark: {
44
- neutral: buildTemplate(lightnessDark, chromaNeutral, baseHue.neutral),
45
- warm: buildTemplate(lightnessDark, chromaWarm, baseHue.warm),
46
- cool: buildTemplate(lightnessDark, chromaCool, baseHue.cool),
47
- },
48
- };
49
-
50
- export function selectTemplateId(oklch: OklchColor): TemplateId {
51
- if (oklch.c < 0.05) {
52
- return "neutral";
53
- }
54
-
55
- const hue = ((oklch.h % 360) + 360) % 360;
56
- const isWarm = hue <= 60 || hue >= 330;
57
- return isWarm ? "warm" : "cool";
58
- }
@@ -1,25 +0,0 @@
1
- import type { Theme, ThemeColorMode } from "../types.js";
2
-
3
- export function selectThemeColorMode(theme: Theme, mode: "srgb" | "p3"): ThemeColorMode {
4
- if (mode === "srgb") {
5
- return theme;
6
- }
7
-
8
- const scales = Object.fromEntries(
9
- Object.entries(theme.scales).map(([slot, scale]) => {
10
- if (!scale.p3) {
11
- return [slot, scale];
12
- }
13
- return [
14
- slot,
15
- {
16
- ...scale,
17
- light: scale.p3.light,
18
- dark: scale.p3.dark,
19
- },
20
- ];
21
- }),
22
- );
23
-
24
- return { ...theme, scales };
25
- }
@@ -1,116 +0,0 @@
1
- import Color from "colorjs.io";
2
-
3
- import type { ColorHex, Step, Theme } from "../types.js";
4
-
5
- type CssVarsOptions = {
6
- prefix?: string;
7
- includeTokens?: boolean;
8
- includeScales?: boolean;
9
- includeAlpha?: boolean;
10
- includeP3?: boolean;
11
- lightSelector?: string;
12
- darkSelector?: string;
13
- };
14
-
15
- function tokenToCssVar(token: string, prefix: string): string {
16
- const normalized = token.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
17
- const dashed = normalized.replace(/\./g, "-");
18
- return `--${prefix}-${dashed}`;
19
- }
20
-
21
- function scaleToCssVar(slot: string, step: Step, prefix: string): string {
22
- return `--${prefix}-scale-${slot}-${step}`;
23
- }
24
-
25
- function alphaToCssVar(step: Step, prefix: string): string {
26
- return `--${prefix}-alpha-${step}`;
27
- }
28
-
29
- function hexToP3(value: ColorHex): string {
30
- return new Color(value).to("p3").toString({ format: "color" });
31
- }
32
-
33
- export function toCssVars(theme: Theme, options?: CssVarsOptions): string {
34
- const prefix = options?.prefix ?? "pk";
35
- const includeTokens = options?.includeTokens ?? true;
36
- const includeScales = options?.includeScales ?? true;
37
- const includeAlpha = options?.includeAlpha ?? true;
38
- const includeP3 = options?.includeP3 ?? false;
39
- const lightSelector = options?.lightSelector ?? ":root";
40
- const darkSelector = options?.darkSelector ?? ".dark";
41
-
42
- const lines: string[] = [];
43
- const hasP3 = Object.values(theme.scales).some((scale) => scale.p3);
44
-
45
- function emitSelector(selector: string, mode: "light" | "dark") {
46
- lines.push(`${selector} {`);
47
-
48
- if (includeTokens) {
49
- for (const [token, value] of Object.entries(theme.tokens[mode])) {
50
- lines.push(` ${tokenToCssVar(token, prefix)}: ${value};`);
51
- }
52
- }
53
-
54
- if (includeScales) {
55
- for (const [slot, scale] of Object.entries(theme.scales)) {
56
- for (const [step, value] of Object.entries(scale[mode])) {
57
- lines.push(` ${scaleToCssVar(slot, Number(step) as Step, prefix)}: ${value};`);
58
- }
59
- }
60
- }
61
-
62
- if (includeAlpha && theme.alpha) {
63
- for (const [step, value] of Object.entries(theme.alpha[mode])) {
64
- lines.push(` ${alphaToCssVar(Number(step) as Step, prefix)}: ${value};`);
65
- }
66
- }
67
-
68
- lines.push("}");
69
- }
70
-
71
- emitSelector(lightSelector, "light");
72
- lines.push("");
73
- emitSelector(darkSelector, "dark");
74
-
75
- if (includeP3 && hasP3) {
76
- lines.push("");
77
- lines.push("@supports (color: color(display-p3 1 1 1)) {");
78
-
79
- function emitP3Selector(selector: string, mode: "light" | "dark") {
80
- lines.push(` ${selector} {`);
81
-
82
- if (includeTokens) {
83
- for (const [token, value] of Object.entries(theme.tokens[mode])) {
84
- lines.push(` ${tokenToCssVar(token, prefix)}: ${hexToP3(value)};`);
85
- }
86
- }
87
-
88
- if (includeScales) {
89
- for (const [slot, scale] of Object.entries(theme.scales)) {
90
- const p3Scale = scale.p3?.[mode];
91
- if (!p3Scale) {
92
- continue;
93
- }
94
- for (const [step, value] of Object.entries(p3Scale)) {
95
- lines.push(` ${scaleToCssVar(slot, Number(step) as Step, prefix)}: ${value};`);
96
- }
97
- }
98
- }
99
-
100
- if (includeAlpha && theme.alpha) {
101
- for (const [step, value] of Object.entries(theme.alpha[mode])) {
102
- lines.push(` ${alphaToCssVar(Number(step) as Step, prefix)}: ${hexToP3(value)};`);
103
- }
104
- }
105
-
106
- lines.push(" }");
107
- }
108
-
109
- emitP3Selector(lightSelector, "light");
110
- lines.push("");
111
- emitP3Selector(darkSelector, "dark");
112
- lines.push("}");
113
- }
114
-
115
- return lines.join("\n");
116
- }
@@ -1,31 +0,0 @@
1
- import type { Theme, ThemeColorMode } from "../types.js";
2
-
3
- export function toJson(theme: Theme): string {
4
- return JSON.stringify(theme, null, 2);
5
- }
6
-
7
- export function toJsonWithMode(theme: Theme, mode: "srgb" | "p3"): string {
8
- if (mode === "p3") {
9
- return JSON.stringify(toP3Theme(theme), null, 2);
10
- }
11
- return JSON.stringify(theme, null, 2);
12
- }
13
-
14
- function toP3Theme(theme: Theme): ThemeColorMode {
15
- const scales = Object.fromEntries(
16
- Object.entries(theme.scales).map(([slot, scale]) => {
17
- if (!scale.p3) {
18
- return [slot, scale];
19
- }
20
- return [
21
- slot,
22
- {
23
- ...scale,
24
- light: scale.p3.light,
25
- dark: scale.p3.dark,
26
- },
27
- ];
28
- }),
29
- );
30
- return { ...theme, scales };
31
- }