Skip to content

Commit 1fdc227

Browse files
sgoudhamunseen-ninja
andauthoredOct 26, 2024··
feat: add ANSI colours (#98)
Co-authored-by: lemon <git@unseen.ninja>
1 parent 6632482 commit 1fdc227

File tree

7 files changed

+587
-18
lines changed

7 files changed

+587
-18
lines changed
 

‎README.md

+17-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { flavors, flavorEntries, version } from "@catppuccin/palette";
2525
import chalk from "chalk";
2626

2727
// a string containing the version of the library
28-
console.log(version)
28+
console.log(version);
2929

3030
// an object containing all catppuccin flavors
3131
console.log(flavors);
@@ -44,6 +44,16 @@ flavorEntries.map(([_, flavor]) => {
4444
);
4545
});
4646
console.log("\n");
47+
48+
// same for the ansi colors
49+
flavor.ansiColorEntries.map(([colorName, ansi]) => {
50+
console.log(
51+
chalk.hex(ansi.normal.hex)(`[${ansi.normal.code}] Normal ${colorName}`)
52+
);
53+
console.log(
54+
chalk.hex(ansi.bright.hex)(`[${ansi.bright.code}] Bright ${colorName}`)
55+
);
56+
});
4757
});
4858
```
4959

@@ -52,11 +62,15 @@ flavorEntries.map(([_, flavor]) => {
5262
The library is available through [JSR](https://jsr.io/@catppuccin/palette) and [`deno.land/x/catppuccin`](https://deno.land/x/catppuccin):
5363

5464
```ts
55-
import { flavors, flavorEntries, version } from "https://deno.land/x/catppuccin/mod.ts";
65+
import {
66+
flavors,
67+
flavorEntries,
68+
version,
69+
} from "https://deno.land/x/catppuccin/mod.ts";
5670
import { bgRgb24 } from "https://deno.land/std/fmt/colors.ts";
5771

5872
// a string containing the version of the library
59-
console.log(version)
73+
console.log(version);
6074

6175
// an object containing all catppuccin flavors
6276
console.log(flavors);

‎deno.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎import_map.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"dnt": "https://deno.land/x/dnt@0.39.0/mod.ts",
1111
"ase-utils": "npm:ase-utils@0.1.1",
1212
"procreate-swatches": "npm:procreate-swatches@0.1.1",
13-
"tinycolor2": "npm:tinycolor2@1.6.0"
13+
"tinycolor2": "npm:tinycolor2@1.6.0",
14+
"colorjs": "npm:colorjs.io@0.5.2"
1415
}
1516
}

‎mod.test.ts

+60-10
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,71 @@ import { flavorEntries, flavors, version } from "@catppuccin/palette";
44
import palette from "@/palette.json" with { type: "json" };
55

