@clhaas/palette-kit 0.1.2 → 0.1.3

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 CHANGED
@@ -73,6 +73,57 @@ body {
73
73
  }
74
74
  ```
75
75
 
76
+ ## Step-by-step (install -> usage -> types)
77
+
78
+ 1) Install:
79
+
80
+ ```bash
81
+ npm install @clhaas/palette-kit
82
+ ```
83
+
84
+ 2) Create a config file (`palette.config.mjs`):
85
+
86
+ ```js
87
+ /** @type {import("@clhaas/palette-kit").CreateThemeOptions} */
88
+ export default {
89
+ neutral: { source: "seed", value: "#111827" },
90
+ accent: { source: "seed", value: "#3d63dd" },
91
+ semantic: {
92
+ success: { source: "seed", value: "#16a34a" },
93
+ warning: { source: "seed", value: "#f59e0b" },
94
+ danger: { source: "seed", value: "#ef4444" },
95
+ },
96
+ tokens: { preset: "radix-like-ui" },
97
+ alpha: { enabled: true },
98
+ p3: true,
99
+ };
100
+ ```
101
+
102
+ 3) Generate a typed theme file (includes token name types):
103
+
104
+ ```bash
105
+ npx palette-kit generate --out src/theme.ts
106
+ ```
107
+
108
+ 4) Export CSS vars (for web apps):
109
+
110
+ ```ts
111
+ import { toCssVars } from "@clhaas/palette-kit";
112
+ import { theme } from "./theme";
113
+
114
+ const css = toCssVars(theme, { prefix: "pk" });
115
+ // write the string into a .css file or inject it at build time
116
+ ```
117
+
118
+ 5) Use the generated types:
119
+
120
+ ```ts
121
+ import { theme, ThemeTokenMap, ThemeTokenName } from "./theme";
122
+
123
+ const tokens: ThemeTokenMap = theme.tokens.light;
124
+ const tokenName: ThemeTokenName = "bg.app";
125
+ ```
126
+
76
127
  ## React Native + Expo
77
128
 
78
129
  Use the React Native exporter and `useColorScheme()`:
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { createTheme } from "./createTheme.js";
7
+ import { toTs, toTsWithMode } from "./exporters/toTs.js";
8
+ const DEFAULT_CONFIG_FILES = [
9
+ "palette.config.mjs",
10
+ "palette.config.js",
11
+ "palette.config.cjs",
12
+ "palette.config.json",
13
+ ];
14
+ const args = process.argv.slice(2);
15
+ const command = args[0];
16
+ if (!command || command === "--help" || command === "-h") {
17
+ printHelp();
18
+ process.exit(0);
19
+ }
20
+ if (command !== "generate") {
21
+ console.error(`Unknown command: ${command}`);
22
+ printHelp();
23
+ process.exit(1);
24
+ }
25
+ const options = parseArgs(args.slice(1));
26
+ const configPath = resolveConfigPath(options.configPath);
27
+ const outPath = path.resolve(process.cwd(), options.outPath ?? "src/theme.ts");
28
+ const mode = options.mode;
29
+ const config = await loadConfig(configPath);
30
+ if (!config || typeof config !== "object") {
31
+ console.error("Config must export a plain object with createTheme options.");
32
+ process.exit(1);
33
+ }
34
+ if (!("neutral" in config) || !("accent" in config)) {
35
+ console.error("Config must include at least `neutral` and `accent` sources.");
36
+ process.exit(1);
37
+ }
38
+ const theme = createTheme(config);
39
+ const ts = mode ? toTsWithMode(theme, mode) : toTs(theme);
40
+ mkdirSync(path.dirname(outPath), { recursive: true });
41
+ writeFileSync(outPath, ts);
42
+ console.log(`Generated ${path.relative(process.cwd(), outPath)}`);
43
+ function parseArgs(input) {
44
+ const options = {};
45
+ for (let i = 0; i < input.length; i += 1) {
46
+ const arg = input[i];
47
+ if (arg === "--config" || arg === "-c") {
48
+ options.configPath = input[i + 1];
49
+ i += 1;
50
+ continue;
51
+ }
52
+ if (arg?.startsWith("--config=")) {
53
+ options.configPath = arg.split("=")[1];
54
+ continue;
55
+ }
56
+ if (arg === "--out" || arg === "-o") {
57
+ options.outPath = input[i + 1];
58
+ i += 1;
59
+ continue;
60
+ }
61
+ if (arg?.startsWith("--out=")) {
62
+ options.outPath = arg.split("=")[1];
63
+ continue;
64
+ }
65
+ if (arg === "--mode" || arg === "-m") {
66
+ const value = input[i + 1];
67
+ if (!value) {
68
+ console.error('Missing value for --mode. Use "srgb" or "p3".');
69
+ process.exit(1);
70
+ }
71
+ if (value !== "srgb" && value !== "p3") {
72
+ console.error(`Invalid mode: ${value}. Use "srgb" or "p3".`);
73
+ process.exit(1);
74
+ }
75
+ options.mode = value;
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (arg?.startsWith("--mode=")) {
80
+ const value = arg.split("=")[1];
81
+ if (!value) {
82
+ console.error('Missing value for --mode. Use "srgb" or "p3".');
83
+ process.exit(1);
84
+ }
85
+ if (value !== "srgb" && value !== "p3") {
86
+ console.error(`Invalid mode: ${value}. Use "srgb" or "p3".`);
87
+ process.exit(1);
88
+ }
89
+ options.mode = value;
90
+ continue;
91
+ }
92
+ if (arg === "--help" || arg === "-h") {
93
+ printHelp();
94
+ process.exit(0);
95
+ }
96
+ console.error(`Unknown argument: ${arg}`);
97
+ printHelp();
98
+ process.exit(1);
99
+ }
100
+ return options;
101
+ }
102
+ function resolveConfigPath(configPath) {
103
+ if (configPath) {
104
+ const resolved = path.resolve(process.cwd(), configPath);
105
+ if (!existsSync(resolved)) {
106
+ console.error(`Config not found: ${configPath}`);
107
+ process.exit(1);
108
+ }
109
+ return resolved;
110
+ }
111
+ for (const filename of DEFAULT_CONFIG_FILES) {
112
+ const candidate = path.resolve(process.cwd(), filename);
113
+ if (existsSync(candidate)) {
114
+ return candidate;
115
+ }
116
+ }
117
+ console.error("No config file found. Expected one of:");
118
+ for (const filename of DEFAULT_CONFIG_FILES) {
119
+ console.error(` - ${filename}`);
120
+ }
121
+ process.exit(1);
122
+ throw new Error("No config file found.");
123
+ }
124
+ async function loadConfig(filePath) {
125
+ const ext = path.extname(filePath).toLowerCase();
126
+ if (ext === ".json") {
127
+ const raw = readFileSync(filePath, "utf8");
128
+ return JSON.parse(raw);
129
+ }
130
+ if (ext === ".cjs") {
131
+ const require = createRequire(import.meta.url);
132
+ return require(filePath);
133
+ }
134
+ const module = await import(pathToFileURL(filePath).href);
135
+ return (module.default ?? module.config ?? module.theme ?? module);
136
+ }
137
+ function printHelp() {
138
+ console.log([
139
+ "palette-kit generate [options]",
140
+ "",
141
+ "Options:",
142
+ " -c, --config <file> Config file (default: palette.config.*)",
143
+ " -o, --out <file> Output file (default: src/theme.ts)",
144
+ " -m, --mode <srgb|p3> Export mode (default: theme as-is)",
145
+ " -h, --help Show help",
146
+ "",
147
+ "Example:",
148
+ " palette-kit generate --config palette.config.mjs --out src/theme.ts",
149
+ ].join("\n"));
150
+ }
@@ -1,14 +1,14 @@
1
1
  export function toTs(theme) {
2
2
  const serialized = JSON.stringify(theme, null, 2);
3
- return `export const theme = ${serialized} as const;\n`;
3
+ return `export const theme = ${serialized} as const;\n${typeExports()}`;
4
4
  }
5
5
  export function toTsWithMode(theme, mode) {
6
6
  if (mode === "p3") {
7
7
  const serialized = JSON.stringify(toP3Theme(theme), null, 2);
8
- return `export const theme = ${serialized} as const;\n`;
8
+ return `export const theme = ${serialized} as const;\n${typeExports()}`;
9
9
  }
10
10
  const serialized = JSON.stringify(theme, null, 2);
11
- return `export const theme = ${serialized} as const;\n`;
11
+ return `export const theme = ${serialized} as const;\n${typeExports()}`;
12
12
  }
13
13
  function toP3Theme(theme) {
14
14
  const scales = Object.fromEntries(Object.entries(theme.scales).map(([slot, scale]) => {
@@ -26,3 +26,12 @@ function toP3Theme(theme) {
26
26
  }));
27
27
  return { ...theme, scales };
28
28
  }
29
+ function typeExports() {
30
+ return [
31
+ "export type Theme = typeof theme;",
32
+ 'export type ThemeScaleName = keyof Theme["scales"];',
33
+ 'export type ThemeTokenName = keyof Theme["tokens"]["light"];',
34
+ 'export type ThemeTokenMap = Theme["tokens"]["light"];',
35
+ "",
36
+ ].join("\n");
37
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { generateAlphaScale } from "./alpha/generateAlphaScale.js";
2
2
  export { apcaContrast } from "./contrast/apca.js";
3
3
  export { onSolidTextTokens } from "./contrast/onSolid.js";
4
4
  export { adjustTextColor } from "./contrast/solveText.js";
5
+ export type { CreateThemeOptions, TokenOverrides } from "./createTheme.js";
5
6
  export { createTheme } from "./createTheme.js";
6
7
  export { radixSeedNames, radixSeeds } from "./data/radixSeeds.js";
7
8
  export { analyzeScale } from "./diagnostics/analyzeScale.js";
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@clhaas/palette-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
10
+ "bin": {
11
+ "palette-kit": "./dist/cli.js"
12
+ },
10
13
  "exports": {
11
14
  ".": {
12
15
  "types": "./dist/index.d.ts",
@@ -19,6 +22,7 @@
19
22
  "scripts": {
20
23
  "build": "tsc -p tsconfig.build.json",
21
24
  "clean": "rm -rf dist",
25
+ "generate:theme": "tsx -e \"import { createTheme, toTs } from './src/index.ts'; import { writeFileSync } from 'node:fs'; const theme = createTheme({ neutral: { source: 'seed', value: '#111827' }, accent: { source: 'seed', value: '#3d63dd' }, semantic: { success: { source: 'seed', value: '#16a34a' }, warning: { source: 'seed', value: '#f59e0b' }, danger: { source: 'seed', value: '#ef4444' } }, tokens: { preset: 'radix-like-ui' }, alpha: { enabled: true, background: { light: '#ffffff', dark: '#111111' } }, contrast: { textPrimary: 75, textSecondary: 60 }, p3: true }); const ts = toTs(theme); writeFileSync('docs/theme.generated.ts', ts);\"",
22
26
  "prepublishOnly": "npm run build",
23
27
  "test": "vitest run",
24
28
  "test:watch": "vitest",
@@ -41,6 +45,7 @@
41
45
  "devDependencies": {
42
46
  "@biomejs/biome": "^2.3.11",
43
47
  "@types/apca-w3": "^0.1.3",
48
+ "@types/node": "^22.10.5",
44
49
  "markdownlint-cli": "^0.47.0",
45
50
  "tsx": "^4.21.0",
46
51
  "typescript": "^5.9.3",