🧑‍💻(tokens) add getThemesFromGlobals utils

Export a new util method `getThemesFromGlobals` to easily generate theme with
its variant only by providing a partial globals object. By default it returns
both available theme variants (light & dark). Through options you can prefix
variant property keys, only generate theme with a subset of variant and also
overrides/extend theme.
This commit is contained in:
jbpenrath
2025-12-08 20:35:45 +01:00
committed by Jean-Baptiste PENRATH
parent fe8eb4b802
commit db26e21b88
19 changed files with 1766 additions and 2348 deletions

View File

@@ -0,0 +1,5 @@
---
"@gouvfr-lasuite/cunningham-tokens": minor
---
Add getThemesFromGlobals util

2
.gitignore vendored
View File

@@ -33,4 +33,6 @@ env.d
.turbo .turbo
ghpages-output ghpages-output
# Project ignore files
packages/react/chromatic.config.json packages/react/chromatic.config.json
packages/tokens/src/lib/cunningham.ts

File diff suppressed because it is too large Load Diff

View File

@@ -27,11 +27,11 @@ const THEMES: Record<Theme, Record<Variant, string | undefined>> = {
}, },
[Theme.REDFLUX]: { [Theme.REDFLUX]: {
light: undefined, light: undefined,
dark: "redflux", dark: "redflux-dark",
}, },
[Theme.BLUENEY]: { [Theme.BLUENEY]: {
light: undefined, light: undefined,
dark: "blueney", dark: "blueney-dark",
}, },
}; };

View File