66
Deno.test("flavorEntries", () => {
7-
flavorEntries
8-
.map(([flavorName, flavor]) =>
9-
flavor.colorEntries
10-
.map(([colorName, color]) =>
11-
assertEquals(palette[flavorName].colors[colorName].hex, color.hex)
12-
)
7+
flavorEntries.map(([flavorName, flavor]) => {
8+
flavor.colorEntries.map(([colorName, color]) =>
9+
assertEquals(color.hex, palette[flavorName].colors[colorName].hex)
1310
);
11+
});
1412
});
1513

1614
Deno.test("flavors", () => {
1715
flavorEntries.map(([flavorName]) => {
18-
assertEquals(
19-
flavors[flavorName].name,
20-
palette[flavorName].name,
21-
);
16+
assertEquals(flavors[flavorName].name, palette[flavorName].name);
17+
});
18+
});
19+
20+
Deno.test("ansiEntries", () => {
21+
flavorEntries.map(([flavorName, flavor]) => {
22+
flavor.ansiColorEntries.map(([ansiColorName, ansiColor]) => {
23+
assertEquals(
24+
ansiColor.normal.hex,
25+
palette[flavorName].ansiColors[ansiColorName].normal.hex,
26+
);
27+
28+
if (ansiColorName == "black") {
29+
if (flavorName == "latte") {
30+
assertEquals(
31+
ansiColor.normal.hex,
32+
palette[flavorName].colors.subtext1.hex,
33+
);
34+
assertEquals(
35+
ansiColor.bright.hex,
36+
palette[flavorName].colors.subtext0.hex,
37+
);
38+
} else {
39+
assertEquals(
40+
ansiColor.normal.hex,
41+
palette[flavorName].colors.surface1.hex,
42+
);
43+
assertEquals(
44+
ansiColor.bright.hex,
45+
palette[flavorName].colors.surface2.hex,
46+
);
47+
}
48+
}
49+
50+
if (ansiColorName == "white") {
51+
if (flavorName == "latte") {
52+
assertEquals(
53+
ansiColor.normal.hex,
54+
palette[flavorName].colors.surface2.hex,
55+
);
56+
assertEquals(
57+
ansiColor.bright.hex,
58+
palette[flavorName].colors.surface1.hex,
59+
);
60+
} else {
61+
assertEquals(
62+
ansiColor.normal.hex,
63+
palette[flavorName].colors.subtext0.hex,
64+
);
65+
assertEquals(
66+
ansiColor.bright.hex,
67+
palette[flavorName].colors.subtext1.hex,
68+
);
69+
}
70+
}
71+
});
2272
});
2373
});
2474

‎mod.ts

+59
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ export type MonochromaticName =
4949
| "mantle"
5050
| "crust";
5151

52+
type AnsiName =
53+
| "black"
54+
| "red"
55+
| "green"
56+
| "yellow"
57+
| "blue"
58+
| "magenta"
59+
| "cyan"
60+
| "white";
61+
5262
/**
5363
* All color names of Catppuccin
5464
*/
@@ -59,6 +69,11 @@ export type ColorName = AccentName | MonochromaticName;
5969
*/
6070
export type Colors<T> = Record<ColorName, T>;
6171

72+
/**
73+
* Generic to map type T to all ANSI color names
74+
*/
75+
export type AnsiColors<T> = Record<AnsiName, T>;
76+
6277
/**
6378
* A flavor of Catppuccin
6479
*/
@@ -88,17 +103,32 @@ export type CatppuccinFlavor = Readonly<{
88103
*/
89104
colors: CatppuccinColors;
90105

106+
/**
107+
* An object containing all the ANSI color mappings of the flavor
108+
*/
109+
ansiColors: CatppuccinAnsiColors;
110+
91111
/**
92112
* A typed Object.entries iterable of the colors of the flavor
93113
*/
94114
colorEntries: Entries<CatppuccinColors>;
115+
116+
/**
117+
* A typed Object.entries iterable of the ANSI colors of the flavor
118+
*/
119+
ansiColorEntries: Entries<CatppuccinAnsiColors>;
95120
}>;
96121

97122
/**
98123
* All colors of Catppuccin
99124
*/
100125
export type CatppuccinColors = Readonly<Colors<ColorFormat>>;
101126

127+
/**
128+
* All ANSI color mappings of Catppuccin
129+
*/
130+
export type CatppuccinAnsiColors = Readonly<AnsiColors<AnsiColorGroups>>;
131+
102132
/**
103133
* All flavors of Catppuccin
104134
*/
@@ -187,6 +217,34 @@ export type ColorFormat = Readonly<{
187217
accent: boolean;
188218
}>;
189219

220+
export type AnsiColorGroups = Readonly<{
221+
/**
222+
* An object containing all the ANSI "normal" colors, which are the 8 standard colors from 0 to 7.
223+
*/
224+
normal: AnsiColorFormat;
225+
226+
/**
227+
* An object containing all the ANSI "bright" colors, which are the 8 standard colors from 8 to 15.
228+
*
229+
* Note: These bright colors are not necessarily "brighter" than the normal colors, but rather more bold and saturated.
230+
*/
231+
bright: AnsiColorFormat;
232+
}>;
233+
234+
export type AnsiColorFormat = Readonly<{
235+
/**
236+
* String-formatted hex value
237+
* @example "#babbf1"
238+
*/
239+
hex: string;
240+
241+
/**
242+
* The ANSI color code
243+
* @example 4
244+
*/
245+
code: number;
246+
}>;
247+
190248
const { version: _, ...jsonFlavors } = definitions;
191249

192250
/**
@@ -203,6 +261,7 @@ export const flavors: CatppuccinFlavors = entriesFromObject(
203261
acc[flavorName] = {
204262
...flavor,
205263
colorEntries: entriesFromObject(flavor.colors),
264+
ansiColorEntries: entriesFromObject(flavor.ansiColors),
206265
};
207266
return acc;
208267
}, {} as CatppuccinFlavors);

‎palette.json

+329-1
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,88 @@
422422
},
423423
"accent": false
424424
}
425+
},
426+
"ansiColors": {
427+
"black": {
428+
"normal": {
429+
"hex": "#5c5f77",
430+
"code": 0
431+
},
432+
"bright": {
433+
"hex": "#6c6f85",
434+
"code": 8
435+
}
436+
},
437+
"red": {
438+
"normal": {
439+
"hex": "#d20f39",
440+
"code": 1
441+
},
442+
"bright": {
443+
"hex": "#de293e",
444+
"code": 9
445+
}
446+
},
447+
"green": {
448+
"normal": {
449+
"hex": "#40a02b",
450+
"code": 2
451+
},
452+
"bright": {
453+
"hex": "#49af3d",
454+
"code": 10
455+
}
456+
},
457+
"yellow": {
458+
"normal": {
459+
"hex": "#df8e1d",
460+
"code": 3
461+
},
462+
"bright": {
463+
"hex": "#eea02d",
464+
"code": 11
465+
}
466+
},
467+
"blue": {
468+
"normal": {
469+
"hex": "#1e66f5",
470+
"code": 4
471+
},
472+
"bright": {
473+
"hex": "#456eff",
474+
"code": 12
475+
}
476+
},
477+
"magenta": {
478+
"normal": {
479+
"hex": "#ea76cb",
480+
"code": 5
481+
},
482+
"bright": {
483+
"hex": "#fe85d8",
484+
"code": 13
485+
}
486+
},
487+
"cyan": {
488+
"normal": {
489+
"hex": "#179299",
490+
"code": 6
491+
},
492+
"bright": {
493+
"hex": "#2d9fa8",
494+
"code": 14
495+
}
496+
},
497+
"white": {
498+
"normal": {
499+
"hex": "#acb0be",
500+
"code": 7
501+
},
502+
"bright": {
503+
"hex": "#bcc0cc",
504+
"code": 15
505+
}
506+
}
425507
}
426508
},
427509
"frappe": {
@@ -846,6 +928,88 @@
846928
},
847929
"accent": false
848930
}
931+
},
932+
"ansiColors": {
933+
"black": {
934+
"normal": {
935+
"hex": "#51576d",
936+
"code": 0
937+
},
938+
"bright": {
939+
"hex": "#626880",
940+
"code": 8
941+
}
942+
},
943+
"red": {
944+
"normal": {
945+
"hex": "#e78284",
946+
"code": 1
947+
},
948+
"bright": {
949+
"hex": "#e67172",
950+
"code": 9
951+
}
952+
},
953+
"green": {
954+
"normal": {
955+
"hex": "#a6d189",
956+
"code": 2
957+
},
958+
"bright": {
959+
"hex": "#8ec772",
960+
"code": 10
961+
}
962+
},
963+
"yellow": {
964+
"normal": {
965+
"hex": "#e5c890",
966+
"code": 3
967+
},
968+
"bright": {
969+
"hex": "#d9ba73",
970+
"code": 11
971+
}
972+
},
973+
"blue": {
974+
"normal": {
975+
"hex": "#8caaee",
976+
"code": 4
977+
},
978+
"bright": {
979+
"hex": "#7b9ef0",
980+
"code": 12
981+
}
982+
},
983+
"magenta": {
984+
"normal": {
985+
"hex": "#f4b8e4",
986+
"code": 5
987+
},
988+
"bright": {
989+
"hex": "#f2a4db",
990+
"code": 13
991+
}
992+
},
993+
"cyan": {
994+
"normal": {
995+
"hex": "#81c8be",
996+
"code": 6
997+
},
998+
"bright": {
999+
"hex": "#5abfb5",
1000+
"code": 14
1001+
}
1002+
},
1003+
"white": {
1004+
"normal": {
1005+
"hex": "#a5adce",
1006+
"code": 7
1007+
},
1008+
"bright": {
1009+
"hex": "#b5bfe2",
1010+
"code": 15
1011+
}
1012+
}
8491013
}
8501014
},
8511015
"macchiato": {
@@ -1270,6 +1434,88 @@
12701434
},
12711435
"accent": false
12721436
}
1437+
},
1438+
"ansiColors": {
1439+
"black": {
1440+
"normal": {
1441+
"hex": "#494d64",
1442+
"code": 0
1443+
},
1444+
"bright": {
1445+
"hex": "#5b6078",
1446+
"code": 8
1447+
}
1448+
},
1449+
"red": {
1450+
"normal": {
1451+
"hex": "#ed8796",
1452+
"code": 1
1453+
},
1454+
"bright": {
1455+
"hex": "#ec7486",
1456+
"code": 9
1457+
}
1458+
},
1459+
"green": {
1460+
"normal": {
1461+
"hex": "#a6da95",
1462+
"code": 2
1463+
},
1464+
"bright": {
1465+
"hex": "#8ccf7f",
1466+
"code": 10
1467+
}
1468+
},
1469+
"yellow": {
1470+
"normal": {
1471+
"hex": "#eed49f",
1472+
"code": 3
1473+
},
1474+
"bright": {
1475+
"hex": "#e1c682",
1476+
"code": 11
1477+
}
1478+
},
1479+
"blue": {
1480+
"normal": {
1481+
"hex": "#8aadf4",
1482+
"code": 4
1483+
},
1484+
"bright": {
1485+
"hex": "#78a1f6",
1486+
"code": 12
1487+
}
1488+
},
1489+
"magenta": {
1490+
"normal": {
1491+
"hex": "#f5bde6",
1492+
"code": 5
1493+
},
1494+
"bright": {
1495+
"hex": "#f2a9dd",
1496+
"code": 13
1497+
}
1498+
},
1499+
"cyan": {
1500+
"normal": {
1501+
"hex": "#8bd5ca",
1502+
"code": 6
1503+
},
1504+
"bright": {
1505+
"hex": "#63cbc0",
1506+
"code": 14
1507+
}
1508+
},
1509+
"white": {
1510+
"normal": {
1511+
"hex": "#a5adcb",
1512+
"code": 7
1513+
},
1514+
"bright": {
1515+
"hex": "#b8c0e0",
1516+
"code": 15
1517+
}
1518+
}
12731519
}
12741520
},
12751521
"mocha": {
@@ -1694,6 +1940,88 @@
16941940
},
16951941
"accent": false
16961942
}
1943+
},
1944+
"ansiColors": {
1945+
"black": {
1946+
"normal": {
1947+
"hex": "#45475a",
1948+
"code": 0
1949+
},
1950+
"bright": {
1951+
"hex": "#585b70",
1952+
"code": 8
1953+
}
1954+
},
1955+
"red": {
1956+
"normal": {
1957+
"hex": "#f38ba8",
1958+
"code": 1
1959+
},
1960+
"bright": {
1961+
"hex": "#f37799",
1962+
"code": 9
1963+
}
1964+
},
1965+
"green": {
1966+
"normal": {
1967+
"hex": "#a6e3a1",
1968+
"code": 2
1969+
},
1970+
"bright": {
1971+
"hex": "#89d88b",
1972+
"code": 10
1973+
}
1974+
},
1975+
"yellow": {
1976+
"normal": {
1977+
"hex": "#f9e2af",
1978+
"code": 3
1979+
},
1980+
"bright": {
1981+
"hex": "#ebd391",
1982+
"code": 11
1983+
}
1984+
},
1985+
"blue": {
1986+
"normal": {
1987+
"hex": "#89b4fa",
1988+
"code": 4
1989+
},
1990+
"bright": {
1991+
"hex": "#74a8fc",
1992+
"code": 12
1993+
}
1994+
},
1995+
"magenta": {
1996+
"normal": {
1997+
"hex": "#f5c2e7",
1998+
"code": 5
1999+
},
2000+
"bright": {
2001+
"hex": "#f2aede",
2002+
"code": 13
2003+
}
2004+
},
2005+
"cyan": {
2006+
"normal": {
2007+
"hex": "#94e2d5",
2008+
"code": 6
2009+
},
2010+
"bright": {
2011+
"hex": "#6bd7ca",
2012+
"code": 14
2013+
}
2014+
},
2015+
"white": {
2016+
"normal": {
2017+
"hex": "#a6adc8",
2018+
"code": 7
2019+
},
2020+
"bright": {
2021+
"hex": "#bac2de",
2022+
"code": 15
2023+
}
2024+
}
16972025
}
16982026
}
1699-
}
2027+
}

‎scripts/gen_palette.ts

+113-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { join } from "std/path/join.ts";
22
import tinycolor from "tinycolor2";
3+
import Color from "colorjs";
34

45
import meta from "../deno.json" with { type: "json" };
56

67
import type {
8+
CatppuccinAnsiColors,
79
CatppuccinColors,
810
CatppuccinFlavor,
11+
ColorName,
912
Flavors,
1013
} from "@catppuccin/palette";
1114

@@ -199,8 +202,83 @@ const accents = [
199202
"lavender",
200203
];
201204

202-
const formatted = entriesFromObject(definitions)
203-
.reduce((acc, [flavorName, flavor], currentIndex) => {
205+
const ansiMappings = {
206+
black: {
207+
normal: {
208+
mapping: "", // superfluous, exists to make TypeScript happy
209+
code: 0,
210+
},
211+
bright: {
212+
code: 8,
213+
},
214+
},
215+
red: {
216+
normal: {
217+
mapping: "red",
218+
code: 1,
219+
},
220+
bright: {
221+
code: 9,
222+
},
223+
},
224+
green: {
225+
normal: {
226+
mapping: "green",
227+
code: 2,
228+
},
229+
bright: {
230+
code: 10,
231+
},
232+
},
233+
yellow: {
234+
normal: {
235+
mapping: "yellow",
236+
code: 3,
237+
},
238+
bright: {
239+
code: 11,
240+
},
241+
},
242+
blue: {
243+
normal: {
244+
mapping: "blue",
245+
code: 4,
246+
},
247+
bright: {
248+
code: 12,
249+
},
250+
},
251+
magenta: {
252+
normal: {
253+
mapping: "pink",
254+
code: 5,
255+
},
256+
bright: {
257+
code: 13,
258+
},
259+
},
260+
cyan: {
261+
normal: {
262+
mapping: "teal",
263+
code: 6,
264+
},
265+
bright: {
266+
code: 14,
267+
},
268+
},
269+
white: {
270+
normal: {
271+
mapping: "", // superfluous, exists to make TypeScript happy
272+
code: 7,
273+
},
274+
bright: {
275+
code: 15,
276+
},
277+
},
278+
};
279+
280+
const formatted = entriesFromObject(definitions).reduce(
281+
(acc, [flavorName, flavor], currentIndex) => {
204282
acc[flavorName] = {
205283
name: flavor.name,
206284
emoji: flavor.emoji,
@@ -222,9 +300,41 @@ const formatted = entriesFromObject(definitions)
222300
},
223301
{} as Writeable<CatppuccinColors>,
224302
),
303+
ansiColors: entriesFromObject(ansiMappings).reduce((acc, [name, props]) => {
304+
const mapping = props.normal.mapping as ColorName;
305+
let normalColorHex = flavor.colors[mapping];
306+
let brightColorHex: string;
307+
308+
if (name == "black") {
309+
normalColorHex = flavor.dark ? flavor.colors["surface1"] : flavor.colors["subtext1"];
310+
brightColorHex = flavor.dark ? flavor.colors["surface2"] : flavor.colors["subtext0"];
311+
} else if (name == "white") {
312+
normalColorHex = flavor.dark ? flavor.colors["subtext0"] : flavor.colors["surface2"] ;
313+
brightColorHex = flavor.dark ? flavor.colors["subtext1"] : flavor.colors["surface1"] ;
314+
} else {
315+
const brightColor = new Color(normalColorHex);
316+
brightColor.lch.l *= flavor.dark ? 0.94 : 1.09;
317+
brightColor.lch.c += flavor.dark ? 8 : 0;
318+
brightColor.lch.h += 2;
319+
brightColorHex = brightColor.toString({ format: "hex" });
320+
}
321+
acc[name] = {
322+
normal: {
323+
hex: normalColorHex,
324+
code: props.normal.code,
325+
},
326+
bright: {
327+
hex: brightColorHex,
328+
code: props.bright.code,
329+
},
330+
};
331+
return acc;
332+
}, {} as Writeable<CatppuccinAnsiColors>),
225333
};
226334
return acc;
227-
}, {} as Flavors<Omit<CatppuccinFlavor, "colorEntries">>);
335+
},
336+
{} as Flavors<Omit<CatppuccinFlavor, "colorEntries" | "ansiColorEntries">>,
337+
);
228338

229339
const __dirname = new URL(".", import.meta.url).pathname;
230340

0 commit comments

Comments
 (0)
Please sign in to comment.