@symblight/md-colors 0.1.0 → 0.2.0

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
@@ -10,44 +10,61 @@ Built on [`@material/material-color-utilities`](https://github.com/material-foun
10
10
  npm install @symblight/md-colors
11
11
  ```
12
12
 
13
- ## Two builds
13
+ ## Builds
14
14
 
15
15
  | Build | Entry | Format | Use case |
16
16
  |---|---|---|---|
17
- | Node / bundler | `dist/index.js` | ESM | Programmatic token generation, CSS file output |
18
- | Browser (bundler) | `dist/client.esm.js` | ESM | Vite, Storybook, bundler imports |
19
- | Browser (script) | `dist/client.js` | IIFE | Plain `<script>` tag, no bundler |
17
+ | Node / bundler | `dist/index.js` | ESM | `generateTokens`, `generateCSSFile` |
18
+ | Browser (bundler) | `dist/client.esm.js` | ESM | Vite, Storybook — named import |
19
+ | Browser (CJS) | `dist/client.cjs` | CJS | `require("@symblight/md-colors/client")` |
20
+ | Browser (script) | `dist/client.js` | IIFE | Plain `<script>` tag — `window.MdColors` |
21
+ | CLI | `dist/cli.js` | ESM | Terminal usage via `md-colors` |
20
22
 
21
23
  ---
22
24
 
23
25
  ## Node — programmatic API
24
26
 
25
- Import `generateTokens` to build token maps in Node.js scripts or server-side tooling.
26
-
27
27
  ```js
28
- import { generateTokens } from "@symblight/md-colors";
28
+ import { generateTokens, generateCSSFile } from "@symblight/md-colors";
29
+ ```
30
+
31
+ ### `generateTokens(config)`
32
+
33
+ Returns a token map — use it to apply tokens however you like. All 49 MD3
34
+ color roles are resolved from one tonal-spot scheme (the M3 default), including
35
+ `surface-container`, `surface-tint` and the `*-fixed` / `*-fixed-dim` /
36
+ `on-*-fixed` / `on-*-fixed-variant` roles. Missing config fields fall back to
37
+ their defaults; an invalid `sourceColor` or `scheme` throws a `TypeError`.
29
38
 
39
+ ```js
30
40
  const tokens = generateTokens({ sourceColor: "#6750A4", scheme: "dark" });
31
41
  // { "--md-sys-color-primary": "oklch(…)", … }
32
42
  ```
33
43
 
34
- ### `generateTokens(config)`
35
-
36
44
  | Param | Type | Default | Description |
37
45
  |---|---|---|---|
38
46
  | `config.sourceColor` | `string` | `"#1D5D78"` | Seed color as a hex string |
39
47
  | `config.scheme` | `"light" \| "dark"` | `"light"` | Color scheme variant |
40
48
 
41
- Returns `Record<string, string>` — CSS variable names mapped to oklch color strings.
42
-
43
- ### Generate a CSS file
49
+ ### `generateCSSFile(config)`
44
50
 
45
- Run the following to write a `colors.css` file to the package directory:
51
+ Writes a `colors.css` file with `:root`-scoped tokens. Returns a promise that
52
+ resolves with the absolute output path and rejects if the write fails.
46
53
 
47
- ```bash
48
- pnpm generate-theme
54
+ ```js
55
+ await generateCSSFile({
56
+ sourceColor: "#6750A4",
57
+ scheme: "dark",
58
+ output: "./theme/colors.css",
59
+ });
49
60
  ```
50
61
 
62
+ | Param | Type | Default | Description |
63
+ |---|---|---|---|
64
+ | `config.sourceColor` | `string` | `"#1D5D78"` | Seed color as a hex string |
65
+ | `config.scheme` | `"light" \| "dark"` | `"light"` | Color scheme variant |
66
+ | `config.output` | `string` | `./colors.css` (next to module) | Output file path |
67
+
51
68
  Output:
52
69
 
53
70
  ```css
@@ -59,47 +76,77 @@ Output:
59
76
  }
60
77
  ```
61
78
 
62
- Source color and scheme are configured directly in `create-theme-file.mjs`.
63
-
64
79
  ---
65
80
 
66
81
  ## Browser client — runtime theming
67
82
 
68
- The browser build registers `window.generateTheme`, which injects tokens as CSS custom properties on `:root` at runtime.
83
+ Import `generateTheme` and call it directly — no globals, no window assignment.
69
84
 
70
- ### Via ES module import (Vite, Storybook)
85
+ ### Named import (Vite, Storybook, bundler)
71
86
 
72
87
  ```js
73
- import "@symblight/md-colors/client";
88
+ import { generateTheme } from "@symblight/md-colors/client";
74
89
 
75
- window.generateTheme({ sourceColor: "#6750A4", scheme: "dark" });
90
+ generateTheme({ sourceColor: "#6750A4", scheme: "dark" });
76
91
  ```
77
92
 
78
93
  ### Via `<script>` tag (no bundler)
79
94
 
95
+ ```html
96
+ <script type="module">
97
+ import { generateTheme } from "./node_modules/@symblight/md-colors/dist/client.esm.js";
98
+ generateTheme({ sourceColor: "#6750A4", scheme: "dark" });
99
+ </script>
100
+ ```
101
+
102
+ Or with the self-contained IIFE build:
103
+
80
104
  ```html
81
105
  <script src="node_modules/@symblight/md-colors/dist/client.js"></script>
82
106
  <script>
83
- window.generateTheme({ sourceColor: "#6750A4", scheme: "dark" });
107
+ MdColors.generateTheme({ sourceColor: "#6750A4", scheme: "dark" });
84
108
  </script>
85
109
  ```
86
110
 
87
- ### `window.generateTheme(options)`
111
+ ### `generateTheme(options)`
88
112
 
89
113
  | Param | Type | Default | Description |
90
114
  |---|---|---|---|
91
- | `options.sourceColor` | `string` | — | Seed color as a hex string |
115
+ | `options.sourceColor` | `string` | `"#1D5D78"` | Seed color as a hex string |
92
116
  | `options.scheme` | `"light" \| "dark"` | `"light"` | Color scheme variant |
93
117
 
94
118
  Sets every `--md-sys-color-*` variable on `document.documentElement` via `style.setProperty`.
95
119
 
96
120
  ---
97
121
 
122
+ ## CLI
123
+
124
+ ```bash
125
+ # npx (no install)
126
+ npx md-colors --sourceColor="#6750A4" --scheme=dark --output=./theme/colors.css
127
+
128
+ # global install
129
+ npm install -g @symblight/md-colors
130
+ md-colors --sourceColor="#6750A4" --scheme=light --output=./colors.css
131
+ ```
132
+
133
+ ### Options
134
+
135
+ | Flag | Short | Default | Description |
136
+ |---|---|---|---|
137
+ | `--sourceColor` | `-c` | `#1D5D78` | Seed color as a hex string |
138
+ | `--scheme` | `-s` | `light` | `light` or `dark` |
139
+ | `--output` | `-o` | `./colors.css` | Output file path |
140
+
141
+ Exits with code 1 on an invalid color, invalid scheme or failed write.
142
+
143
+ ---
144
+
98
145
  ## Development
99
146
 
100
147
  ```bash
101
148
  pnpm build # Build all dist outputs
102
- pnpm generate-theme # Write colors.css
149
+ pnpm generate-theme # Write colors.css via CLI (uses defaults)
103
150
  pnpm test # Run Jest tests
104
151
  ```
105
152
 
package/dist/cli.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli.mjs
4
+ import { parseArgs } from "node:util";
5
+
6
+ // create-theme-file.mjs
7
+ import path from "node:path";
8
+ import fs from "node:fs/promises";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ // tokens.mjs
12
+ import {
13
+ argbFromHex,
14
+ hexFromArgb,
15
+ MaterialDynamicColors,
16
+ SchemeTonalSpot,
17
+ Hct
18
+ } from "@material/material-color-utilities";
19
+ import { converter, formatCss } from "culori";
20
+ var oklch = converter("oklch");
21
+ var DEFAULT_SOURCE_COLOR = "#1D5D78";
22
+ var HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
23
+ var COLOR_ROLES = (
24
+ /** @type {const} */
25
+ [
26
+ "primary",
27
+ "onPrimary",
28
+ "primaryContainer",
29
+ "onPrimaryContainer",
30
+ "inversePrimary",
31
+ "primaryFixed",
32
+ "primaryFixedDim",
33
+ "onPrimaryFixed",
34
+ "onPrimaryFixedVariant",
35
+ "secondary",
36
+ "onSecondary",
37
+ "secondaryContainer",
38
+ "onSecondaryContainer",
39
+ "secondaryFixed",
40
+ "secondaryFixedDim",
41
+ "onSecondaryFixed",
42
+ "onSecondaryFixedVariant",
43
+ "tertiary",
44
+ "onTertiary",
45
+ "tertiaryContainer",
46
+ "onTertiaryContainer",
47
+ "tertiaryFixed",
48
+ "tertiaryFixedDim",
49
+ "onTertiaryFixed",
50
+ "onTertiaryFixedVariant",
51
+ "error",
52
+ "onError",
53
+ "errorContainer",
54
+ "onErrorContainer",
55
+ "surface",
56
+ "onSurface",
57
+ "surfaceVariant",
58
+ "onSurfaceVariant",
59
+ "surfaceDim",
60
+ "surfaceBright",
61
+ "surfaceContainerLowest",
62
+ "surfaceContainerLow",
63
+ "surfaceContainer",
64
+ "surfaceContainerHigh",
65
+ "surfaceContainerHighest",
66
+ "surfaceTint",
67
+ "inverseSurface",
68
+ "inverseOnSurface",
69
+ "outline",
70
+ "outlineVariant",
71
+ "shadow",
72
+ "scrim",
73
+ "background",
74
+ "onBackground"
75
+ ]
76
+ );
77
+ function resolveConfig({ sourceColor = DEFAULT_SOURCE_COLOR, scheme = "light" } = {}) {
78
+ if (typeof sourceColor !== "string" || !HEX_RE.test(sourceColor)) {
79
+ throw new TypeError(`sourceColor must be a hex color like "#6750A4", got ${JSON.stringify(sourceColor)}`);
80
+ }
81
+ if (scheme !== "light" && scheme !== "dark") {
82
+ throw new TypeError(`scheme must be "light" or "dark", got ${JSON.stringify(scheme)}`);
83
+ }
84
+ return { sourceColor, scheme: (
85
+ /** @type {ColorScheme} */
86
+ scheme
87
+ ) };
88
+ }
89
+ function setSchemeProperties(scheme, suffix = "") {
90
+ let result = {};
91
+ for (const [key, value] of Object.entries(scheme)) {
92
+ const token = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
93
+ const color = hexFromArgb(value);
94
+ const converted = oklch(color);
95
+ const name = formatCss(converted);
96
+ const variableName = `--md-sys-color-${token}${suffix}`;
97
+ result[variableName] = name;
98
+ }
99
+ return result;
100
+ }
101
+ function generateTokens(config = {}) {
102
+ const { sourceColor, scheme } = resolveConfig(config);
103
+ const dynamicScheme = new SchemeTonalSpot(
104
+ Hct.fromInt(argbFromHex(sourceColor)),
105
+ scheme === "dark",
106
+ 0
107
+ );
108
+ let palette = {};
109
+ for (const role of COLOR_ROLES) {
110
+ palette[role] = MaterialDynamicColors[role].getArgb(dynamicScheme);
111
+ }
112
+ return setSchemeProperties(palette);
113
+ }
114
+
115
+ // create-theme-file.mjs
116
+ function template(variables) {
117
+ return `:root {
118
+ ${variables}
119
+ }`;
120
+ }
121
+ function normalizeCSSVariablesContent(variables = {}) {
122
+ let str = "";
123
+ for (const cssVar in variables) {
124
+ str += `${cssVar}: ${variables[cssVar]};
125
+ `;
126
+ }
127
+ return str;
128
+ }
129
+ async function generateCSSFile(config = {}) {
130
+ const { output: output2, ...tokenConfig } = config;
131
+ const __filename = fileURLToPath(import.meta.url);
132
+ const __dirname = path.dirname(__filename);
133
+ const outputPath = output2 ? path.resolve(output2) : path.join(__dirname, "colors.css");
134
+ const tokens = generateTokens(tokenConfig);
135
+ const stringTokens = normalizeCSSVariablesContent(tokens);
136
+ const themeBody = template(stringTokens);
137
+ await fs.writeFile(outputPath, themeBody);
138
+ return outputPath;
139
+ }
140
+
141
+ // cli.mjs
142
+ var { values } = parseArgs({
143
+ options: {
144
+ sourceColor: { type: "string", short: "c", default: DEFAULT_SOURCE_COLOR },
145
+ scheme: { type: "string", short: "s", default: "light" },
146
+ output: { type: "string", short: "o", default: "./colors.css" }
147
+ }
148
+ });
149
+ var { output } = values;
150
+ try {
151
+ const { sourceColor, scheme } = resolveConfig(values);
152
+ const outputPath = await generateCSSFile({ sourceColor, scheme, output });
153
+ console.log(`Generated ${outputPath} (sourceColor: ${sourceColor}, scheme: ${scheme})`);
154
+ } catch (err) {
155
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
156
+ process.exit(1);
157
+ }