colbrush 1.4.0 → 1.6.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.cjs CHANGED
@@ -75,16 +75,17 @@ var DEFAULT_SCALE = {
75
75
 
76
76
  // src/core/utils/colorScale.ts
77
77
  var CLAMP01 = (x) => Math.max(0, Math.min(1, x));
78
+ var Color = import_colorjs.default.default ?? import_colorjs.default;
78
79
  function hexToOKLCH(hex) {
79
- const c = new import_colorjs.default(hex);
80
+ const c = new Color(hex);
80
81
  const o = c.to("oklch");
81
82
  return { l: o.l, c: o.c, h: o.h ?? 0 };
82
83
  }
83
84
  function oklchToHex(l, c, h) {
84
- const color = new import_colorjs.default("oklch", [l, c, h]);
85
+ const color = new Color("oklch", [l, c, h]);
85
86
  const srgb = color.to("srgb");
86
87
  const r = CLAMP01(srgb.r), g = CLAMP01(srgb.g), b = CLAMP01(srgb.b);
87
- return new import_colorjs.default("srgb", [r, g, b]).toString({ format: "hex" });
88
+ return new Color("srgb", [r, g, b]).toString({ format: "hex" });
88
89
  }
89
90
  function buildScaleFromBaseHex(baseHex, opts) {
90
91
  const keys = opts?.keys ?? DEFAULT_KEYS;
@@ -190,72 +191,48 @@ function inferRich(varName, baseHex) {
190
191
 
191
192
  // src/cli/colorTransform.ts
192
193
  var import_chroma_js = __toESM(require("chroma-js"), 1);
193
- var import_color_blind = __toESM(require("color-blind"), 1);
194
- var THRESHOLD = 10;
195
- var CANDIDATE_COUNT = 10;
196
- var HUE_STEP = 180;
197
- function generateCandidates(hex) {
198
- const [baseHue] = (0, import_chroma_js.default)(hex).hcl();
199
- const candidates = [];
200
- candidates[0] = hex;
201
- for (let i = 1; i <= CANDIDATE_COUNT / 2; i++) {
202
- candidates[i * 2 - 1] = (0, import_chroma_js.default)(hex).set("hcl.h", (baseHue + HUE_STEP / CANDIDATE_COUNT * i) % 360).hex();
203
- candidates[i * 2] = (0, import_chroma_js.default)(hex).set(
204
- "hcl.h",
205
- (baseHue - HUE_STEP / CANDIDATE_COUNT * i + 360) % 360
206
- ).hex();
207
- }
208
- return candidates;
209
- }
210
- function findOptimalColorCombination(colorKeys, baseColorsArray, candidateList, vision) {
211
- const n = baseColorsArray.length;
212
- let bestColors = null;
213
- let minDeltaSum = Infinity;
214
- let blind;
215
- switch (vision) {
216
- // 적색맹
217
- case "protanopia":
218
- blind = import_color_blind.default.protanopia;
219
- break;
220
- // 녹색맹
221
- case "deuteranopia":
222
- blind = import_color_blind.default.deuteranopia;
223
- break;
224
- // 청색맹
225
- case "tritanopia":
226
- blind = import_color_blind.default.tritanopia;
227
- break;
228
- default:
229
- throw new Error("Invalid color blindness option");
230
- }
231
- function dfs(index, currentColors, totalDelta) {
232
- if (index === n) {
233
- if (isValidColors(currentColors, blind)) {
234
- if (totalDelta < minDeltaSum) {
235
- minDeltaSum = totalDelta;
236
- bestColors = [...currentColors];
237
- }
238
- }
239
- return;
194
+ var ALPHA = 1.5;
195
+ function colorTranslate(colorKeys, baseColorsArray, vision) {
196
+ const labArray = baseColorsArray.map((hex) => (0, import_chroma_js.default)(hex).lab());
197
+ const newLabArray = labArray.map((lab) => {
198
+ const newLab = [...lab];
199
+ switch (vision) {
200
+ case "protanopia":
201
+ newLab[2] += ALPHA * lab[1];
202
+ break;
203
+ case "deuteranopia":
204
+ newLab[2] += ALPHA * lab[1];
205
+ break;
206
+ case "tritanopia":
207
+ newLab[1] += ALPHA * 0.2 * lab[2];
208
+ break;
240
209
  }
241
- for (const candidate of candidateList[index]) {
242
- const delta = import_chroma_js.default.deltaE(baseColorsArray[index], candidate);
243
- if (totalDelta + delta > minDeltaSum) continue;
244
- currentColors.push(candidate);
245
- dfs(index + 1, currentColors, totalDelta + delta);
246
- currentColors.pop();
247
- }
248
- }
249
- dfs(0, [], 0);
250
- const finalColors = bestColors ?? [...baseColorsArray];
210
+ return newLab;
211
+ });
212
+ const newColorArray = newLabArray.map((lab) => import_chroma_js.default.lab(lab[0], lab[1], lab[2]).hex());
251
213
  return colorKeys.reduce(
252
214
  (acc, key, idx) => {
253
- acc[key] = finalColors[idx];
215
+ acc[key] = newColorArray[idx];
254
216
  return acc;
255
217
  },
256
218
  {}
257
219
  );
258
220
  }
221
+ function buildThemeForVision(colorKeys, baseColorsArray, vision) {
222
+ const colors = colorTranslate(
223
+ colorKeys,
224
+ baseColorsArray,
225
+ vision
226
+ );
227
+ const variables = Object.entries(colors).reduce(
228
+ (acc, [varName, baseHex]) => {
229
+ acc[varName] = inferRich(varName, baseHex);
230
+ return acc;
231
+ },
232
+ {}
233
+ );
234
+ return { vision, variables };
235
+ }
259
236
  function prepareCandidates(variables, progress) {
260
237
  const scaleGroups = {};
261
238
  const filteredVariables = {};
@@ -282,30 +259,7 @@ function prepareCandidates(variables, progress) {
282
259
  }
283
260
  const colorKeys = Object.keys(filteredVariables);
284
261
  const baseColorsArray = Object.values(filteredVariables).map((v) => v.base);
285
- progress?.startSection("Generate candidates");
286
- const candidateList = [];
287
- for (let i = 0; i < baseColorsArray.length; i++) {
288
- candidateList[i] = generateCandidates(baseColorsArray[i]);
289
- progress?.update((i + 1) / baseColorsArray.length * 100);
290
- }
291
- progress?.finishSection("Done");
292
- return { colorKeys, baseColorsArray, candidateList };
293
- }
294
- function buildThemeForVision(colorKeys, baseColorsArray, candidateList, vision) {
295
- const colors = findOptimalColorCombination(
296
- colorKeys,
297
- baseColorsArray,
298
- candidateList,
299
- vision
300
- );
301
- const variables = Object.entries(colors).reduce(
302
- (acc, [varName, baseHex]) => {
303
- acc[varName] = inferRich(varName, baseHex);
304
- return acc;
305
- },
306
- {}
307
- );
308
- return { vision, variables };
262
+ return { colorKeys, baseColorsArray };
309
263
  }
310
264
  function getMiddleScaleKey(keys) {
311
265
  const scaleNumbers = keys.map((k) => {
@@ -319,15 +273,6 @@ function getMiddleScaleKey(keys) {
319
273
  const middleKey = keys.find((k) => k.endsWith(midNumber.toString())) || null;
320
274
  return middleKey;
321
275
  }
322
- function isValidColors(colors, blind) {
323
- for (let i = 0; i < colors.length; i++) {
324
- for (let j = i + 1; j < colors.length; j++) {
325
- if (import_chroma_js.default.deltaE(blind(colors[i]), blind(colors[j])) < THRESHOLD)
326
- return false;
327
- }
328
- }
329
- return true;
330
- }
331
276
 
332
277
  // src/cli/runThemeApply.ts
333
278
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -338,7 +283,7 @@ function removeExistingThemeBlocks(content) {
338
283
  let cleaned = content;
339
284
  for (const vision of visions) {
340
285
  const pattern = new RegExp(
341
- `\\/\\*\\s*${vision} theme start\\s*\\*\\/[^]*?\\/\\*\\s*${vision} theme end\\s*\\*\\/`,
286
+ `\\[data-theme=['"]${vision}['"]\\][^}]*}\\s*`,
342
287
  "gm"
343
288
  );
344
289
  cleaned = cleaned.replace(pattern, "");
@@ -418,7 +363,7 @@ async function runThemeApply(cssPath, progress) {
418
363
  progress?.finishSection("Done");
419
364
  const visions = ["deuteranopia", "protanopia", "tritanopia"];
420
365
  try {
421
- const { colorKeys, baseColorsArray, candidateList } = prepareCandidates(
366
+ const { colorKeys, baseColorsArray } = prepareCandidates(
422
367
  variables,
423
368
  progress
424
369
  );
@@ -429,7 +374,6 @@ async function runThemeApply(cssPath, progress) {
429
374
  const themeData = buildThemeForVision(
430
375
  colorKeys,
431
376
  baseColorsArray,
432
- candidateList,
433
377
  vision
434
378
  );
435
379
  progress?.update(70, "Applying CSS...");
package/dist/client.cjs CHANGED
@@ -118,9 +118,8 @@ var SvgLogo = (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
118
118
  "svg",
119
119
  {
120
120
  xmlns: "http://www.w3.org/2000/svg",
121
- width: 39,
122
- height: 40,
123
121
  fill: "none",
122
+ viewBox: "0 0 39 40",
124
123
  ...props,
125
124
  children: [
126
125
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#CE78A9", d: "m.5 17.805 1.516-3.514 16.812-7.924-1.309 3.308z" }),
@@ -184,9 +183,8 @@ var SvgUs = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
184
183
  "svg",
185
184
  {
186
185
  xmlns: "http://www.w3.org/2000/svg",
187
- width: 28,
188
- height: 21,
189
186
  fill: "none",
187
+ viewBox: "0 0 28 21",
190
188
  ...props,
191
189
  children: [
192
190
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
@@ -263,9 +261,8 @@ var SvgKr = (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
263
261
  "svg",
264
262
  {
265
263
  xmlns: "http://www.w3.org/2000/svg",
266
- width: 24,
267
- height: 17,
268
264
  fill: "none",
265
+ viewBox: "0 0 24 17",
269
266
  ...props,
270
267
  children: [
271
268
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
@@ -384,9 +381,8 @@ var SvgX = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
384
381
  "svg",
385
382
  {
386
383
  xmlns: "http://www.w3.org/2000/svg",
387
- width: 30,
388
- height: 30,
389
384
  fill: "none",
385
+ viewBox: "0 0 30 30",
390
386
  ...props,
391
387
  children: [
392
388
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -648,7 +644,7 @@ function ThemeSwitcher({ options }) {
648
644
  {
649
645
  role: "menu",
650
646
  "aria-label": "Select theme",
651
- className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px]",
647
+ className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px] filter drop-shadow-[0_0_1.3px_rgba(0,0,0,0.25)]",
652
648
  children: [
653
649
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: list.map((opt) => {
654
650
  const Icon = THEME_ICON[opt.key];
@@ -693,7 +689,7 @@ function ThemeSwitcher({ options }) {
693
689
  className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center`,
694
690
  onClick: () => updateLanguage("Korean"),
695
691
  children: [
696
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Us_default, {}),
692
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Us_default, { width: 20 }),
697
693
  "English"
698
694
  ]
699
695
  }
@@ -703,7 +699,7 @@ function ThemeSwitcher({ options }) {
703
699
  className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center `,
704
700
  onClick: () => updateLanguage("English"),
705
701
  children: [
706
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Kr_default, {}),
702
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Kr_default, { width: 20 }),
707
703
  "Korean"
708
704
  ]
709
705
  }
package/dist/client.js CHANGED
@@ -96,9 +96,8 @@ var SvgLogo = (props) => /* @__PURE__ */ jsxs(
96
96
  "svg",
97
97
  {
98
98
  xmlns: "http://www.w3.org/2000/svg",
99
- width: 39,
100
- height: 40,
101
99
  fill: "none",
100
+ viewBox: "0 0 39 40",
102
101
  ...props,
103
102
  children: [
104
103
  /* @__PURE__ */ jsx2("path", { fill: "#CE78A9", d: "m.5 17.805 1.516-3.514 16.812-7.924-1.309 3.308z" }),
@@ -162,9 +161,8 @@ var SvgUs = (props) => /* @__PURE__ */ jsxs2(
162
161
  "svg",
163
162
  {
164
163
  xmlns: "http://www.w3.org/2000/svg",
165
- width: 28,
166
- height: 21,
167
164
  fill: "none",
165
+ viewBox: "0 0 28 21",
168
166
  ...props,
169
167
  children: [
170
168
  /* @__PURE__ */ jsxs2("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
@@ -241,9 +239,8 @@ var SvgKr = (props) => /* @__PURE__ */ jsxs3(
241
239
  "svg",
242
240
  {
243
241
  xmlns: "http://www.w3.org/2000/svg",
244
- width: 24,
245
- height: 17,
246
242
  fill: "none",
243
+ viewBox: "0 0 24 17",
247
244
  ...props,
248
245
  children: [
249
246
  /* @__PURE__ */ jsxs3("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
@@ -362,9 +359,8 @@ var SvgX = (props) => /* @__PURE__ */ jsxs4(
362
359
  "svg",
363
360
  {
364
361
  xmlns: "http://www.w3.org/2000/svg",
365
- width: 30,
366
- height: 30,
367
362
  fill: "none",
363
+ viewBox: "0 0 30 30",
368
364
  ...props,
369
365
  children: [
370
366
  /* @__PURE__ */ jsx5(
@@ -626,7 +622,7 @@ function ThemeSwitcher({ options }) {
626
622
  {
627
623
  role: "menu",
628
624
  "aria-label": "Select theme",
629
- className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px]",
625
+ className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px] filter drop-shadow-[0_0_1.3px_rgba(0,0,0,0.25)]",
630
626
  children: [
631
627
  /* @__PURE__ */ jsx10("div", { children: list.map((opt) => {
632
628
  const Icon = THEME_ICON[opt.key];
@@ -671,7 +667,7 @@ function ThemeSwitcher({ options }) {
671
667
  className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center`,
672
668
  onClick: () => updateLanguage("Korean"),
673
669
  children: [
674
- /* @__PURE__ */ jsx10(Us_default, {}),
670
+ /* @__PURE__ */ jsx10(Us_default, { width: 20 }),
675
671
  "English"
676
672
  ]
677
673
  }
@@ -681,7 +677,7 @@ function ThemeSwitcher({ options }) {
681
677
  className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center `,
682
678
  onClick: () => updateLanguage("English"),
683
679
  children: [
684
- /* @__PURE__ */ jsx10(Kr_default, {}),
680
+ /* @__PURE__ */ jsx10(Kr_default, { width: 20 }),
685
681
  "Korean"
686
682
  ]
687
683
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colbrush",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "A React theme switching library that makes it easy to apply color-blind accessible UI themes",
5
5
  "homepage": "https://colbrush.vercel.app",
6
6
  "repository": {
@@ -72,8 +72,9 @@
72
72
  "typescript": "^5.8.3"
73
73
  },
74
74
  "scripts": {
75
- "svgr": "svgr src/assets --out-dir src/icons --typescript --expand-props end --svgo-config svgo.config.js",
75
+ "svgr": "svgr src/assets --out-dir src/icons --config-file svgr.config.cjs",
76
76
  "build:css": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
77
+ "build:icons": "svgr src/assets --out-dir src/icons",
77
78
  "build": "pnpm svgr && tsup --config tsup.config.ts && pnpm build:css",
78
79
  "dev": "tsup --config tsup.config.ts --watch",
79
80
  "lint": "eslint ."