@diskette/palette 0.11.0 → 0.12.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/dist/cli.js CHANGED
@@ -2,18 +2,35 @@ import * as p from '@clack/prompts';
2
2
  import { mkdirSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { blackAlpha, whiteAlpha } from "./colors/index.js";
5
- import { colors, css, grayColors } from "./index.js";
6
- function colorOptions() {
7
- return colors.map((color) => {
8
- const option = {
9
- value: color.name,
10
- label: color.name,
11
- };
12
- if (grayColors.includes(color.name)) {
13
- option.hint = 'gray';
14
- }
15
- return option;
5
+ import { accentColors, colors, css, grayColors } from "./index.js";
6
+ function cancel() {
7
+ p.cancel('Operation cancelled.');
8
+ process.exit(0);
9
+ }
10
+ async function askColors(label, choices) {
11
+ const mode = await p.select({
12
+ message: `Which ${label} colors do you want to generate?`,
13
+ options: [
14
+ { value: 'select', label: 'Select specific colors' },
15
+ {
16
+ value: 'all',
17
+ label: `All ${label} colors`,
18
+ hint: `${choices.length} colors`,
19
+ },
20
+ ],
16
21
  });
22
+ if (p.isCancel(mode))
23
+ cancel();
24
+ if (mode === 'all')
25
+ return choices;
26
+ const selected = await p.autocompleteMultiselect({
27
+ message: `Select ${label} colors:`,
28
+ options: choices.map((name) => ({ value: name, label: name })),
29
+ required: false,
30
+ });
31
+ if (p.isCancel(selected))
32
+ cancel();
33
+ return selected;
17
34
  }
18
35
  async function askOutputDir(message) {
19
36
  const outputDir = await p.text({
@@ -31,105 +48,43 @@ async function askOutputDir(message) {
31
48
  catch { }
32
49
  },
33
50
  });
34
- if (p.isCancel(outputDir)) {
35
- p.cancel('Operation cancelled.');
36
- process.exit(0);
37
- }
51
+ if (p.isCancel(outputDir))
52
+ cancel();
38
53
  return outputDir;
39
54
  }
40
55
  async function main() {
41
56
  p.intro('Palette CSS Generator');
42
- // --- Color Scales ---
43
- const scaleMode = await p.select({
44
- message: 'Which color scales do you want to generate?',
45
- options: [
46
- { value: 'all', label: 'All colors', hint: `${colors.length} colors` },
47
- { value: 'select', label: 'Select specific colors' },
48
- ],
49
- });
50
- if (p.isCancel(scaleMode)) {
51
- p.cancel('Operation cancelled.');
52
- process.exit(0);
53
- }
54
- let scaleColors = colors;
55
- if (scaleMode === 'select') {
56
- const scaleChoices = await p.autocompleteMultiselect({
57
- message: 'Select colors for scales:',
58
- options: colorOptions(),
59
- required: true,
60
- });
61
- if (p.isCancel(scaleChoices)) {
62
- p.cancel('Operation cancelled.');
63
- process.exit(0);
64
- }
65
- scaleColors = colors.filter((c) => scaleChoices.includes(c.name));
66
- }
67
- const scalesOutputDir = await askOutputDir('Output directory for color scales:');
68
- // --- Semantic Tokens ---
69
- const generateSemantic = await p.confirm({
70
- message: 'Generate semantic tokens?',
57
+ const selectedAccentColors = await askColors('accent', accentColors);
58
+ const selectedGrayColors = await askColors('gray', grayColors.slice(1));
59
+ const selectedColors = [...selectedAccentColors, ...selectedGrayColors];
60
+ const generateAccents = await p.confirm({
61
+ message: 'Generate accents for selected colors?',
71
62
  });
72
- if (p.isCancel(generateSemantic)) {
73
- p.cancel('Operation cancelled.');
74
- process.exit(0);
75
- }
76
- let semanticColors = colors;
77
- let semanticOutputDir = null;
78
- if (generateSemantic) {
79
- const semanticMode = await p.select({
80
- message: 'Which colors for semantic tokens?',
81
- options: [
82
- { value: 'all', label: 'All colors', hint: `${colors.length} colors` },
83
- { value: 'select', label: 'Select specific colors' },
84
- ],
85
- });
86
- if (p.isCancel(semanticMode)) {
87
- p.cancel('Operation cancelled.');
88
- process.exit(0);
89
- }
90
- if (semanticMode === 'select') {
91
- const semanticChoices = await p.autocompleteMultiselect({
92
- message: 'Select colors for semantic tokens:',
93
- options: colorOptions(),
94
- required: true,
95
- });
96
- if (p.isCancel(semanticChoices)) {
97
- p.cancel('Operation cancelled.');
98
- process.exit(0);
99
- }
100
- semanticColors = colors.filter((c) => semanticChoices.includes(c.name));
101
- }
102
- semanticOutputDir = await askOutputDir('Output directory for semantic tokens:');
103
- }
63
+ if (p.isCancel(generateAccents))
64
+ cancel();
65
+ const outputDir = await askOutputDir('Output directory:');
104
66
  // --- Generate Files ---
105
67
  const s = p.spinner();
106
68
  s.start('Generating CSS files...');
107
- // Generate color scales
108
- const scalesPath = resolve(process.cwd(), scalesOutputDir);
109
- mkdirSync(scalesPath, { recursive: true });
110
- for (const color of scaleColors) {
111
- const name = color.name;
112
- writeFileSync(`${scalesPath}/${name}.css`, css.scale('light', color));
113
- writeFileSync(`${scalesPath}/${name}-alpha.css`, css.scale('lightAlpha', color));
114
- writeFileSync(`${scalesPath}/${name}-dark.css`, css.scale('dark', color));
115
- writeFileSync(`${scalesPath}/${name}-dark-alpha.css`, css.scale('darkAlpha', color));
69
+ const outputPath = resolve(process.cwd(), outputDir);
70
+ mkdirSync(outputPath, { recursive: true });
71
+ for (const name of selectedColors) {
72
+ const palette = colors.find((c) => c.name === name);
73
+ // Light theme (scales + alphas + semantics)
74
+ writeFileSync(`${outputPath}/${name}.css`, css.palette(name, palette, { schemes: ['light'], alpha: true }));
75
+ // Dark theme (scales + alphas + semantics)
76
+ writeFileSync(`${outputPath}/${name}-dark.css`, css.palette(name, palette, { schemes: ['dark'], alpha: true }));
116
77
  }
117
78
  // Always generate alpha overlay files
118
- writeFileSync(`${scalesPath}/black-alpha.css`, css.alpha(blackAlpha));
119
- writeFileSync(`${scalesPath}/white-alpha.css`, css.alpha(whiteAlpha));
120
- // Generate semantic tokens
121
- if (generateSemantic && semanticOutputDir) {
122
- const semanticPath = resolve(process.cwd(), semanticOutputDir);
123
- mkdirSync(semanticPath, { recursive: true });
124
- for (const color of semanticColors) {
125
- writeFileSync(`${semanticPath}/${color.name}.css`, css.semantic(color.name, color));
126
- }
79
+ writeFileSync(`${outputPath}/black-alpha.css`, css.alpha(blackAlpha));
80
+ writeFileSync(`${outputPath}/white-alpha.css`, css.alpha(whiteAlpha));
81
+ // Generate accents
82
+ if (generateAccents) {
83
+ const accents = css.accents([...selectedAccentColors]);
84
+ const grays = css.grays(selectedGrayColors, 'diskette-palette');
85
+ writeFileSync(`${outputPath}/accents.css`, `${accents}\n${grays}`);
127
86
  }
128
87
  s.stop();
129
- let message = `Color scales saved to ${scalesPath}`;
130
- if (semanticOutputDir) {
131
- message += `\nSemantic tokens saved to ${resolve(process.cwd(), semanticOutputDir)}`;
132
- }
133
- p.outro(message);
88
+ p.outro(`CSS saved to ${outputPath}`);
134
89
  }
135
90
  main().catch(console.error);
package/dist/css.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { AnyPalette } from './types.ts';
2
+ type AlphaConfig = {
3
+ srgb: Record<string, string>;
4
+ p3: Record<string, string>;
5
+ };
6
+ type PaletteOptions = {
7
+ /** Which color schemes to include. Defaults to ['light'] */
8
+ schemes?: ('light' | 'dark')[];
9
+ /** Include alpha scale variables (e.g., --amber-a1). Defaults to false */
10
+ alpha?: boolean;
11
+ };
12
+ export declare const css: {
13
+ /**
14
+ * Generate combined CSS for scale variables and semantic tokens
15
+ */
16
+ palette(name: string, config: AnyPalette, options?: PaletteOptions): string;
17
+ /**
18
+ * Generate CSS for alpha-only color scales
19
+ */
20
+ alpha(config: AlphaConfig): string;
21
+ /**
22
+ * Generate CSS for accent color data attribute selectors
23
+ */
24
+ accents(colorNames: string[]): string;
25
+ /**
26
+ * Generate CSS for gray color data attribute selectors
27
+ */
28
+ grays(names: readonly string[], className: string): string;
29
+ };
30
+ export {};
package/dist/css.js ADDED
@@ -0,0 +1,126 @@
1
+ const steps = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
2
+ const LIGHT_SELECTOR = ':root, .light, .light-theme';
3
+ const DARK_SELECTOR = '.dark, .dark-theme';
4
+ const schemeSelector = (scheme) => scheme === 'light' ? LIGHT_SELECTOR : DARK_SELECTOR;
5
+ /** Convert "amber9" to "--amber-9" or "amberA9" to "--amber-a9" */
6
+ const toVarName = (key) => `--${key.replace(/A?(\d+)/, (m, n) => (m.startsWith('A') ? `-a${n}` : `-${n}`))}`;
7
+ const formatRule = (selector, vars) => `${selector} {\n ${vars.join(';\n ')};\n}`;
8
+ const formatP3 = (selector, vars) => `@supports (color: color(display-p3 1 1 1)) {\n @media (color-gamut: p3) {\n ${selector} {\n ${vars.join(';\n ')};\n }\n }\n}`;
9
+ const formatNestedBlock = (selector, vars) => `&:where(${selector}) {\n ${vars.join(';\n ')};\n }`;
10
+ export const css = {
11
+ /**
12
+ * Generate combined CSS for scale variables and semantic tokens
13
+ */
14
+ palette(name, config, options = {}) {
15
+ const { schemes = ['light'], alpha = false } = options;
16
+ const output = [];
17
+ if (schemes.includes('light')) {
18
+ output.push(formatRule(':root', [
19
+ `--${name}-contrast: ${config.contrast}`,
20
+ `--${name}-indicator: var(${toVarName(config.indicator)})`,
21
+ `--${name}-track: var(${toVarName(config.track)})`,
22
+ ]));
23
+ }
24
+ for (const scheme of schemes) {
25
+ const isLight = scheme === 'light';
26
+ const selector = schemeSelector(scheme);
27
+ const srgbScale = isLight ? config.srgb.light : config.srgb.dark;
28
+ const p3Scale = isLight ? config.p3.light : config.p3.dark;
29
+ const srgbSurface = isLight
30
+ ? config.surface.srgb.light
31
+ : config.surface.srgb.dark;
32
+ const p3Surface = isLight
33
+ ? config.surface.p3.light
34
+ : config.surface.p3.dark;
35
+ const srgb = [];
36
+ const p3 = [];
37
+ for (const n of steps) {
38
+ srgb.push(`--${name}-${n}: ${srgbScale[`${name}${n}`]}`);
39
+ p3.push(`--${name}-${n}: ${p3Scale[`${name}${n}`]}`);
40
+ }
41
+ if (alpha) {
42
+ for (const n of steps) {
43
+ srgb.push(`--${name}-a${n}: ${srgbScale[`${name}A${n}`]}`);
44
+ p3.push(`--${name}-a${n}: ${p3Scale[`${name}A${n}`]}`);
45
+ }
46
+ }
47
+ srgb.push(`--${name}-surface: ${srgbSurface}`);
48
+ p3.push(`--${name}-surface: ${p3Surface}`);
49
+ output.push(formatRule(selector, srgb));
50
+ output.push(formatP3(selector, p3));
51
+ }
52
+ return output.join('\n\n') + '\n';
53
+ },
54
+ /**
55
+ * Generate CSS for alpha-only color scales
56
+ */
57
+ alpha(config) {
58
+ const srgb = [];
59
+ const p3 = [];
60
+ for (const key in config.srgb) {
61
+ srgb.push(`${toVarName(key)}: ${config.srgb[key]}`);
62
+ }
63
+ for (const key in config.p3) {
64
+ p3.push(`${toVarName(key)}: ${config.p3[key]}`);
65
+ }
66
+ return ([formatRule(':root', srgb), formatP3(':root', p3)].join('\n\n') + '\n');
67
+ },
68
+ /**
69
+ * Generate CSS for accent color data attribute selectors
70
+ */
71
+ accents(colorNames) {
72
+ const output = colorNames.map((colorName) => {
73
+ const srgb = [];
74
+ const alpha = [];
75
+ for (const n of steps) {
76
+ srgb.push(`--accent-${n}: var(--${colorName}-${n})`);
77
+ alpha.push(`--accent-a${n}: var(--${colorName}-a${n})`);
78
+ }
79
+ const semantic = [
80
+ `--accent-contrast: var(--${colorName}-contrast)`,
81
+ `--accent-surface: var(--${colorName}-surface)`,
82
+ `--accent-indicator: var(--${colorName}-indicator)`,
83
+ `--accent-track: var(--${colorName}-track)`,
84
+ ];
85
+ return formatRule(`[data-accent-color='${colorName}']`, [
86
+ ...srgb,
87
+ ...alpha,
88
+ ...semantic,
89
+ ]);
90
+ });
91
+ const focus = [];
92
+ const focusAlpha = [];
93
+ for (const n of steps) {
94
+ focus.push(`--focus-${n}: var(--accent-${n})`);
95
+ focusAlpha.push(`--focus-a${n}: var(--accent-a${n})`);
96
+ }
97
+ output.push(formatRule(`[data-accent-color]:where(:not([data-accent-color=''], [data-accent-color='gray']))`, [...focus, ...focusAlpha]));
98
+ return output.join('\n\n') + '\n';
99
+ },
100
+ /**
101
+ * Generate CSS for gray color data attribute selectors
102
+ */
103
+ grays(names, className) {
104
+ const grays = names.filter((n) => n !== 'gray');
105
+ const blocks = grays.map((colorName) => {
106
+ const srgb = [];
107
+ const alpha = [];
108
+ for (const n of steps) {
109
+ srgb.push(`--gray-${n}: var(--${colorName}-${n})`);
110
+ alpha.push(`--gray-a${n}: var(--${colorName}-a${n})`);
111
+ }
112
+ const semantic = [
113
+ `--gray-contrast: var(--${colorName}-contrast)`,
114
+ `--gray-surface: var(--${colorName}-surface)`,
115
+ `--gray-indicator: var(--${colorName}-indicator)`,
116
+ `--gray-track: var(--${colorName}-track)`,
117
+ ];
118
+ return formatNestedBlock(`[data-gray-color='${colorName}']`, [
119
+ ...srgb,
120
+ ...alpha,
121
+ ...semantic,
122
+ ]);
123
+ });
124
+ return `.${className} {\n ${blocks.join('\n\n ')}\n}\n`;
125
+ },
126
+ };
package/dist/index.d.ts CHANGED
@@ -3939,4 +3939,5 @@ export declare const colors: ({
3939
3939
  export default colors;
3940
3940
  export { accentColors, grayColors } from './types.ts';
3941
3941
  export type { AccentColor, AlphaConfig, AlphaScale, Color, ColorPalette, ColorScale, GrayColor, } from './types.ts';
3942
- export { css, type ColorConfig } from './lib.ts';
3942
+ export { css } from './css.ts';
3943
+ export { getMatchingGrayColor } from './utils.ts';
package/dist/index.js CHANGED
@@ -64,4 +64,5 @@ export const colors = [
64
64
  ];
65
65
  export default colors;
66
66
  export { accentColors, grayColors } from "./types.js";
67
- export { css } from "./lib.js";
67
+ export { css } from "./css.js";
68
+ export { getMatchingGrayColor } from "./utils.js";
package/dist/types.d.ts CHANGED
@@ -31,6 +31,7 @@ export type ColorPalette<C extends Color> = {
31
31
  };
32
32
  };
33
33
  };
34
+ export type AnyPalette = ColorPalette<any>;
34
35
  export type AlphaScale<C extends 'white' | 'back'> = {
35
36
  [K in `${C}A${ScaleStep}`]: string;
36
37
  };
@@ -0,0 +1,2 @@
1
+ import type { AccentColor, GrayColor } from './types.ts';
2
+ export declare function getMatchingGrayColor(accentColor: AccentColor): GrayColor;
package/dist/utils.js ADDED
@@ -0,0 +1,37 @@
1
+ export function getMatchingGrayColor(accentColor) {
2
+ switch (accentColor) {
3
+ case 'tomato':
4
+ case 'red':
5
+ case 'ruby':
6
+ case 'crimson':
7
+ case 'pink':
8
+ case 'plum':
9
+ case 'purple':
10
+ case 'violet':
11
+ return 'mauve';
12
+ case 'iris':
13
+ case 'indigo':
14
+ case 'blue':
15
+ case 'sky':
16
+ case 'cyan':
17
+ return 'slate';
18
+ case 'teal':
19
+ case 'jade':
20
+ case 'mint':
21
+ case 'green':
22
+ return 'sage';
23
+ case 'grass':
24
+ case 'lime':
25
+ return 'olive';
26
+ case 'yellow':
27
+ case 'amber':
28
+ case 'orange':
29
+ case 'brown':
30
+ case 'gold':
31
+ case 'bronze':
32
+ return 'sand';
33
+ case 'gray':
34
+ default:
35
+ return 'gray';
36
+ }
37
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diskette/palette",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "bin": {
6
6
  "palette": "./dist/cli.js"
7
7
  },
package/dist/lib.d.ts DELETED
@@ -1,30 +0,0 @@
1
- export type AlphaConfig = {
2
- srgb: Record<string, string>;
3
- p3: Record<string, string>;
4
- };
5
- export type ColorConfig = {
6
- srgb: {
7
- light: Record<string, string> & {
8
- surface: string;
9
- };
10
- dark: Record<string, string> & {
11
- surface: string;
12
- };
13
- };
14
- p3: {
15
- light: Record<string, string> & {
16
- surface: string;
17
- };
18
- dark: Record<string, string> & {
19
- surface: string;
20
- };
21
- };
22
- contrast: string;
23
- indicator: string;
24
- track: string;
25
- };
26
- export declare const css: {
27
- scale(scheme: "light" | "lightAlpha" | "dark" | "darkAlpha", config: ColorConfig): string;
28
- alpha(config: AlphaConfig): string;
29
- semantic(name: string, config: ColorConfig): string;
30
- };
package/dist/lib.js DELETED
@@ -1,89 +0,0 @@
1
- const lightSelector = `:root, .light, .light-theme`;
2
- const darkSelector = `.dark, .dark-theme`;
3
- /** Convert color key to CSS variable name (e.g., "blue1" → "--blue-1", "blueA1" → "--blue-a1") */
4
- const toVarName = (key) => '--' + key.replace(/A?(\d)/, (_, num) => (key.includes('A') ? `-a${num}` : `-${num}`));
5
- export const css = {
6
- scale(scheme, config) {
7
- const isLight = scheme === 'light' || scheme === 'lightAlpha';
8
- const isAlpha = scheme === 'lightAlpha' || scheme === 'darkAlpha';
9
- const selector = isLight ? lightSelector : darkSelector;
10
- const srgbScale = isLight ? config.srgb.light : config.srgb.dark;
11
- const p3Scale = isLight ? config.p3.light : config.p3.dark;
12
- const filterEntries = (obj) => Object.entries(obj).filter(([key]) => {
13
- if (key === 'surface')
14
- return false;
15
- const hasAlpha = key.includes('A');
16
- return isAlpha ? hasAlpha : !hasAlpha;
17
- });
18
- const baseVars = filterEntries(srgbScale)
19
- .map(([key, value]) => ` ${toVarName(key)}: ${value};`)
20
- .join('\n');
21
- const p3Vars = filterEntries(p3Scale)
22
- .map(([key, value]) => ` ${toVarName(key)}: ${value};`)
23
- .join('\n');
24
- return `${selector} {
25
- ${baseVars}
26
- }
27
-
28
- @supports (color: color(display-p3 1 1 1)) {
29
- @media (color-gamut: p3) {
30
- ${selector} {
31
- ${p3Vars}
32
- }
33
- }
34
- }
35
- `;
36
- },
37
- alpha(config) {
38
- const baseVars = Object.entries(config.srgb)
39
- .map(([key, value]) => ` ${toVarName(key)}: ${value};`)
40
- .join('\n');
41
- const p3Vars = Object.entries(config.p3)
42
- .map(([key, value]) => ` ${toVarName(key)}: ${value};`)
43
- .join('\n');
44
- return `:root {
45
- ${baseVars}
46
- }
47
-
48
- @supports (color: color(display-p3 1 1 1)) {
49
- @media (color-gamut: p3) {
50
- :root {
51
- ${p3Vars}
52
- }
53
- }
54
- }
55
- `;
56
- },
57
- semantic(name, config) {
58
- const varRef = (key) => `var(${toVarName(key)})`;
59
- return `:root {
60
- --${name}-contrast: ${config.contrast};
61
- }
62
-
63
- :root,
64
- .light,
65
- .light-theme {
66
- --${name}-surface: ${config.srgb.light.surface};
67
- --${name}-indicator: ${varRef(config.indicator)};
68
- --${name}-track: ${varRef(config.track)};
69
- @supports (color: color(display-p3 1 1 1)) {
70
- @media (color-gamut: p3) {
71
- --${name}-surface: ${config.p3.light.surface};
72
- }
73
- }
74
- }
75
-
76
- .dark,
77
- .dark-theme {
78
- --${name}-surface: ${config.srgb.dark.surface};
79
- --${name}-indicator: ${varRef(config.indicator)};
80
- --${name}-track: ${varRef(config.track)};
81
- @supports (color: color(display-p3 1 1 1)) {
82
- @media (color-gamut: p3) {
83
- --${name}-surface: ${config.p3.dark.surface};
84
- }
85
- }
86
- }
87
- `;
88
- },
89
- };