diff --git a/pkgs/flake-module.nix b/pkgs/flake-module.nix
index 52ff2084d..fb8b1536c 100644
--- a/pkgs/flake-module.nix
+++ b/pkgs/flake-module.nix
@@ -3,6 +3,7 @@
./clan-cli/flake-module.nix
./installer/flake-module.nix
./ui/flake-module.nix
+ ./theme/flake-module.nix
];
perSystem = { pkgs, config, ... }: {
diff --git a/pkgs/theme/.editorconfig b/pkgs/theme/.editorconfig
new file mode 100644
index 000000000..4e89cb0e9
--- /dev/null
+++ b/pkgs/theme/.editorconfig
@@ -0,0 +1,3 @@
+[*.{js,jsx,ts,tsx,json}]
+indent_style = space
+indent_size = 4
\ No newline at end of file
diff --git a/pkgs/theme/.envrc b/pkgs/theme/.envrc
new file mode 100644
index 000000000..2933b5bd2
--- /dev/null
+++ b/pkgs/theme/.envrc
@@ -0,0 +1,12 @@
+# Because we depend on nixpkgs sources, uploading to builders takes a long time
+
+source_up
+
+files=(flake-module.nix package.json package-lock.json)
+if type nix_direnv_watch_file &>/dev/null; then
+ nix_direnv_watch_file "${files[@]}"
+else
+ watch_file "${files[@]}"
+fi
+
+use flake .#theme --builders ''
diff --git a/pkgs/theme/.gitignore b/pkgs/theme/.gitignore
new file mode 100644
index 000000000..9e29a683b
--- /dev/null
+++ b/pkgs/theme/.gitignore
@@ -0,0 +1,43 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# nix
+.floco
+src/fonts
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# Generated api code
+openapi.json
+api/
diff --git a/pkgs/theme/default.nix b/pkgs/theme/default.nix
new file mode 100644
index 000000000..d66639194
--- /dev/null
+++ b/pkgs/theme/default.nix
@@ -0,0 +1,41 @@
+{ floco
+, system
+, pkgs
+, clanPkgs
+}:
+let
+
+ lib = floco.lib;
+
+ pjs =
+ let
+ msg = "default.nix: Expected to find `package.json' to lookup " +
+ "package name/version, but no such file exists at: " +
+ (toString ./package.json);
+ in
+ if builtins.pathExists ./package.json then lib.importJSON ./package.json
+ else throw msg;
+ ident = pjs.name;
+ inherit (pjs) version;
+
+
+ fmod = lib.evalModules {
+ modules = [
+ floco.nixosModules.floco
+ { config.floco.settings = { inherit system; basedir = ./.; }; }
+ ./nix/floco-cfg.nix
+ ];
+ specialArgs = {
+ inherit pkgs clanPkgs;
+ };
+ };
+
+ # This attrset holds a few derivations related to our package.
+ # We'll expose these below to the CLI.
+ pkg = fmod.config.floco.packages.${ident}.${version};
+
+
+in
+{
+ inherit pkg fmod;
+}
diff --git a/pkgs/theme/flake-module.nix b/pkgs/theme/flake-module.nix
new file mode 100644
index 000000000..864cea516
--- /dev/null
+++ b/pkgs/theme/flake-module.nix
@@ -0,0 +1,18 @@
+{ self, ... }:
+{
+ perSystem = { self', pkgs, ... }:
+ let
+ inherit (self.inputs) floco;
+ base = pkgs.callPackage ./default.nix { inherit floco; clanPkgs = self'.packages; };
+ in
+ {
+ packages = {
+ theme = base.pkg.global;
+ };
+ devShells.theme = pkgs.callPackage ./shell.nix {
+ inherit pkgs;
+ inherit (base) fmod pkg;
+ clanPkgs = self'.packages;
+ };
+ };
+}
diff --git a/pkgs/theme/index.html b/pkgs/theme/index.html
new file mode 100644
index 000000000..35eadad0e
--- /dev/null
+++ b/pkgs/theme/index.html
@@ -0,0 +1,962 @@
+
+
+
+
Page Title
+
+
+
+
+ neutral0
+
+
+ neutral10
+
+
+ neutral20
+
+
+ neutral30
+
+
+ neutral40
+
+
+ neutral50
+
+
+ neutral51
+
+
+ neutral60
+
+
+ neutral70
+
+
+ neutral80
+
+
+ neutral90
+
+
+ neutral98
+
+
+ neutral100
+
+
+
+
+ red0
+
+
+ red10
+
+
+ red20
+
+
+ red30
+
+
+ red40
+
+
+ red50
+
+
+ red60
+
+
+ red70
+
+
+ red80
+
+
+ red90
+
+
+ red95
+
+
+ red100
+
+
+
+
+ green0
+
+
+ green10
+
+
+ green20
+
+
+ green30
+
+
+ green40
+
+
+ green50
+
+
+ green60
+
+
+ green70
+
+
+ green72
+
+
+ green80
+
+
+ green90
+
+
+ green98
+
+
+ green100
+
+
+
+
+ yellow0
+
+
+ yellow10
+
+
+ yellow20
+
+
+ yellow30
+
+
+ yellow40
+
+
+ yellow50
+
+
+ yellow60
+
+
+ yellow70
+
+
+ yellow80
+
+
+ yellow87
+
+
+ yellow90
+
+
+ yellow98
+
+
+ yellow100
+
+
+
+
+ purple0
+
+
+ purple10
+
+
+ purple20
+
+
+ purple30
+
+
+ purple33
+
+
+ purple40
+
+
+ purple50
+
+
+ purple60
+
+
+ purple70
+
+
+ purple80
+
+
+ purple90
+
+
+ purple100
+
+
+
+
+ blue0
+
+
+ blue10
+
+
+ blue20
+
+
+ blue30
+
+
+ blue40
+
+
+ blue50
+
+
+ blue60
+
+
+ blue70
+
+
+ blue80
+
+
+ blue90
+
+
+ blue95
+
+
+ blue100
+
+
+
+
diff --git a/pkgs/theme/nix/floco-cfg.nix b/pkgs/theme/nix/floco-cfg.nix
new file mode 100644
index 000000000..e5b5d8719
--- /dev/null
+++ b/pkgs/theme/nix/floco-cfg.nix
@@ -0,0 +1,26 @@
+# ============================================================================ #
+#
+# Aggregates configs making them available to `default.nix', `flake.nix',
+# or other projects that want to consume this module/package as a dependency.
+#
+# ---------------------------------------------------------------------------- #
+{
+ _file = "theme/nix/floco-cfg.nix";
+ imports =
+ let
+ ifExist = builtins.filter builtins.pathExists [
+ ./pdefs.nix # Generated `pdefs.nix'
+ ./foverrides.nix # Explicit config
+ ];
+ in
+ ifExist
+ ++ [
+
+ ];
+}
+# ---------------------------------------------------------------------------- #
+#
+#
+#
+# ============================================================================ #
+
diff --git a/pkgs/theme/nix/foverrides.nix b/pkgs/theme/nix/foverrides.nix
new file mode 100644
index 000000000..acdafbe3b
--- /dev/null
+++ b/pkgs/theme/nix/foverrides.nix
@@ -0,0 +1,12 @@
+{ lib, config, ... }:
+let
+ pjs = lib.importJSON ../package.json;
+ ident = pjs.name;
+ inherit (pjs) version;
+in
+{
+ config.floco.packages.${ident}.${version} =
+ {
+ source = lib.libfloco.cleanLocalSource ../.;
+ };
+}
diff --git a/pkgs/theme/nix/pdefs.nix b/pkgs/theme/nix/pdefs.nix
new file mode 100644
index 000000000..d3567754a
--- /dev/null
+++ b/pkgs/theme/nix/pdefs.nix
@@ -0,0 +1,90 @@
+{
+ floco = {
+ pdefs = {
+ "@clan/colors" = {
+ "1.0.0" = {
+ depInfo = {
+ "@material/material-color-utilities" = {
+ descriptor = "^0.2.6";
+ pin = "0.2.7";
+ };
+ "@types/node" = {
+ descriptor = "^20.3.2";
+ pin = "20.8.2";
+ };
+ typescript = {
+ descriptor = "^5.1.5";
+ pin = "5.2.2";
+ };
+ };
+ fetchInfo = "path:..";
+ ident = "@clan/colors";
+ lifecycle = {
+ build = true;
+ };
+ ltype = "dir";
+ treeInfo = {
+ "node_modules/@material/material-color-utilities" = {
+ dev = true;
+ key = "@material/material-color-utilities/0.2.7";
+ };
+ "node_modules/@types/node" = {
+ dev = true;
+ key = "@types/node/20.8.2";
+ };
+ "node_modules/typescript" = {
+ dev = true;
+ key = "typescript/5.2.2";
+ };
+ };
+ version = "1.0.0";
+ };
+ };
+ "@material/material-color-utilities" = {
+ "0.2.7" = {
+ fetchInfo = {
+ narHash = "sha256-hRYXqtkoXHoB30v1hstWz7dO7dNeBb6EJqZG66hHi94=";
+ type = "tarball";
+ url = "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz";
+ };
+ ident = "@material/material-color-utilities";
+ ltype = "file";
+ treeInfo = { };
+ version = "0.2.7";
+ };
+ };
+ "@types/node" = {
+ "20.8.2" = {
+ fetchInfo = {
+ narHash = "sha256-o4hyob1kLnm0OE8Rngm0d6XJxobpMlYSoquusktmLPk=";
+ type = "tarball";
+ url = "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz";
+ };
+ ident = "@types/node";
+ ltype = "file";
+ treeInfo = { };
+ version = "20.8.2";
+ };
+ };
+ typescript = {
+ "5.2.2" = {
+ binInfo = {
+ binPairs = {
+ tsc = "bin/tsc";
+ tsserver = "bin/tsserver";
+ };
+ };
+ fetchInfo = {
+ narHash = "sha256-io9rXH9RLRLB0484ZdvcqblLQquLFUBGxDuwSixWxus=";
+ type = "tarball";
+ url = "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz";
+ };
+ ident = "typescript";
+ ltype = "file";
+ treeInfo = { };
+ version = "5.2.2";
+ };
+ };
+ };
+ };
+}
diff --git a/pkgs/theme/package-lock.json b/pkgs/theme/package-lock.json
new file mode 100644
index 000000000..6a755684b
--- /dev/null
+++ b/pkgs/theme/package-lock.json
@@ -0,0 +1,63 @@
+{
+ "name": "@clan/colors",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@clan/colors",
+ "version": "1.0.0",
+ "license": "ISC",
+ "devDependencies": {
+ "@material/material-color-utilities": "^0.2.6",
+ "@types/node": "^20.3.2",
+ "typescript": "^5.1.5"
+ }
+ },
+ "node_modules/@material/material-color-utilities": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz",
+ "integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.8.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
+ "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
+ "dev": true
+ },
+ "node_modules/typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ }
+ },
+ "dependencies": {
+ "@material/material-color-utilities": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz",
+ "integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "20.8.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
+ "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
+ "dev": true
+ },
+ "typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true
+ }
+ }
+}
diff --git a/pkgs/theme/package.json b/pkgs/theme/package.json
new file mode 100644
index 000000000..febd95e29
--- /dev/null
+++ b/pkgs/theme/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@clan/colors",
+ "version": "1.0.0",
+ "description": "",
+ "type": "module",
+ "files": [
+ "colors.json"
+ ],
+ "scripts": {
+ "typecheck": "./node_modules/.bin/tsc -p ./tsconfig.json --noEmit",
+ "build": "tsc --build --clean && tsc && node ./build/main.js",
+ "html": "tsc --build --clean && tsc && node ./build/generate.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@material/material-color-utilities": "^0.2.6",
+ "typescript": "^5.1.5",
+ "@types/node": "^20.3.2"
+ }
+}
diff --git a/pkgs/theme/shell.nix b/pkgs/theme/shell.nix
new file mode 100644
index 000000000..697ce02dc
--- /dev/null
+++ b/pkgs/theme/shell.nix
@@ -0,0 +1,24 @@
+{ fmod
+, pkg
+, pkgs
+, clanPkgs
+}:
+pkgs.mkShell {
+ buildInputs = [
+ fmod.config.floco.settings.nodePackage
+ ];
+ shellHook = ''
+ ID=${pkg.built.tree}
+ currID=$(cat .floco/.node_modules_id 2> /dev/null)
+
+ mkdir -p .floco
+ if [[ "$ID" != "$currID" || ! -d "node_modules" ]];
+ then
+ ${pkgs.rsync}/bin/rsync -a --chmod=ug+w --delete ${pkg.built.tree}/node_modules/ ./node_modules/
+ echo -n $ID > .floco/.node_modules_id
+ echo "floco ok: node_modules updated"
+ fi
+
+ export PATH="$PATH:$(realpath ./node_modules)/.bin"
+ '';
+}
diff --git a/pkgs/theme/src/config.ts b/pkgs/theme/src/config.ts
new file mode 100644
index 000000000..2d2bb39c6
--- /dev/null
+++ b/pkgs/theme/src/config.ts
@@ -0,0 +1,78 @@
+import { AliasMap, BaseColors, HexString } from "./types.js";
+
+export type PaletteConfig = {
+ baseColors: BaseColors;
+ tones: number[];
+ aliases: AliasMap<"primary" | "secondary" | "error">;
+ common: {
+ // Black and white is always constant
+ // We declare this on the type level
+ white: "#ffffff";
+ black: "#000000";
+ // Some other color constants/reservation
+ [id: string]: HexString;
+ };
+};
+
+export const config: PaletteConfig = {
+ /** All color shades that are available
+ * This colors are used as "key colors" to generate a tonal palette from 0 to 100
+ * Steps are defined in 'tones'
+ */
+ baseColors: {
+ neutral: {
+ keyColor: "#807788",
+ tones: [98],
+ },
+ red: {
+ keyColor: "#e82439",
+ tones: [95],
+ },
+ green: {
+ keyColor: "#7AC51B",
+ tones: [98],
+ },
+ yellow: {
+ keyColor: "#E0E01F",
+ tones: [98],
+ },
+ purple: {
+ keyColor: "#661bc5",
+ tones: [],
+ },
+ blue: {
+ keyColor: "#1B7AC5",
+ tones: [95],
+ },
+ },
+
+ /** Common tones to generate out of all the baseColors
+ * number equals to the amount of light present in the color (HCT Color Space)
+ */
+ tones: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
+
+ /** create aliases from the color palette
+ *
+ * @example
+ *
+ * primary: "blue"
+ * ->
+ * ...
+ * primary40 -> blue40
+ * primary50 -> blue50
+ * ...
+ */
+ aliases: {
+ primary: "purple",
+ secondary: "green",
+ error: "red",
+ },
+ /** some color names are reserved
+ * typically those colors do not change when switching theme
+ * or are other types of constant in the UI
+ */
+ common: {
+ white: "#ffffff",
+ black: "#000000",
+ },
+};
diff --git a/pkgs/theme/src/generate.ts b/pkgs/theme/src/generate.ts
new file mode 100644
index 000000000..353c8bfe6
--- /dev/null
+++ b/pkgs/theme/src/generate.ts
@@ -0,0 +1,46 @@
+import { writeFile } from "fs";
+import palette from "./out.json" assert { type: "json" };
+import { config } from "./config.js";
+
+type PaletteFile = typeof palette;
+
+const html = (palette: PaletteFile): string => {
+ const colors = Object.keys(config.baseColors).map((baseName) => {
+ const colors = Object.entries(palette.ref.palette)
+ .filter(([name, _]) => name.includes(baseName))
+ .sort((a, b) => {
+ return a[1].meta.color.shade - b[1].meta.color.shade;
+ })
+ .map(([key, color]) => {
+ console.log({ key, color });
+ return `${key}
`;
+ });
+ return `${colors.join(
+ "\n",
+ )}
`;
+ });
+
+ return `
+
+
+Page Title
+
+
+
+${colors.join("\n")}
+
+
+
+`;
+};
+
+writeFile("index.html", html(palette), (err) => {
+ if (err) {
+ console.error({ err });
+ } else {
+ console.log("Exported colors to html");
+ }
+});
diff --git a/pkgs/theme/src/main.ts b/pkgs/theme/src/main.ts
new file mode 100644
index 000000000..16cada9b6
--- /dev/null
+++ b/pkgs/theme/src/main.ts
@@ -0,0 +1,182 @@
+#!usr/bin/node
+import * as fs from "fs";
+import {
+ argbFromHex,
+ Hct,
+ hexFromArgb,
+} from "@material/material-color-utilities";
+import {
+ AliasTokenMap,
+ ColorDesignToken,
+ ColorSet,
+ HexString,
+ RefTokenSystem,
+ TonalPalette,
+ TonalPaletteConfig,
+ TonalPaletteItem,
+} from "./types.js";
+import { config } from "./config.js";
+
+const { baseColors, tones, aliases, common } = config;
+
+/** Takes a color, tone and name
+ * If a tone is given adjust the lightning level accordingly
+ *
+ * @returns TonalPaletteItem (meta wrapper around HCT)
+ */
+const getTonalPaletteItem = (
+ value: HexString,
+ name: string,
+ tone?: number,
+): TonalPaletteItem => {
+ const aRGB = argbFromHex(value);
+ const color = Hct.fromInt(aRGB);
+ if (tone !== undefined) {
+ color.tone = tone;
+ }
+ return {
+ shade: color.tone,
+ name: `${name || color.chroma}${Math.round(color.tone)}`,
+ baseName: name,
+ value: color,
+ };
+};
+
+/** create a flat list of the cross product from all colors and all tones.
+ *
+ * every color is mapped in the range from 0 to 100
+ * with the steps configure in `config.tones'
+ * additionally the key color is added unmodified
+ * lightning levels are rounded to the next natural number to form the 'name'
+ * Example:
+ *
+ * "blue" x [20.1, 30.3]
+ * ->
+ * [blue20, blue30]
+ */
+const mkTonalPalette =
+ (config: TonalPaletteConfig) =>
+ (name: string) =>
+ (keyTone: HexString): TonalPalette => {
+ const { tones } = config;
+ const aRGB = argbFromHex(keyTone);
+ const HctColor = Hct.fromInt(aRGB);
+ const roundedTone = Math.round(HctColor.tone * 100) / 100;
+
+ const localTones = [...tones, roundedTone];
+
+ return localTones.map((t) => getTonalPaletteItem(keyTone, name, t));
+ };
+
+/**
+ * Converts a PaletteItem into a hex color. (Wrapped)
+ * Adding meta attributes which avoids any information loss.
+ */
+const toDesignTokenContent = (color: TonalPaletteItem): ColorDesignToken => {
+ const { value } = color;
+ return {
+ type: "color",
+ value: hexFromArgb(value.toInt()),
+ meta: {
+ color,
+ date: new Date(),
+ },
+ };
+};
+
+const color: ColorSet = Object.entries(baseColors)
+ .map(([name, baseColor]) => ({
+ name,
+ baseColor,
+ tones: mkTonalPalette({
+ tones: [...tones, ...baseColor.tones].sort((a, b) => a - b),
+ })(name)(baseColor.keyColor),
+ }))
+ .reduce((acc, curr) => {
+ let currTones = curr.tones.reduce(
+ (o, v) => ({
+ ...o,
+ [v.name]: toDesignTokenContent(v),
+ }),
+ {},
+ );
+ return {
+ ...acc,
+ ...currTones,
+ };
+ }, {});
+
+/** Generate a set of tokens from a given alias mapping
+ *
+ * @param alias A string e.g. Primary -> Blue (Primary is the alias)
+ * @param name A string; Basename of the referenced value (e.g. Blue)
+ * @param colors A set of colors
+ * @returns All aliases from the given color set
+ */
+function resolveAlias(
+ alias: string,
+ name: string,
+ colors: ColorSet,
+): AliasTokenMap {
+ // All colors from the color map belonging to that single alias
+ // Example:
+ // Primary -> "blue"
+ // =>
+ // [ (blue0) , (blue10) , ..., (blue100) ]
+ const all = Object.values(colors)
+ .filter((n) => n.meta.color.name.includes(name))
+ .filter((n) => !n.meta.color.name.includes("."));
+
+ const tokens = all
+ .map((shade) => {
+ const shadeNumber = shade.meta.color.shade;
+ return {
+ name: `${alias}${Math.round(shadeNumber)}`,
+ value: { value: `{ref.palette.${shade.meta.color.name}}` },
+ // propagate the meta attribute of the actual value
+ meta: shade.meta,
+ };
+ })
+ // sort by tone
+ .sort((a, b) => a.meta.color.value.tone - b.meta.color.value.tone)
+ .reduce((acc, { name, value }) => ({ ...acc, [name]: value }), {});
+ return tokens;
+}
+
+const aliasMap = Object.entries(aliases).reduce(
+ (prev, [key, value]) => ({
+ ...prev,
+ ...resolveAlias(key, value, color),
+ }),
+ {},
+);
+
+const commonColors = Object.entries(common)
+ .map(([name, value]) =>
+ toDesignTokenContent(getTonalPaletteItem(value, name)),
+ )
+ .reduce(
+ (acc, val) => ({ ...acc, [val.meta.color.baseName]: val }),
+ {},
+ ) as ColorSet;
+
+const toPaletteToken = (color: ColorSet): RefTokenSystem => ({
+ ref: {
+ palette: color,
+ alias: aliasMap,
+ common: commonColors,
+ },
+});
+
+// Dump tokens to json file
+fs.writeFile(
+ "colors.json",
+ JSON.stringify(toPaletteToken(color), null, 2),
+ (err) => {
+ if (err) {
+ console.error({ err });
+ } else {
+ console.log("tokens successfully exported");
+ }
+ },
+);
diff --git a/pkgs/theme/src/types.ts b/pkgs/theme/src/types.ts
new file mode 100644
index 000000000..4a7a1888b
--- /dev/null
+++ b/pkgs/theme/src/types.ts
@@ -0,0 +1,90 @@
+import { Hct } from "@material/material-color-utilities";
+
+export type BaseColors = {
+ neutral: BaseColor;
+ red: BaseColor;
+ green: BaseColor;
+ yellow: BaseColor;
+ purple: BaseColor;
+ blue: BaseColor;
+};
+
+export type BaseColor = {
+ keyColor: HexString;
+ tones: number[];
+ follows?: string;
+};
+
+export type ColorSet = { [key: string]: ColorDesignToken };
+
+/** The resolved alias tokens
+ *
+ * @example
+ * {
+ * primary: "blue"
+ * ...
+ * }
+ *
+ */
+export type AliasMap = {
+ [alias in T]: keyof BaseColors;
+};
+
+/** The resolved alias tokens
+ *
+ * @example
+ * {
+ * primary0: "blue40"
+ * primary10: "blue40"
+ * ...
+ * primary100: "blue100"
+ * }
+ *
+ * Unfortunately My Typescript skills lack the ability to express this type any narrower :/
+ */
+export type AliasTokenMap = {
+ [alias: string]: { value: string };
+};
+
+export type TonalPaletteConfig = {
+ tones: number[];
+};
+
+export type HexString = string;
+
+export type TonalPaletteItem = {
+ /**
+ * @example
+ * 20
+ */
+ shade: number;
+ /**
+ * @example
+ * "blue20"
+ */
+ name: string;
+ /**
+ * @example
+ * "blue"
+ */
+ baseName: string;
+ value: Hct;
+};
+export type TonalPalette = TonalPaletteItem[];
+
+export type ColorDesignToken = {
+ type: "color";
+ value: HexString;
+ meta: {
+ color: TonalPaletteItem;
+ date: Date;
+ };
+};
+
+export type RefTokenSystem = {
+ ref: {
+ palette: ColorSet;
+ common: ColorSet;
+ alias: AliasTokenMap;
+ };
+};
diff --git a/pkgs/theme/tsconfig.json b/pkgs/theme/tsconfig.json
new file mode 100644
index 000000000..4b562c9c5
--- /dev/null
+++ b/pkgs/theme/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "include": ["src"],
+ "compilerOptions": {
+ "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+
+ "lib": [
+ "ESNext",
+ "dom"
+ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
+ "module": "NodeNext" /* Specify what module code is generated. */,
+ "rootDir": "src" /* Specify the root folder within your source files. */,
+ "moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ "resolveJsonModule": true /* Enable importing .json files. */,
+ "outDir": "build",
+
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
+
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
+ "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */,
+ "strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,
+ "strictBindCallApply": true /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */,
+ "strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */,
+ "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */,
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ "alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
+ "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */,
+ "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */,
+ "exactOptionalPropertyTypes": true /* Interpret optional property types as written, rather than adding 'undefined'. */,
+ "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */,
+ "noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
+ "noUncheckedIndexedAccess": true /* Add 'undefined' to a type when accessed using an index. */,
+ "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
+ "noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+ }
+}
diff --git a/pkgs/ui/nix/floco-cfg.nix b/pkgs/ui/nix/floco-cfg.nix
index d8711270a..f0969327e 100644
--- a/pkgs/ui/nix/floco-cfg.nix
+++ b/pkgs/ui/nix/floco-cfg.nix
@@ -2,5 +2,6 @@
imports = [
./pdefs.nix
./foverrides.nix
+ ../../theme/nix/floco-cfg.nix
];
}
diff --git a/pkgs/ui/nix/foverrides.nix b/pkgs/ui/nix/foverrides.nix
index 3d4393eb6..5fb91a34c 100644
--- a/pkgs/ui/nix/foverrides.nix
+++ b/pkgs/ui/nix/foverrides.nix
@@ -100,6 +100,12 @@ in
optional = false;
dev = true;
};
+ "node_modules/@clan/colors" = {
+ key = "@clan/colors/1.0.0";
+ link = false;
+ optional = false;
+ dev = true;
+ };
};
});
in
diff --git a/pkgs/ui/src/app/theme/themes.ts b/pkgs/ui/src/app/theme/themes.ts
index edd2786ee..3da1b34d7 100644
--- a/pkgs/ui/src/app/theme/themes.ts
+++ b/pkgs/ui/src/app/theme/themes.ts
@@ -1,5 +1,7 @@
import { createTheme } from "@mui/material/styles";
+import colors from "@clan/colors/colors.json";
+
export const darkTheme = createTheme({
breakpoints: {
values: {
@@ -15,6 +17,7 @@ export const darkTheme = createTheme({
},
});
+const { palette, common } = colors.ref;
export const lightTheme = createTheme({
breakpoints: {
values: {
@@ -27,5 +30,27 @@ export const lightTheme = createTheme({
},
palette: {
mode: "light",
+ background: {
+ default: common.white.value,
+ paper: palette.neutral98.value,
+ },
+ primary: {
+ main: palette.green50.value,
+ },
+ secondary: {
+ main: palette.green50.value,
+ },
+ error: {
+ main: palette.red50.value,
+ },
+ warning: {
+ main: palette.yellow50.value,
+ },
+ success: {
+ main: palette.green50.value,
+ },
+ info: {
+ main: palette.red50.value,
+ },
},
});