From 1fdc227a6025fc968990e2439e886b736b807634 Mon Sep 17 00:00:00 2001 From: Hammy <58985301+sgoudham@users.noreply.github.com> Date: Sat, 26 Oct 2024 23:27:45 +0100 Subject: [PATCH] feat: add ANSI colours (#98) Co-authored-by: lemon --- README.md | 20 ++- deno.lock | 7 + import_map.json | 3 +- mod.test.ts | 70 +++++++-- mod.ts | 59 ++++++++ palette.json | 330 ++++++++++++++++++++++++++++++++++++++++- scripts/gen_palette.ts | 116 ++++++++++++++- 7 files changed, 587 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 087870e..3f13db9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ import { flavors, flavorEntries, version } from "@catppuccin/palette"; import chalk from "chalk"; // a string containing the version of the library -console.log(version) +console.log(version); // an object containing all catppuccin flavors console.log(flavors); @@ -44,6 +44,16 @@ flavorEntries.map(([_, flavor]) => { ); }); console.log("\n"); + + // same for the ansi colors + flavor.ansiColorEntries.map(([colorName, ansi]) => { + console.log( + chalk.hex(ansi.normal.hex)(`[${ansi.normal.code}] Normal ${colorName}`) + ); + console.log( + chalk.hex(ansi.bright.hex)(`[${ansi.bright.code}] Bright ${colorName}`) + ); + }); }); ``` @@ -52,11 +62,15 @@ flavorEntries.map(([_, flavor]) => { The library is available through [JSR](https://jsr.io/@catppuccin/palette) and [`deno.land/x/catppuccin`](https://deno.land/x/catppuccin): ```ts -import { flavors, flavorEntries, version } from "https://deno.land/x/catppuccin/mod.ts"; +import { + flavors, + flavorEntries, + version, +} from "https://deno.land/x/catppuccin/mod.ts"; import { bgRgb24 } from "https://deno.land/std/fmt/colors.ts"; // a string containing the version of the library -console.log(version) +console.log(version); // an object containing all catppuccin flavors console.log(flavors); diff --git a/deno.lock b/deno.lock index 8dae943..1f2ea3a 100644 --- a/deno.lock +++ b/deno.lock @@ -5,6 +5,7 @@ "npm:@types/node": "npm:@types/node@18.16.19", "npm:ase-utils@0.1.1": "npm:ase-utils@0.1.1", "npm:chalk@5": "npm:chalk@5.3.0", + "npm:colorjs.io@0.5.2": "npm:colorjs.io@0.5.2", "npm:procreate-swatches@0.1.1": "npm:procreate-swatches@0.1.1", "npm:tinycolor2@1.6.0": "npm:tinycolor2@1.6.0" }, @@ -44,6 +45,10 @@ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dependencies": {} }, + "colorjs.io@0.5.2": { + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dependencies": {} + }, "core-util-is@1.0.3": { "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dependencies": {} @@ -339,6 +344,8 @@ "https://esm.sh/v135/ase-utils@0.1.1/X-ZS8q/denonext/ase-utils.mjs": "9d0e2be7b2d03916d65550a835698138af18d05f329fe64660cce9a20558dbf5", "https://esm.sh/v135/bytebuffer@3.5.4": "25eccd4416d3aba412d5f763b17c2d38e64907a40248d35c0e33b584939db02e", "https://esm.sh/v135/bytebuffer@3.5.4/denonext/bytebuffer.mjs": "82e7ac0bcc2d2706322705b77cbe85e7f7fc3f0dfdb187fb82c6db931f1b6e47", + "https://esm.sh/v135/colorjs.io@0.5.2": "2a1b7be172392fda207281c9d51a54e910e8eaec9fb8bf24bc0a5704b9af8cc7", + "https://esm.sh/v135/colorjs.io@0.5.2/denonext/colorjs.io.mjs": "20be67148544b6e6b6d09836684862cb38041a62272f065f66d7d81ed1ea4f3e", "https://esm.sh/v135/jszip@3.10.1/denonext/jszip.mjs": "e9883ef00252932b610e53a2ae9ef19fd7bc498cd0fb34068f72497e5c5fc4f7", "https://esm.sh/v135/long@2.4.0/denonext/long.mjs": "5859b34edc28854d4d04ed38defd66feb33341a0ac9bff6f4d3fa1abb3b7e529", "https://esm.sh/v135/procreate-swatches@0.1.1/X-ZS8q/denonext/procreate-swatches.mjs": "ef1eaea19420a0ef8f87d38b6600376ccb55f778104bcbeae00080ebe07258d9", diff --git a/import_map.json b/import_map.json index c80167b..e1c9be0 100644 --- a/import_map.json +++ b/import_map.json @@ -10,6 +10,7 @@ "dnt": "https://deno.land/x/dnt@0.39.0/mod.ts", "ase-utils": "npm:ase-utils@0.1.1", "procreate-swatches": "npm:procreate-swatches@0.1.1", - "tinycolor2": "npm:tinycolor2@1.6.0" + "tinycolor2": "npm:tinycolor2@1.6.0", + "colorjs": "npm:colorjs.io@0.5.2" } } diff --git a/mod.test.ts b/mod.test.ts index d29dffe..3b08cd3 100644 --- a/mod.test.ts +++ b/mod.test.ts @@ -4,21 +4,71 @@ import { flavorEntries, flavors, version } from "@catppuccin/palette"; import palette from "@/palette.json" with { type: "json" }; Deno.test("flavorEntries", () => { - flavorEntries - .map(([flavorName, flavor]) => - flavor.colorEntries - .map(([colorName, color]) => - assertEquals(palette[flavorName].colors[colorName].hex, color.hex) - ) + flavorEntries.map(([flavorName, flavor]) => { + flavor.colorEntries.map(([colorName, color]) => + assertEquals(color.hex, palette[flavorName].colors[colorName].hex) ); + }); }); Deno.test("flavors", () => { flavorEntries.map(([flavorName]) => { - assertEquals( - flavors[flavorName].name, - palette[flavorName].name, - ); + assertEquals(flavors[flavorName].name, palette[flavorName].name); + }); +}); + +Deno.test("ansiEntries", () => { + flavorEntries.map(([flavorName, flavor]) => { + flavor.ansiColorEntries.map(([ansiColorName, ansiColor]) => { + assertEquals( + ansiColor.normal.hex, + palette[flavorName].ansiColors[ansiColorName].normal.hex, + ); + + if (ansiColorName == "black") { + if (flavorName == "latte") { + assertEquals( + ansiColor.normal.hex, + palette[flavorName].colors.subtext1.hex, + ); + assertEquals( + ansiColor.bright.hex, + palette[flavorName].colors.subtext0.hex, + ); + } else { + assertEquals( + ansiColor.normal.hex, + palette[flavorName].colors.surface1.hex, + ); + assertEquals( + ansiColor.bright.hex, + palette[flavorName].colors.surface2.hex, + ); + } + } + + if (ansiColorName == "white") { + if (flavorName == "latte") { + assertEquals( + ansiColor.normal.hex, + palette[flavorName].colors.surface2.hex, + ); + assertEquals( + ansiColor.bright.hex, + palette[flavorName].colors.surface1.hex, + ); + } else { + assertEquals( + ansiColor.normal.hex, + palette[flavorName].colors.subtext0.hex, + ); + assertEquals( + ansiColor.bright.hex, + palette[flavorName].colors.subtext1.hex, + ); + } + } + }); }); }); diff --git a/mod.ts b/mod.ts index e307036..589c2a5 100644 --- a/mod.ts +++ b/mod.ts @@ -49,6 +49,16 @@ export type MonochromaticName = | "mantle" | "crust"; +type AnsiName = + | "black" + | "red" + | "green" + | "yellow" + | "blue" + | "magenta" + | "cyan" + | "white"; + /** * All color names of Catppuccin */ @@ -59,6 +69,11 @@ export type ColorName = AccentName | MonochromaticName; */ export type Colors = Record; +/** + * Generic to map type T to all ANSI color names + */ +export type AnsiColors = Record; + /** * A flavor of Catppuccin */ @@ -88,10 +103,20 @@ export type CatppuccinFlavor = Readonly<{ */ colors: CatppuccinColors; + /** + * An object containing all the ANSI color mappings of the flavor + */ + ansiColors: CatppuccinAnsiColors; + /** * A typed Object.entries iterable of the colors of the flavor */ colorEntries: Entries; + + /** + * A typed Object.entries iterable of the ANSI colors of the flavor + */ + ansiColorEntries: Entries; }>; /** @@ -99,6 +124,11 @@ export type CatppuccinFlavor = Readonly<{ */ export type CatppuccinColors = Readonly>; +/** + * All ANSI color mappings of Catppuccin + */ +export type CatppuccinAnsiColors = Readonly>; + /** * All flavors of Catppuccin */ @@ -187,6 +217,34 @@ export type ColorFormat = Readonly<{ accent: boolean; }>; +export type AnsiColorGroups = Readonly<{ + /** + * An object containing all the ANSI "normal" colors, which are the 8 standard colors from 0 to 7. + */ + normal: AnsiColorFormat; + + /** + * An object containing all the ANSI "bright" colors, which are the 8 standard colors from 8 to 15. + * + * Note: These bright colors are not necessarily "brighter" than the normal colors, but rather more bold and saturated. + */ + bright: AnsiColorFormat; +}>; + +export type AnsiColorFormat = Readonly<{ + /** + * String-formatted hex value + * @example "#babbf1" + */ + hex: string; + + /** + * The ANSI color code + * @example 4 + */ + code: number; +}>; + const { version: _, ...jsonFlavors } = definitions; /** @@ -203,6 +261,7 @@ export const flavors: CatppuccinFlavors = entriesFromObject( acc[flavorName] = { ...flavor, colorEntries: entriesFromObject(flavor.colors), + ansiColorEntries: entriesFromObject(flavor.ansiColors), }; return acc; }, {} as CatppuccinFlavors); diff --git a/palette.json b/palette.json index 46d86ff..ad2b259 100644 --- a/palette.json +++ b/palette.json @@ -422,6 +422,88 @@ }, "accent": false } + }, + "ansiColors": { + "black": { + "normal": { + "hex": "#5c5f77", + "code": 0 + }, + "bright": { + "hex": "#6c6f85", + "code": 8 + } + }, + "red": { + "normal": { + "hex": "#d20f39", + "code": 1 + }, + "bright": { + "hex": "#de293e", + "code": 9 + } + }, + "green": { + "normal": { + "hex": "#40a02b", + "code": 2 + }, + "bright": { + "hex": "#49af3d", + "code": 10 + } + }, + "yellow": { + "normal": { + "hex": "#df8e1d", + "code": 3 + }, + "bright": { + "hex": "#eea02d", + "code": 11 + } + }, + "blue": { + "normal": { + "hex": "#1e66f5", + "code": 4 + }, + "bright": { + "hex": "#456eff", + "code": 12 + } + }, + "magenta": { + "normal": { + "hex": "#ea76cb", + "code": 5 + }, + "bright": { + "hex": "#fe85d8", + "code": 13 + } + }, + "cyan": { + "normal": { + "hex": "#179299", + "code": 6 + }, + "bright": { + "hex": "#2d9fa8", + "code": 14 + } + }, + "white": { + "normal": { + "hex": "#acb0be", + "code": 7 + }, + "bright": { + "hex": "#bcc0cc", + "code": 15 + } + } } }, "frappe": { @@ -846,6 +928,88 @@ }, "accent": false } + }, + "ansiColors": { + "black": { + "normal": { + "hex": "#51576d", + "code": 0 + }, + "bright": { + "hex": "#626880", + "code": 8 + } + }, + "red": { + "normal": { + "hex": "#e78284", + "code": 1 + }, + "bright": { + "hex": "#e67172", + "code": 9 + } + }, + "green": { + "normal": { + "hex": "#a6d189", + "code": 2 + }, + "bright": { + "hex": "#8ec772", + "code": 10 + } + }, + "yellow": { + "normal": { + "hex": "#e5c890", + "code": 3 + }, + "bright": { + "hex": "#d9ba73", + "code": 11 + } + }, + "blue": { + "normal": { + "hex": "#8caaee", + "code": 4 + }, + "bright": { + "hex": "#7b9ef0", + "code": 12 + } + }, + "magenta": { + "normal": { + "hex": "#f4b8e4", + "code": 5 + }, + "bright": { + "hex": "#f2a4db", + "code": 13 + } + }, + "cyan": { + "normal": { + "hex": "#81c8be", + "code": 6 + }, + "bright": { + "hex": "#5abfb5", + "code": 14 + } + }, + "white": { + "normal": { + "hex": "#a5adce", + "code": 7 + }, + "bright": { + "hex": "#b5bfe2", + "code": 15 + } + } } }, "macchiato": { @@ -1270,6 +1434,88 @@ }, "accent": false } + }, + "ansiColors": { + "black": { + "normal": { + "hex": "#494d64", + "code": 0 + }, + "bright": { + "hex": "#5b6078", + "code": 8 + } + }, + "red": { + "normal": { + "hex": "#ed8796", + "code": 1 + }, + "bright": { + "hex": "#ec7486", + "code": 9 + } + }, + "green": { + "normal": { + "hex": "#a6da95", + "code": 2 + }, + "bright": { + "hex": "#8ccf7f", + "code": 10 + } + }, + "yellow": { + "normal": { + "hex": "#eed49f", + "code": 3 + }, + "bright": { + "hex": "#e1c682", + "code": 11 + } + }, + "blue": { + "normal": { + "hex": "#8aadf4", + "code": 4 + }, + "bright": { + "hex": "#78a1f6", + "code": 12 + } + }, + "magenta": { + "normal": { + "hex": "#f5bde6", + "code": 5 + }, + "bright": { + "hex": "#f2a9dd", + "code": 13 + } + }, + "cyan": { + "normal": { + "hex": "#8bd5ca", + "code": 6 + }, + "bright": { + "hex": "#63cbc0", + "code": 14 + } + }, + "white": { + "normal": { + "hex": "#a5adcb", + "code": 7 + }, + "bright": { + "hex": "#b8c0e0", + "code": 15 + } + } } }, "mocha": { @@ -1694,6 +1940,88 @@ }, "accent": false } + }, + "ansiColors": { + "black": { + "normal": { + "hex": "#45475a", + "code": 0 + }, + "bright": { + "hex": "#585b70", + "code": 8 + } + }, + "red": { + "normal": { + "hex": "#f38ba8", + "code": 1 + }, + "bright": { + "hex": "#f37799", + "code": 9 + } + }, + "green": { + "normal": { + "hex": "#a6e3a1", + "code": 2 + }, + "bright": { + "hex": "#89d88b", + "code": 10 + } + }, + "yellow": { + "normal": { + "hex": "#f9e2af", + "code": 3 + }, + "bright": { + "hex": "#ebd391", + "code": 11 + } + }, + "blue": { + "normal": { + "hex": "#89b4fa", + "code": 4 + }, + "bright": { + "hex": "#74a8fc", + "code": 12 + } + }, + "magenta": { + "normal": { + "hex": "#f5c2e7", + "code": 5 + }, + "bright": { + "hex": "#f2aede", + "code": 13 + } + }, + "cyan": { + "normal": { + "hex": "#94e2d5", + "code": 6 + }, + "bright": { + "hex": "#6bd7ca", + "code": 14 + } + }, + "white": { + "normal": { + "hex": "#a6adc8", + "code": 7 + }, + "bright": { + "hex": "#bac2de", + "code": 15 + } + } } } -} +} \ No newline at end of file diff --git a/scripts/gen_palette.ts b/scripts/gen_palette.ts index 8cf9535..a26d27f 100644 --- a/scripts/gen_palette.ts +++ b/scripts/gen_palette.ts @@ -1,11 +1,14 @@ import { join } from "std/path/join.ts"; import tinycolor from "tinycolor2"; +import Color from "colorjs"; import meta from "../deno.json" with { type: "json" }; import type { + CatppuccinAnsiColors, CatppuccinColors, CatppuccinFlavor, + ColorName, Flavors, } from "@catppuccin/palette"; @@ -199,8 +202,83 @@ const accents = [ "lavender", ]; -const formatted = entriesFromObject(definitions) - .reduce((acc, [flavorName, flavor], currentIndex) => { +const ansiMappings = { + black: { + normal: { + mapping: "", // superfluous, exists to make TypeScript happy + code: 0, + }, + bright: { + code: 8, + }, + }, + red: { + normal: { + mapping: "red", + code: 1, + }, + bright: { + code: 9, + }, + }, + green: { + normal: { + mapping: "green", + code: 2, + }, + bright: { + code: 10, + }, + }, + yellow: { + normal: { + mapping: "yellow", + code: 3, + }, + bright: { + code: 11, + }, + }, + blue: { + normal: { + mapping: "blue", + code: 4, + }, + bright: { + code: 12, + }, + }, + magenta: { + normal: { + mapping: "pink", + code: 5, + }, + bright: { + code: 13, + }, + }, + cyan: { + normal: { + mapping: "teal", + code: 6, + }, + bright: { + code: 14, + }, + }, + white: { + normal: { + mapping: "", // superfluous, exists to make TypeScript happy + code: 7, + }, + bright: { + code: 15, + }, + }, +}; + +const formatted = entriesFromObject(definitions).reduce( + (acc, [flavorName, flavor], currentIndex) => { acc[flavorName] = { name: flavor.name, emoji: flavor.emoji, @@ -222,9 +300,41 @@ const formatted = entriesFromObject(definitions) }, {} as Writeable, ), + ansiColors: entriesFromObject(ansiMappings).reduce((acc, [name, props]) => { + const mapping = props.normal.mapping as ColorName; + let normalColorHex = flavor.colors[mapping]; + let brightColorHex: string; + + if (name == "black") { + normalColorHex = flavor.dark ? flavor.colors["surface1"] : flavor.colors["subtext1"]; + brightColorHex = flavor.dark ? flavor.colors["surface2"] : flavor.colors["subtext0"]; + } else if (name == "white") { + normalColorHex = flavor.dark ? flavor.colors["subtext0"] : flavor.colors["surface2"] ; + brightColorHex = flavor.dark ? flavor.colors["subtext1"] : flavor.colors["surface1"] ; + } else { + const brightColor = new Color(normalColorHex); + brightColor.lch.l *= flavor.dark ? 0.94 : 1.09; + brightColor.lch.c += flavor.dark ? 8 : 0; + brightColor.lch.h += 2; + brightColorHex = brightColor.toString({ format: "hex" }); + } + acc[name] = { + normal: { + hex: normalColorHex, + code: props.normal.code, + }, + bright: { + hex: brightColorHex, + code: props.bright.code, + }, + }; + return acc; + }, {} as Writeable), }; return acc; - }, {} as Flavors>); + }, + {} as Flavors>, +); const __dirname = new URL(".", import.meta.url).pathname;