Skip to content

Commit e022e68

Browse files
committed
feat: add theme icon tokens
1 parent afc4a91 commit e022e68

2 files changed

Lines changed: 283 additions & 2 deletions

File tree

packages/web/src/styles/tokens-touch.test.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@ function getRuleBlock(selector: string): string {
1010
let match: RegExpExecArray | null = null;
1111

1212
while ((match = matcher.exec(stylesheet)) !== null) {
13-
const currentSelector = match[1].replace(/\/\*[\s\S]*?\*\//g, "").trim();
13+
const selectors = match[1]
14+
.replace(/\/\*[\s\S]*?\*\//g, "")
15+
.split(",")
16+
.map((part) => part.trim());
1417

15-
if (currentSelector === selector) {
18+
if (selectors.length === 1 && selectors[0] === selector) {
19+
block = match[2];
20+
continue;
21+
}
22+
23+
if (!block && selectors.includes(selector)) {
1624
block = match[2];
1725
}
1826
}
@@ -26,6 +34,32 @@ function getCustomProperty(block: string, name: string): string | null {
2634
}
2735

2836
describe("tokens.css touch tokens", () => {
37+
const builtInThemes = [
38+
"mint-dark",
39+
"mint-light",
40+
"graphite-dark",
41+
"graphite-light",
42+
"nord-dark",
43+
"nord-light",
44+
"hc-dark",
45+
"hc-light",
46+
] as const;
47+
48+
const requiredIconTokens = [
49+
"--icon-primary",
50+
"--icon-secondary",
51+
"--icon-muted",
52+
"--icon-accent",
53+
"--icon-success",
54+
"--icon-warning",
55+
"--icon-error",
56+
"--icon-info",
57+
"--icon-file-folder",
58+
"--icon-git-deleted",
59+
"--icon-surface-subtle",
60+
"--icon-surface-error",
61+
] as const;
62+
2963
it("defines named theme blocks for all built-in themes", () => {
3064
expect(stylesheet).toContain(':root,\n[data-theme="mint-dark"]');
3165
expect(stylesheet).toContain('[data-theme="mint-light"]');
@@ -98,4 +132,27 @@ describe("tokens.css touch tokens", () => {
98132
expect(getCustomProperty(nordLight, "--accent-blue")).toBe("#5b7fa8");
99133
expect(getCustomProperty(nordLight, "--shadow-glow")).toBe("0 0 12px rgba(91, 127, 168, 0.18)");
100134
});
135+
136+
it("defines required icon tokens for every built-in theme", () => {
137+
for (const theme of builtInThemes) {
138+
const block = getRuleBlock(`[data-theme="${theme}"]`);
139+
140+
for (const token of requiredIconTokens) {
141+
expect(getCustomProperty(block, token), `${theme} should define ${token}`).not.toBeNull();
142+
}
143+
}
144+
});
145+
146+
it("keeps light-theme icon tokens visually distinct across families", () => {
147+
const mintLight = getRuleBlock('[data-theme="mint-light"]');
148+
const graphiteLight = getRuleBlock('[data-theme="graphite-light"]');
149+
const nordLight = getRuleBlock('[data-theme="nord-light"]');
150+
151+
expect(getCustomProperty(mintLight, "--icon-file-folder")).not.toBe(
152+
getCustomProperty(graphiteLight, "--icon-file-folder")
153+
);
154+
expect(getCustomProperty(graphiteLight, "--icon-accent")).not.toBe(
155+
getCustomProperty(nordLight, "--icon-accent")
156+
);
157+
});
101158
});

packages/web/src/styles/tokens.css

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,34 @@
169169
--color-error: #ff9eb0;
170170
--color-info: #6cb6ff;
171171

172+
/* ========== Icon Tokens ========== */
173+
--icon-primary: var(--text-primary);
174+
--icon-secondary: var(--text-secondary);
175+
--icon-muted: var(--text-tertiary);
176+
--icon-accent: var(--accent-green);
177+
--icon-success: var(--color-success);
178+
--icon-warning: var(--color-warning);
179+
--icon-error: var(--color-error);
180+
--icon-info: var(--color-info);
181+
182+
--icon-file-folder: #8adfc0;
183+
--icon-file-code: var(--accent-blue);
184+
--icon-file-data: var(--accent-purple);
185+
--icon-file-doc: #d7e2ec;
186+
--icon-file-media: #ffb7c5;
187+
--icon-file-default: var(--text-secondary);
188+
--icon-git-staged: var(--color-success);
189+
--icon-git-modified: var(--color-warning);
190+
--icon-git-deleted: var(--color-error);
191+
--icon-git-untracked: var(--color-info);
192+
193+
--icon-surface-subtle: color-mix(in srgb, var(--text-secondary) 18%, var(--bg-surface));
194+
--icon-surface-accent: color-mix(in srgb, var(--accent-green) 24%, var(--bg-surface));
195+
--icon-surface-success: color-mix(in srgb, var(--color-success) 24%, var(--bg-surface));
196+
--icon-surface-warning: color-mix(in srgb, var(--color-warning) 24%, var(--bg-surface));
197+
--icon-surface-error: color-mix(in srgb, var(--color-error) 24%, var(--bg-surface));
198+
--icon-surface-info: color-mix(in srgb, var(--color-info) 24%, var(--bg-surface));
199+
172200
/* ========== Shadows ========== */
173201
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
174202
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
@@ -217,6 +245,34 @@
217245
--color-error: #cf222e;
218246
--color-info: #148a7a;
219247

248+
/* ========== Icon Tokens ========== */
249+
--icon-primary: var(--text-primary);
250+
--icon-secondary: var(--text-secondary);
251+
--icon-muted: var(--text-tertiary);
252+
--icon-accent: var(--accent-blue);
253+
--icon-success: var(--color-success);
254+
--icon-warning: var(--color-warning);
255+
--icon-error: var(--color-error);
256+
--icon-info: var(--color-info);
257+
258+
--icon-file-folder: #1f9d7f;
259+
--icon-file-code: #1373a5;
260+
--icon-file-data: var(--accent-purple);
261+
--icon-file-doc: #567065;
262+
--icon-file-media: #c65f80;
263+
--icon-file-default: var(--text-secondary);
264+
--icon-git-staged: var(--color-success);
265+
--icon-git-modified: var(--color-warning);
266+
--icon-git-deleted: var(--color-error);
267+
--icon-git-untracked: var(--color-info);
268+
269+
--icon-surface-subtle: color-mix(in srgb, var(--text-secondary) 16%, var(--bg-surface));
270+
--icon-surface-accent: color-mix(in srgb, var(--accent-blue) 18%, var(--bg-surface));
271+
--icon-surface-success: color-mix(in srgb, var(--color-success) 18%, var(--bg-surface));
272+
--icon-surface-warning: color-mix(in srgb, var(--color-warning) 18%, var(--bg-surface));
273+
--icon-surface-error: color-mix(in srgb, var(--color-error) 18%, var(--bg-surface));
274+
--icon-surface-info: color-mix(in srgb, var(--color-info) 18%, var(--bg-surface));
275+
220276
/* ========== Shadows ========== */
221277
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.08);
222278
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
@@ -265,6 +321,34 @@
265321
--color-error: #ff7b72;
266322
--color-info: #7aa2f7;
267323

324+
/* ========== Icon Tokens ========== */
325+
--icon-primary: var(--text-primary);
326+
--icon-secondary: var(--text-secondary);
327+
--icon-muted: var(--text-tertiary);
328+
--icon-accent: #8fb1ff;
329+
--icon-success: var(--color-success);
330+
--icon-warning: var(--color-warning);
331+
--icon-error: var(--color-error);
332+
--icon-info: var(--color-info);
333+
334+
--icon-file-folder: #e1b86b;
335+
--icon-file-code: var(--accent-blue);
336+
--icon-file-data: var(--accent-purple);
337+
--icon-file-doc: #c5ccd6;
338+
--icon-file-media: #ff9f99;
339+
--icon-file-default: var(--text-secondary);
340+
--icon-git-staged: var(--color-success);
341+
--icon-git-modified: var(--color-warning);
342+
--icon-git-deleted: var(--color-error);
343+
--icon-git-untracked: var(--color-info);
344+
345+
--icon-surface-subtle: color-mix(in srgb, var(--text-secondary) 18%, var(--bg-surface));
346+
--icon-surface-accent: color-mix(in srgb, #8fb1ff 24%, var(--bg-surface));
347+
--icon-surface-success: color-mix(in srgb, var(--color-success) 24%, var(--bg-surface));
348+
--icon-surface-warning: color-mix(in srgb, var(--color-warning) 24%, var(--bg-surface));
349+
--icon-surface-error: color-mix(in srgb, var(--color-error) 24%, var(--bg-surface));
350+
--icon-surface-info: color-mix(in srgb, var(--color-info) 24%, var(--bg-surface));
351+
268352
/* ========== Shadows ========== */
269353
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.42);
270354
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.54);
@@ -313,6 +397,34 @@
313397
--color-error: #d14343;
314398
--color-info: #315fdd;
315399

400+
/* ========== Icon Tokens ========== */
401+
--icon-primary: var(--text-primary);
402+
--icon-secondary: var(--text-secondary);
403+
--icon-muted: var(--text-tertiary);
404+
--icon-accent: #2f4fb8;
405+
--icon-success: var(--color-success);
406+
--icon-warning: var(--color-warning);
407+
--icon-error: var(--color-error);
408+
--icon-info: var(--color-info);
409+
410+
--icon-file-folder: #8a6324;
411+
--icon-file-code: var(--accent-blue);
412+
--icon-file-data: var(--accent-purple);
413+
--icon-file-doc: #516171;
414+
--icon-file-media: #bf5a5a;
415+
--icon-file-default: var(--text-secondary);
416+
--icon-git-staged: var(--color-success);
417+
--icon-git-modified: var(--color-warning);
418+
--icon-git-deleted: var(--color-error);
419+
--icon-git-untracked: var(--color-info);
420+
421+
--icon-surface-subtle: color-mix(in srgb, var(--text-secondary) 16%, var(--bg-surface));
422+
--icon-surface-accent: color-mix(in srgb, #2f4fb8 18%, var(--bg-surface));
423+
--icon-surface-success: color-mix(in srgb, var(--color-success) 18%, var(--bg-surface));
424+
--icon-surface-warning: color-mix(in srgb, var(--color-warning) 18%, var(--bg-surface));
425+
--icon-surface-error: color-mix(in srgb, var(--color-error) 18%, var(--bg-surface));
426+
--icon-surface-info: color-mix(in srgb, var(--color-info) 18%, var(--bg-surface));
427+
316428
/* ========== Shadows ========== */
317429
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
318430
--shadow-md: 0 4px 12px rgba(15, 23, 42, 0.1);
@@ -361,6 +473,34 @@
361473
--color-error: #bf616a;
362474
--color-info: #88c0d0;
363475

476+
/* ========== Icon Tokens ========== */
477+
--icon-primary: var(--text-primary);
478+
--icon-secondary: var(--text-secondary);
479+
--icon-muted: var(--text-tertiary);
480+
--icon-accent: #8fbcbb;
481+
--icon-success: var(--color-success);
482+
--icon-warning: var(--color-warning);
483+
--icon-error: var(--color-error);
484+
--icon-info: var(--color-info);
485+
486+
--icon-file-folder: #d8b76e;
487+
--icon-file-code: var(--accent-blue);
488+
--icon-file-data: var(--accent-purple);
489+
--icon-file-doc: #c7d3e0;
490+
--icon-file-media: #cf8c95;
491+
--icon-file-default: var(--text-secondary);
492+
--icon-git-staged: var(--color-success);
493+
--icon-git-modified: var(--color-warning);
494+
--icon-git-deleted: var(--color-error);
495+
--icon-git-untracked: var(--color-info);
496+
497+
--icon-surface-subtle: color-mix(in srgb, var(--text-secondary) 20%, var(--bg-surface));
498+
--icon-surface-accent: color-mix(in srgb, #8fbcbb 24%, var(--bg-surface));
499+
--icon-surface-success: color-mix(in srgb, var(--color-success) 24%, var(--bg-surface));
500+
--icon-surface-warning: color-mix(in srgb, var(--color-warning) 24%, var(--bg-surface));
501+
--icon-surface-error: color-mix(in srgb, var(--color-error) 24%, var(--bg-surface));
502+
--icon-surface-info: color-mix(in srgb, var(--color-info) 24%, var(--bg-surface));
503+
364504
/* ========== Shadows ========== */
365505
--shadow-sm: 0 1px 2px rgba(17, 24, 39, 0.36);
366506
--shadow-md: 0 4px 12px rgba(17, 24, 39, 0.46);
@@ -409,6 +549,34 @@
409549
--color-error: #bf616a;
410550
--color-info: #5b7fa8;
411551

552+
/* ========== Icon Tokens ========== */
553+
--icon-primary: var(--text-primary);
554+
--icon-secondary: var(--text-secondary);
555+
--icon-muted: var(--text-tertiary);
556+
--icon-accent: #6b8fb9;
557+
--icon-success: var(--color-success);
558+
--icon-warning: var(--color-warning);
559+
--icon-error: var(--color-error);
560+
--icon-info: var(--color-info);
561+
562+
--icon-file-folder: #b28a49;
563+
--icon-file-code: var(--accent-blue);
564+
--icon-file-data: var(--accent-purple);
565+
--icon-file-doc: #5d6c82;
566+
--icon-file-media: #b96f77;
567+
--icon-file-default: var(--text-secondary);
568+
--icon-git-staged: var(--color-success);
569+
--icon-git-modified: var(--color-warning);
570+
--icon-git-deleted: var(--color-error);
571+
--icon-git-untracked: var(--color-info);
572+
573+
--icon-surface-subtle: color-mix(in srgb, var(--text-secondary) 16%, var(--bg-surface));
574+
--icon-surface-accent: color-mix(in srgb, #6b8fb9 18%, var(--bg-surface));
575+
--icon-surface-success: color-mix(in srgb, var(--color-success) 18%, var(--bg-surface));
576+
--icon-surface-warning: color-mix(in srgb, var(--color-warning) 18%, var(--bg-surface));
577+
--icon-surface-error: color-mix(in srgb, var(--color-error) 18%, var(--bg-surface));
578+
--icon-surface-info: color-mix(in srgb, var(--color-info) 18%, var(--bg-surface));
579+
412580
/* ========== Shadows ========== */
413581
--shadow-sm: 0 1px 2px rgba(46, 52, 64, 0.08);
414582
--shadow-md: 0 4px 12px rgba(46, 52, 64, 0.1);
@@ -457,6 +625,34 @@
457625
--color-error: #ff4d4d;
458626
--color-info: #66b3ff;
459627

628+
/* ========== Icon Tokens ========== */
629+
--icon-primary: #ffffff;
630+
--icon-secondary: #ffff00;
631+
--icon-muted: #d0d0d0;
632+
--icon-accent: #66b3ff;
633+
--icon-success: #00ff7f;
634+
--icon-warning: #ffff66;
635+
--icon-error: #ff4d4d;
636+
--icon-info: #66b3ff;
637+
638+
--icon-file-folder: #ffff00;
639+
--icon-file-code: #66b3ff;
640+
--icon-file-data: #ff7fff;
641+
--icon-file-doc: #ffffff;
642+
--icon-file-media: #ff4d4d;
643+
--icon-file-default: #ffffff;
644+
--icon-git-staged: #00ff7f;
645+
--icon-git-modified: #ffff66;
646+
--icon-git-deleted: #ff4d4d;
647+
--icon-git-untracked: #66b3ff;
648+
649+
--icon-surface-subtle: color-mix(in srgb, #ffffff 20%, var(--bg-surface));
650+
--icon-surface-accent: color-mix(in srgb, #66b3ff 28%, var(--bg-surface));
651+
--icon-surface-success: color-mix(in srgb, #00ff7f 28%, var(--bg-surface));
652+
--icon-surface-warning: color-mix(in srgb, #ffff66 28%, var(--bg-surface));
653+
--icon-surface-error: color-mix(in srgb, #ff4d4d 28%, var(--bg-surface));
654+
--icon-surface-info: color-mix(in srgb, #66b3ff 28%, var(--bg-surface));
655+
460656
/* ========== Shadows ========== */
461657
--shadow-sm: 0 0 0 1px rgba(255, 255, 255, 0.55);
462658
--shadow-md: 0 0 0 1px rgba(255, 255, 255, 0.72);
@@ -505,6 +701,34 @@
505701
--color-error: #b00020;
506702
--color-info: #0037da;
507703

704+
/* ========== Icon Tokens ========== */
705+
--icon-primary: #000000;
706+
--icon-secondary: #111111;
707+
--icon-muted: #333333;
708+
--icon-accent: #0037da;
709+
--icon-success: #006b3c;
710+
--icon-warning: #7a5c00;
711+
--icon-error: #b00020;
712+
--icon-info: #0037da;
713+
714+
--icon-file-folder: #7a5c00;
715+
--icon-file-code: #0037da;
716+
--icon-file-data: #7a00cc;
717+
--icon-file-doc: #000000;
718+
--icon-file-media: #b00020;
719+
--icon-file-default: #111111;
720+
--icon-git-staged: #006b3c;
721+
--icon-git-modified: #7a5c00;
722+
--icon-git-deleted: #b00020;
723+
--icon-git-untracked: #0037da;
724+
725+
--icon-surface-subtle: color-mix(in srgb, #000000 12%, var(--bg-surface));
726+
--icon-surface-accent: color-mix(in srgb, #0037da 18%, var(--bg-surface));
727+
--icon-surface-success: color-mix(in srgb, #006b3c 18%, var(--bg-surface));
728+
--icon-surface-warning: color-mix(in srgb, #7a5c00 18%, var(--bg-surface));
729+
--icon-surface-error: color-mix(in srgb, #b00020 18%, var(--bg-surface));
730+
--icon-surface-info: color-mix(in srgb, #0037da 18%, var(--bg-surface));
731+
508732
/* ========== Shadows ========== */
509733
--shadow-sm: 0 0 0 1px rgba(0, 0, 0, 0.18);
510734
--shadow-md: 0 0 0 1px rgba(0, 0, 0, 0.3);

0 commit comments

Comments
 (0)