@@ -1,5 +1,10 @@
import React from "react"; import React from "react";
import { Button, Modal, ModalSize, useModal } from "@gouvfr-lasuite/cunningham-react"; import {
Button,
Modal,
ModalSize,
useModal,
} from "@gouvfr-lasuite/cunningham-react";
const Onboarding = () => { const Onboarding = () => {
const modal = useModal({ const modal = useModal({

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,33 +0,0 @@
/**
* This util script is used to convert Figma's Token Studio JSON color config to TS file.
*
* CLI usage example:
* $ yarn figma-to-cunningham dark.json
*/
import fs from "fs";
import path from "path";
const filePath = process.argv[2];
const data = fs.readFileSync(filePath);
const json = JSON.parse(data.toString());
const output: any = {};
Object.keys(json).forEach((color) => {
console.log("Parsing color:", color);
Object.keys(json[color]).forEach((colorVariation) => {
const colorName = color === "Succes" ? "success" : color.toLowerCase();
output[colorName + "-" + colorVariation] =
json[color][colorVariation].value;
});
});
console.log("Output:", output);
const content = `/**
* /!\\ Please do not edit this file directly. Instead use the FigmaToCunningham.ts converter that generated this file.
*/
export const colors = ${JSON.stringify(output)}
`;
const fileName = path.parse(filePath).name;
const outputPath = `./src/bin/ThemeColors/${fileName}.ts`;
fs.writeFileSync(outputPath, content);
console.log("Successfuly written to", outputPath);

View File

@@ -26,12 +26,11 @@
"lint": "eslint . 'src/**/*.{ts,tsx}'", "lint": "eslint . 'src/**/*.{ts,tsx}'",
"dev": "nodemon --watch 'src/bin' --ext '*' --exec 'yarn build'", "dev": "nodemon --watch 'src/bin' --ext '*' --exec 'yarn build'",
"build-bin": "cd src/bin && rm -rf ../../dist/bin && tsc -p tsconfig.build.json && tsc-alias && chmod +x ../../dist/bin/Main.js", "build-bin": "cd src/bin && rm -rf ../../dist/bin && tsc -p tsconfig.build.json && tsc-alias && chmod +x ../../dist/bin/Main.js",
"build-lib": "cp dist/cunningham-tokens.ts src/lib && cd src/lib && tsc -p tsconfig.json", "build-lib": "cp src/bin/cunningham.ts src/lib && cp dist/cunningham-tokens.ts src/lib && cd src/lib && tsc -p tsconfig.json",
"build": "yarn build-bin && yarn build-default-theme && yarn build-lib && cd ../.. && ln -sf ../../packages/tokens/dist/bin/Main.js node_modules/.bin/cunningham", "build": "yarn build-bin && yarn build-default-theme && yarn build-lib && cd ../.. && ln -sf ../../packages/tokens/dist/bin/Main.js node_modules/.bin/cunningham",
"build-default-theme": "node ./dist/bin/Main.js -o dist -s html -g scss,css,js,ts --utility-classes", "build-default-theme": "node ./dist/bin/Main.js -o dist -s html -g scss,css,js,ts --utility-classes",
"test": "cross-env FORCE_COLOR=1 jest --runInBand --verbose src", "test": "cross-env FORCE_COLOR=1 jest --runInBand --verbose src",
"test-ci": "cross-env FORCE_COLOR=1 jest --runInBand src", "test-ci": "cross-env FORCE_COLOR=1 jest --runInBand src"
"figma-to-cunningham": "ts-node FigmaToCunningham.ts"
}, },
"dependencies": { "dependencies": {
"chalk": "4.1.2", "chalk": "4.1.2",

View File

@@ -1,51 +0,0 @@
/**
* /!\ Please do not edit this file directly. Instead use the FigmaToCunningham.ts converter that generated this file.
*/
export const colors = {
'greyscale-100': '#182536',
'greyscale-200': '#303C4B',
'greyscale-300': '#555F6B',
'greyscale-400': '#79818A',
'greyscale-500': '#9EA3AA',
'greyscale-600': '#C2C6CA',
'greyscale-700': '#E7E8EA',
'greyscale-800': '#F3F4F4',
'greyscale-900': '#FAFAFB',
'greyscale-000': '#0C1A2B',
'primary-100': '#3B4C62',
'primary-200': '#4D6481',
'primary-300': '#6381A6',
'primary-400': '#7FA5D5',
'primary-500': '#8CB5EA',
'primary-600': '#A3C4EE',
'primary-700': '#C3D8F4',
'primary-800': '#DDE9F8',
'primary-900': '#F4F8FD',
'success-100': '#EEF8D7',
'success-200': '#D9F1B2',
'success-300': '#BDE985',
'success-400': '#A0E25D',
'success-500': '#76D628',
'success-600': '#5BB520',
'success-700': '#43941A',
'success-800': '#307414',
'success-900': '#225D10',
'warning-100': '#F7F3D5',
'warning-200': '#F0E5AA',
'warning-300': '#E8D680',
'warning-400': '#E3C95F',
'warning-500': '#D9B32B',
'warning-600': '#BD9721',
'warning-700': '#9D7B1C',
'warning-800': '#7E6016',
'warning-900': '#684D12',
'danger-100': '#F8D0D0',
'danger-200': '#F09898',
'danger-300': '#F09898',
'danger-400': '#ED8585',
'danger-500': '#E96666',
'danger-600': '#DD6666',
'danger-700': '#C36666',
'danger-800': '#AE6666',
'danger-900': '#9D6666',
};

View File

@@ -1,51 +0,0 @@
/**
* /!\ Please do not edit this file directly. Instead use the FigmaToCunningham.ts converter that generated this file.
*/
export const colors = {
'greyscale-100': '#FAFAFB',
'greyscale-200': '#F3F4F4',
'greyscale-300': '#E7E8EA',
'greyscale-400': '#C2C6CA',
'greyscale-500': '#9EA3AA',
'greyscale-600': '#79818A',
'greyscale-700': '#555F6B',
'greyscale-800': '#303C4B',
'greyscale-900': '#0C1A2B',
'greyscale-000': '#FFFFFF',
'primary-100': '#EBF2FC',
'primary-200': '#8CB5EA',
'primary-300': '#5894E1',
'primary-400': '#377FDB',
'primary-500': '#055FD2',
'primary-600': '#0556BF',
'primary-700': '#044395',
'primary-800': '#033474',
'primary-900': '#022858',
'success-100': '#EFFCD3',
'success-200': '#DBFAA9',
'success-300': '#BEF27C',
'success-400': '#A0E659',
'success-500': '#76D628',
'success-600': '#5AB81D',
'success-700': '#419A14',
'success-800': '#2C7C0C',
'success-900': '#1D6607',
'warning-100': '#FFF8CD',
'warning-200': '#FFEF9B',
'warning-300': '#FFE469',
'warning-400': '#FFDA43',
'warning-500': '#FFC805',
'warning-600': '#DBA603',
'warning-700': '#B78702',
'warning-800': '#936901',
'warning-900': '#7A5400',
'danger-100': '#F4B0B0',
'danger-200': '#EE8A8A',
'danger-300': '#E65454',
'danger-400': '#E13333',
'danger-500': '#DA0000',
'danger-600': '#C60000',
'danger-700': '#9B0000',
'danger-800': '#780000',
'danger-900': '#5C0000',
};

View File

@@ -1,8 +1,22 @@
/*
This file is hardlinked from `src/shared/cunningham.ts`
to `src/bin/cunningham.ts` and `src/lib/cunningham.ts`
```
cd packages/tokens
ln src/shared/cunningham.ts src/lib/cunningham.ts
ln src/shared/cunningham.ts src/bin/cunningham.ts
```
It is a convenient way to share the same tokens between the bin and the lib
and do not put extra logic to customize build outputs.
*/
export const colors = { export const colors = {
"logo-1": "#377FDB", "logo-1-light": "#377FDB",
"logo-2": "#377FDB", "logo-2-light": "#377FDB",
"logo-1-dark": "#95ABFF", "logo-1-dark": "#C1D6F2",
"logo-2-dark": "#95ABFF", "logo-2-dark": "#C1D6F2",
"brand-050": "#EAF1FB", "brand-050": "#EAF1FB",
"brand-100": "#D5E4F7", "brand-100": "#D5E4F7",
"brand-150": "#C0D6F4", "brand-150": "#C0D6F4",
@@ -291,46 +305,47 @@ export const colors = {
"pink-850": "#332028", "pink-850": "#332028",
"pink-900": "#24181D", "pink-900": "#24181D",
"pink-950": "#160F12", "pink-950": "#160F12",
"black-000": "#00000000", "black-000": "#1B1C1D00",
"black-050": "#0000000D", "black-050": "#1B1C1D0D",
"black-100": "#0000001A", "black-100": "#1B1C1D1A",
"black-150": "#00000026", "black-150": "#1B1C1D26",
"black-200": "#00000033", "black-200": "#1B1C1D33",
"black-250": "#00000040", "black-250": "#1B1C1D40",
"black-300": "#0000004D", "black-300": "#1B1C1D4D",
"black-350": "#00000059", "black-350": "#1B1C1D59",
"black-400": "#00000066", "black-400": "#1B1C1D66",
"black-450": "#00000073", "black-450": "#1B1C1D73",
"black-500": "#00000080", "black-500": "#1B1C1D80",
"black-550": "#0000008C", "black-550": "#1B1C1D8C",
"black-600": "#00000099", "black-600": "#1B1C1D99",
"black-650": "#000000A6", "black-650": "#1B1C1DA6",
"black-700": "#000000B3", "black-700": "#1B1C1DB2",
"black-750": "#000000BF", "black-750": "#1B1C1DBF",
"black-800": "#000000CC", "black-800": "#1B1C1DCC",
"black-850": "#000000D9", "black-850": "#1B1C1DD9",
"black-900": "#000000E6", "black-900": "#1B1C1DE5",
"black-950": "#000000F2", "black-950": "#101112F2",
"white-000": "#FFFFFF", "white-000": "#F7F8F800",
"white-050": "#FFFFFF0D", "white-050": "#F7F8F80D",
"white-100": "#FFFFFF1A", "white-100": "#F7F8F81A",
"white-150": "#FFFFFF26", "white-150": "#F7F8F826",
"white-200": "#FFFFFF33", "white-200": "#F7F8F833",
"white-250": "#FFFFFF40", "white-250": "#F7F8F840",
"white-300": "#FFFFFF4D", "white-300": "#F7F8F84D",
"white-350": "#FFFFFF59", "white-350": "#F7F8F859",
"white-400": "#FFFFFF66", "white-400": "#F7F8F866",
"white-450": "#FFFFFF73", "white-450": "#F7F8F873",
"white-500": "#FFFFFF80", "white-500": "#F7F8F880",
"white-550": "#FFFFFF8C", "white-550": "#F7F8F88C",
"white-600": "#FFFFFF99", "white-600": "#F7F8F899",
"white-650": "#FFFFFFA6", "white-650": "#F7F8F8A6",
"white-700": "#FFFFFFB3", "white-700": "#F7F8F8B2",
"white-750": "#FFFFFFBF", "white-750": "#F7F8F8BF",
"white-800": "#FFFFFFCC", "white-800": "#F7F8F8CC",
"white-850": "#FFFFFFD9", "white-850": "#F7F8F8D9",
"white-900": "#FFFFFFE6", "white-900": "#F7F8F8E5",
"white-950": "#FFFFFFF2", "white-950": "#F7F8F8F2",
"white-975": "#F7F8F8F9",
}; };
const fontFamilies = { const fontFamilies = {
@@ -374,7 +389,7 @@ const transitions = {
}; };
const breakpoints = { const breakpoints = {
xs: 0, xs: "0px",
sm: "576px", sm: "576px",
md: "768px", md: "768px",
lg: "992px", lg: "992px",
@@ -425,7 +440,7 @@ export const globals = {
breakpoints, breakpoints,
}; };
export const contextuaDefault = { export const contextualDefaultTokens = {
background: { background: {
surface: { surface: {
primary: "ref(globals.colors.gray-000)", primary: "ref(globals.colors.gray-000)",
@@ -551,13 +566,10 @@ export const contextuaDefault = {
tertiary: "ref(globals.colors.gray-150)", tertiary: "ref(globals.colors.gray-150)",
}, },
}, },
text: {
primary: "ref(globals.colors.black-050)",
},
}, },
content: { content: {
logo1: "ref(globals.colors.logo-1)", logo1: "ref(globals.colors.logo-1-light)",
logo2: "ref(globals.colors.logo-2)", logo2: "ref(globals.colors.logo-2-light)",
semantic: { semantic: {
contextual: { contextual: {
primary: "ref(globals.colors.white-950)", primary: "ref(globals.colors.white-950)",
@@ -689,11 +701,6 @@ export const contextuaDefault = {
}, },
}, },
}; };
const defaultTheme = {
globals,
contextuals: contextuaDefault,
components: {},
};
export const contextualDarkTokens = { export const contextualDarkTokens = {
background: { background: {
@@ -959,7 +966,11 @@ export const contextualDarkTokens = {
export default { export default {
themes: { themes: {
default: defaultTheme, default: {
globals,
contextuals: contextualDefaultTokens,
components: {},
},
dark: { dark: {
globals, globals,
contextuals: contextualDarkTokens, contextuals: contextualDarkTokens,

View File

@@ -164,10 +164,10 @@ describe("Cunningham Bin", () => {
expect(fs.existsSync(cssTokensFile)).toEqual(true); expect(fs.existsSync(cssTokensFile)).toEqual(true);
expect(fs.readFileSync(cssTokensFile).toString()).toMatchInlineSnapshot(` expect(fs.readFileSync(cssTokensFile).toString()).toMatchInlineSnapshot(`
":root { ":root {
--c--globals--colors--logo-1: #377FDB; --c--globals--colors--logo-1-light: #377FDB;
--c--globals--colors--logo-2: #377FDB; --c--globals--colors--logo-2-light: #377FDB;
--c--globals--colors--logo-1-dark: #95ABFF; --c--globals--colors--logo-1-dark: #C1D6F2;
--c--globals--colors--logo-2-dark: #95ABFF; --c--globals--colors--logo-2-dark: #C1D6F2;
--c--globals--colors--brand-050: #EAF1FB; --c--globals--colors--brand-050: #EAF1FB;
--c--globals--colors--brand-100: #D5E4F7; --c--globals--colors--brand-100: #D5E4F7;
--c--globals--colors--brand-150: #C0D6F4; --c--globals--colors--brand-150: #C0D6F4;
@@ -456,46 +456,47 @@ describe("Cunningham Bin", () => {
--c--globals--colors--pink-850: #332028; --c--globals--colors--pink-850: #332028;
--c--globals--colors--pink-900: #24181D; --c--globals--colors--pink-900: #24181D;
--c--globals--colors--pink-950: #160F12; --c--globals--colors--pink-950: #160F12;
--c--globals--colors--black-000: #00000000; --c--globals--colors--black-000: #1B1C1D00;
--c--globals--colors--black-050: #0000000D; --c--globals--colors--black-050: #1B1C1D0D;
--c--globals--colors--black-100: #0000001A; --c--globals--colors--black-100: #1B1C1D1A;
--c--globals--colors--black-150: #00000026; --c--globals--colors--black-150: #1B1C1D26;
--c--globals--colors--black-200: #00000033; --c--globals--colors--black-200: #1B1C1D33;
--c--globals--colors--black-250: #00000040; --c--globals--colors--black-250: #1B1C1D40;
--c--globals--colors--black-300: #0000004D; --c--globals--colors--black-300: #1B1C1D4D;
--c--globals--colors--black-350: #00000059; --c--globals--colors--black-350: #1B1C1D59;
--c--globals--colors--black-400: #00000066; --c--globals--colors--black-400: #1B1C1D66;
--c--globals--colors--black-450: #00000073; --c--globals--colors--black-450: #1B1C1D73;
--c--globals--colors--black-500: #00000080; --c--globals--colors--black-500: #1B1C1D80;
--c--globals--colors--black-550: #0000008C; --c--globals--colors--black-550: #1B1C1D8C;
--c--globals--colors--black-600: #00000099; --c--globals--colors--black-600: #1B1C1D99;
--c--globals--colors--black-650: #000000A6; --c--globals--colors--black-650: #1B1C1DA6;
--c--globals--colors--black-700: #000000B3; --c--globals--colors--black-700: #1B1C1DB2;
--c--globals--colors--black-750: #000000BF; --c--globals--colors--black-750: #1B1C1DBF;
--c--globals--colors--black-800: #000000CC; --c--globals--colors--black-800: #1B1C1DCC;
--c--globals--colors--black-850: #000000D9; --c--globals--colors--black-850: #1B1C1DD9;
--c--globals--colors--black-900: #000000E6; --c--globals--colors--black-900: #1B1C1DE5;
--c--globals--colors--black-950: #000000F2; --c--globals--colors--black-950: #101112F2;
--c--globals--colors--white-000: #FFFFFF; --c--globals--colors--white-000: #F7F8F800;
--c--globals--colors--white-050: #FFFFFF0D; --c--globals--colors--white-050: #F7F8F80D;
--c--globals--colors--white-100: #FFFFFF1A; --c--globals--colors--white-100: #F7F8F81A;
--c--globals--colors--white-150: #FFFFFF26; --c--globals--colors--white-150: #F7F8F826;
--c--globals--colors--white-200: #FFFFFF33; --c--globals--colors--white-200: #F7F8F833;
--c--globals--colors--white-250: #FFFFFF40; --c--globals--colors--white-250: #F7F8F840;
--c--globals--colors--white-300: #FFFFFF4D; --c--globals--colors--white-300: #F7F8F84D;
--c--globals--colors--white-350: #FFFFFF59; --c--globals--colors--white-350: #F7F8F859;
--c--globals--colors--white-400: #FFFFFF66; --c--globals--colors--white-400: #F7F8F866;
--c--globals--colors--white-450: #FFFFFF73; --c--globals--colors--white-450: #F7F8F873;
--c--globals--colors--white-500: #FFFFFF80; --c--globals--colors--white-500: #F7F8F880;
--c--globals--colors--white-550: #FFFFFF8C; --c--globals--colors--white-550: #F7F8F88C;
--c--globals--colors--white-600: #FFFFFF99; --c--globals--colors--white-600: #F7F8F899;
--c--globals--colors--white-650: #FFFFFFA6; --c--globals--colors--white-650: #F7F8F8A6;
--c--globals--colors--white-700: #FFFFFFB3; --c--globals--colors--white-700: #F7F8F8B2;
--c--globals--colors--white-750: #FFFFFFBF; --c--globals--colors--white-750: #F7F8F8BF;
--c--globals--colors--white-800: #FFFFFFCC; --c--globals--colors--white-800: #F7F8F8CC;
--c--globals--colors--white-850: #FFFFFFD9; --c--globals--colors--white-850: #F7F8F8D9;
--c--globals--colors--white-900: #FFFFFFE6; --c--globals--colors--white-900: #F7F8F8E5;
--c--globals--colors--white-950: #FFFFFFF2; --c--globals--colors--white-950: #F7F8F8F2;
--c--globals--colors--white-975: #F7F8F8F9;
--c--globals--transitions--ease-in: cubic-bezier(0.32, 0, 0.67, 0); --c--globals--transitions--ease-in: cubic-bezier(0.32, 0, 0.67, 0);
--c--globals--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1); --c--globals--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1);
--c--globals--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); --c--globals--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
@@ -553,7 +554,7 @@ describe("Cunningham Bin", () => {
--c--globals--spacings--5xl: 4.5rem; --c--globals--spacings--5xl: 4.5rem;
--c--globals--spacings--6xl: 6rem; --c--globals--spacings--6xl: 6rem;
--c--globals--spacings--7xl: 7.5rem; --c--globals--spacings--7xl: 7.5rem;
--c--globals--breakpoints--xs: 0; --c--globals--breakpoints--xs: 0px;
--c--globals--breakpoints--sm: 576px; --c--globals--breakpoints--sm: 576px;
--c--globals--breakpoints--md: 768px; --c--globals--breakpoints--md: 768px;
--c--globals--breakpoints--lg: 992px; --c--globals--breakpoints--lg: 992px;
@@ -637,9 +638,8 @@ describe("Cunningham Bin", () => {
--c--contextuals--background--palette--gray--primary: var(--c--globals--colors--gray-500); --c--contextuals--background--palette--gray--primary: var(--c--globals--colors--gray-500);
--c--contextuals--background--palette--gray--secondary: var(--c--globals--colors--gray-350); --c--contextuals--background--palette--gray--secondary: var(--c--globals--colors--gray-350);
--c--contextuals--background--palette--gray--tertiary: var(--c--globals--colors--gray-150); --c--contextuals--background--palette--gray--tertiary: var(--c--globals--colors--gray-150);
--c--contextuals--background--text--primary: var(--c--globals--colors--black-050); --c--contextuals--content--logo1: var(--c--globals--colors--logo-1-light);
--c--contextuals--content--logo1: var(--c--globals--colors--logo-1); --c--contextuals--content--logo2: var(--c--globals--colors--logo-2-light);
--c--contextuals--content--logo2: var(--c--globals--colors--logo-2);
--c--contextuals--content--semantic--contextual--primary: var(--c--globals--colors--white-950); --c--contextuals--content--semantic--contextual--primary: var(--c--globals--colors--white-950);
--c--contextuals--content--semantic--overlay--primary: var(--c--globals--colors--white-950); --c--contextuals--content--semantic--overlay--primary: var(--c--globals--colors--white-950);
--c--contextuals--content--semantic--brand--primary: var(--c--globals--colors--brand-700); --c--contextuals--content--semantic--brand--primary: var(--c--globals--colors--brand-700);
@@ -704,10 +704,10 @@ describe("Cunningham Bin", () => {
--c--theme--colors--primary: typescript; --c--theme--colors--primary: typescript;
} }
.cunningham-theme--dark{ .cunningham-theme--dark{
--c--globals--colors--logo-1: #377FDB; --c--globals--colors--logo-1-light: #377FDB;
--c--globals--colors--logo-2: #377FDB; --c--globals--colors--logo-2-light: #377FDB;
--c--globals--colors--logo-1-dark: #95ABFF; --c--globals--colors--logo-1-dark: #C1D6F2;
--c--globals--colors--logo-2-dark: #95ABFF; --c--globals--colors--logo-2-dark: #C1D6F2;
--c--globals--colors--brand-050: #EAF1FB; --c--globals--colors--brand-050: #EAF1FB;
--c--globals--colors--brand-100: #D5E4F7; --c--globals--colors--brand-100: #D5E4F7;
--c--globals--colors--brand-150: #C0D6F4; --c--globals--colors--brand-150: #C0D6F4;
@@ -996,46 +996,47 @@ describe("Cunningham Bin", () => {
--c--globals--colors--pink-850: #332028; --c--globals--colors--pink-850: #332028;
--c--globals--colors--pink-900: #24181D; --c--globals--colors--pink-900: #24181D;
--c--globals--colors--pink-950: #160F12; --c--globals--colors--pink-950: #160F12;
--c--globals--colors--black-000: #00000000; --c--globals--colors--black-000: #1B1C1D00;
--c--globals--colors--black-050: #0000000D; --c--globals--colors--black-050: #1B1C1D0D;
--c--globals--colors--black-100: #0000001A; --c--globals--colors--black-100: #1B1C1D1A;
--c--globals--colors--black-150: #00000026; --c--globals--colors--black-150: #1B1C1D26;
--c--globals--colors--black-200: #00000033; --c--globals--colors--black-200: #1B1C1D33;
--c--globals--colors--black-250: #00000040; --c--globals--colors--black-250: #1B1C1D40;
--c--globals--colors--black-300: #0000004D; --c--globals--colors--black-300: #1B1C1D4D;
--c--globals--colors--black-350: #00000059; --c--globals--colors--black-350: #1B1C1D59;
--c--globals--colors--black-400: #00000066; --c--globals--colors--black-400: #1B1C1D66;
--c--globals--colors--black-450: #00000073; --c--globals--colors--black-450: #1B1C1D73;
--c--globals--colors--black-500: #00000080; --c--globals--colors--black-500: #1B1C1D80;
--c--globals--colors--black-550: #0000008C; --c--globals--colors--black-550: #1B1C1D8C;
--c--globals--colors--black-600: #00000099; --c--globals--colors--black-600: #1B1C1D99;
--c--globals--colors--black-650: #000000A6; --c--globals--colors--black-650: #1B1C1DA6;
--c--globals--colors--black-700: #000000B3; --c--globals--colors--black-700: #1B1C1DB2;
--c--globals--colors--black-750: #000000BF; --c--globals--colors--black-750: #1B1C1DBF;
--c--globals--colors--black-800: #000000CC; --c--globals--colors--black-800: #1B1C1DCC;
--c--globals--colors--black-850: #000000D9; --c--globals--colors--black-850: #1B1C1DD9;
--c--globals--colors--black-900: #000000E6; --c--globals--colors--black-900: #1B1C1DE5;
--c--globals--colors--black-950: #000000F2; --c--globals--colors--black-950: #101112F2;
--c--globals--colors--white-000: #FFFFFF; --c--globals--colors--white-000: #F7F8F800;
--c--globals--colors--white-050: #FFFFFF0D; --c--globals--colors--white-050: #F7F8F80D;
--c--globals--colors--white-100: #FFFFFF1A; --c--globals--colors--white-100: #F7F8F81A;
--c--globals--colors--white-150: #FFFFFF26; --c--globals--colors--white-150: #F7F8F826;
--c--globals--colors--white-200: #FFFFFF33; --c--globals--colors--white-200: #F7F8F833;
--c--globals--colors--white-250: #FFFFFF40; --c--globals--colors--white-250: #F7F8F840;
--c--globals--colors--white-300: #FFFFFF4D; --c--globals--colors--white-300: #F7F8F84D;
--c--globals--colors--white-350: #FFFFFF59; --c--globals--colors--white-350: #F7F8F859;
--c--globals--colors--white-400: #FFFFFF66; --c--globals--colors--white-400: #F7F8F866;
--c--globals--colors--white-450: #FFFFFF73; --c--globals--colors--white-450: #F7F8F873;
--c--globals--colors--white-500: #FFFFFF80; --c--globals--colors--white-500: #F7F8F880;
--c--globals--colors--white-550: #FFFFFF8C; --c--globals--colors--white-550: #F7F8F88C;
--c--globals--colors--white-600: #FFFFFF99; --c--globals--colors--white-600: #F7F8F899;
--c--globals--colors--white-650: #FFFFFFA6; --c--globals--colors--white-650: #F7F8F8A6;
--c--globals--colors--white-700: #FFFFFFB3; --c--globals--colors--white-700: #F7F8F8B2;
--c--globals--colors--white-750: #FFFFFFBF; --c--globals--colors--white-750: #F7F8F8BF;
--c--globals--colors--white-800: #FFFFFFCC; --c--globals--colors--white-800: #F7F8F8CC;
--c--globals--colors--white-850: #FFFFFFD9; --c--globals--colors--white-850: #F7F8F8D9;
--c--globals--colors--white-900: #FFFFFFE6; --c--globals--colors--white-900: #F7F8F8E5;
--c--globals--colors--white-950: #FFFFFFF2; --c--globals--colors--white-950: #F7F8F8F2;
--c--globals--colors--white-975: #F7F8F8F9;
--c--globals--transitions--ease-in: cubic-bezier(0.32, 0, 0.67, 0); --c--globals--transitions--ease-in: cubic-bezier(0.32, 0, 0.67, 0);
--c--globals--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1); --c--globals--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1);
--c--globals--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); --c--globals--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
@@ -1093,7 +1094,7 @@ describe("Cunningham Bin", () => {
--c--globals--spacings--5xl: 4.5rem; --c--globals--spacings--5xl: 4.5rem;
--c--globals--spacings--6xl: 6rem; --c--globals--spacings--6xl: 6rem;
--c--globals--spacings--7xl: 7.5rem; --c--globals--spacings--7xl: 7.5rem;
--c--globals--breakpoints--xs: 0; --c--globals--breakpoints--xs: 0px;
--c--globals--breakpoints--sm: 576px; --c--globals--breakpoints--sm: 576px;
--c--globals--breakpoints--md: 768px; --c--globals--breakpoints--md: 768px;
--c--globals--breakpoints--lg: 992px; --c--globals--breakpoints--lg: 992px;

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import { buildRefs } from "./index"; import { buildRefs, getThemesFromGlobals } from "./index";
describe("buildRefs", () => { describe("buildRefs", () => {
it("should replace raw values by ref keys", () => { it("should replace raw values by ref keys", () => {
@@ -19,3 +19,49 @@ describe("buildRefs", () => {
}); });
}); });
}); });
describe("getThemesFromGlobals", () => {
it("should return a theme from a set of globals", () => {
const themes = getThemesFromGlobals({
colors: { "brand-500": "blue" },
font: { families: { base: "Comic Sans MS" } },
});
expect(Object.keys(themes)).toEqual(["light", "dark"]);
expect(themes.light.globals.colors["brand-500"]).toEqual("blue");
expect(themes.dark.globals.colors["brand-500"]).toEqual("blue");
expect(themes.light.globals.font.families.base).toEqual("Comic Sans MS");
expect(themes.dark.globals.font.families.base).toEqual("Comic Sans MS");
expect(themes.light.contextuals).toBeDefined();
expect(themes.dark.contextuals).toBeDefined();
});
it("should allow to prefix the theme names", () => {
const themes = getThemesFromGlobals({}, { prefix: "custom" });
expect(Object.keys(themes)).toEqual(["custom-light", "custom-dark"]);
});
it("should allow to get theme for a subset of variants", () => {
const themes = getThemesFromGlobals({}, { variants: ["light"] });
expect(Object.keys(themes)).toEqual(["light"]);
});
it("should allow to override/extend themes", () => {
const themes = getThemesFromGlobals(
{},
{
overrides: {
components: {
button: {
"font-family": "Papyrus",
},
},
},
},
);
expect(themes.light.components.button["font-family"]).toEqual("Papyrus");
});
});

View File

@@ -1,4 +1,6 @@
import { tokens } from "./cunningham-tokens"; import { tokens } from "./cunningham-tokens";
import { buildRefs } from "./utils/buildRefs";
import { getThemesFromGlobals } from "./utils/getThemesFromGlobals";
export type Configuration = typeof tokens; export type Configuration = typeof tokens;
export type DefaultTokens = (typeof tokens)["themes"]["default"]; export type DefaultTokens = (typeof tokens)["themes"]["default"];
@@ -6,38 +8,7 @@ export type DefaultTokens = (typeof tokens)["themes"]["default"];
export const defaultTokens = tokens.themes.default; export const defaultTokens = tokens.themes.default;
export const defaultThemes = tokens.themes; export const defaultThemes = tokens.themes;
/**
* Transform such object:
* {
* theme: {
* colors: {
* "primary-500": "blue"
* }
* }
* }
*
* to:
* {
* theme: {
* colors: {
* "primary-500": "ref(theme.colors.primary-500)"
* }
* }
* }
* @param tokens_
*/
export const buildRefs = <T extends Object>(tokens_: T): T => {
const buildRefsAux = (upperKey: string, subTokens: any) => {
if (typeof subTokens === "object") {
const obj: any = {};
Object.entries(subTokens).forEach(([key, value]) => {
obj[key] = buildRefsAux((upperKey ? upperKey + "." : "") + key, value);
});
return obj;
}
return "ref(" + upperKey + ")";
};
return buildRefsAux("", tokens_);
};
export const defaultTokenRefs = buildRefs(defaultTokens); export const defaultTokenRefs = buildRefs(defaultTokens);
export { buildRefs };
export { getThemesFromGlobals };

View File

@@ -0,0 +1,33 @@
/**
* Transform such object:
* {
* theme: {
* colors: {
* "primary-500": "blue"
* }
* }
* }
*
* to:
* {
* theme: {
* colors: {
* "primary-500": "ref(theme.colors.primary-500)"
* }
* }
* }
* @param tokens_
*/
export const buildRefs = <T extends Object>(tokens_: T): T => {
const buildRefsAux = (upperKey: string, subTokens: any) => {
if (typeof subTokens === "object") {
const obj: any = {};
Object.entries(subTokens).forEach(([key, value]) => {
obj[key] = buildRefsAux((upperKey ? upperKey + "." : "") + key, value);
});
return obj;
}
return "ref(" + upperKey + ")";
};
return buildRefsAux("", tokens_);
};

View File

@@ -0,0 +1,61 @@
// deepmerge is not available as a module, so we need to import it as a commonjs module...
import deepmerge = require("deepmerge");
import {
contextualDefaultTokens,
contextualDarkTokens,
globals as defaultGlobals,
} from "../cunningham";
type GlobalTokens = typeof defaultGlobals;
type PartialExtendableNested<T> = {
[K in keyof T]?: T[K] extends object ? PartialExtendableNested<T[K]> : T[K];
} & Record<PropertyKey, any>;
const THEME_VARIANTS = ["light", "dark"] as const;
type ThemeVariant = (typeof THEME_VARIANTS)[number];
const CONTEXTUAL_TOKENS_MAP = {
light: contextualDefaultTokens,
dark: contextualDarkTokens,
};
interface Options {
prefix?: string;
variants?: readonly ThemeVariant[];
overrides?: Record<string, unknown>;
}
/**
* Generates theme objects from global tokens and optional overrides.
*
* @param globals - A partial global tokens object.
* @param options - Additional options for generating themes.
* @param options.prefix - Optional prefix for the theme keys.
* @param options.variants - Theme variants to generate (e.g., ['light', 'dark']).
* @param options.overrides - Optional overrides/extensions to apply to the generated themes.
* @returns An object mapping each theme variant (with optional prefix) to its corresponding tokens.
*/
export const getThemesFromGlobals = (
globals: PartialExtendableNested<GlobalTokens> = {},
options: Options = {},
) => {
const variants = options.variants || THEME_VARIANTS;
return variants.reduce(
(themes, variant) => {
const variantKey = options.prefix
? `${options.prefix}-${variant}`
: variant;
themes[variantKey] = deepmerge(
{
globals,
contextuals: CONTEXTUAL_TOKENS_MAP[variant],
},
options.overrides || {},
);
return themes;
},
{} as Record<string, any>,
);
};