diff --git a/.changeset/yellow-glasses-tie.md b/.changeset/yellow-glasses-tie.md new file mode 100644 index 0000000..2cdcdfa --- /dev/null +++ b/.changeset/yellow-glasses-tie.md @@ -0,0 +1,8 @@ +--- +"@hebilicious/cssforge": minor +--- + +# Conditions and variables access + +Add the posibility to add conditions for colors modules. This is a breaking change for the +configuration format. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bd74917..7015975 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,3 +34,16 @@ jobs: - name: Publish dry run run: deno publish --dry-run + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Run tests + run: deno test -A diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7960d10 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# Css Forge Library + +This is a library for generating CSS variables from design tokens. This library uses Deno +and TypeScript. + +## Creating new features and modules + +Make sure to follow the established conventions for modules in `src/modules`. Make sure to +add tests following the pattern in the `tests` folder. + +## Dependencies + +Ask permissions before adding new dependencies. Always prefer using features from the +standard library or writing code from first principles. + +## Updating the README.md + +To update the README, use the `deno task readme:update` command. There is a special syntax +in the README that generates the codeblocks. For a comment like this: + + + + + +It will generate a codeblock below. Therefore, these comments are the source of truth and +should never be deleted. But they should be updated if the source code changes. diff --git a/README.md b/README.md index 07e3e35..afa37b0 100644 --- a/README.md +++ b/README.md @@ -39,21 +39,27 @@ For programmatic usage, you can install CSS Forge as a dependency: # Using npm npx jsr add @hebilicious/cssforge -# Using Deno -deno install jsr:@hebilicious/cssforge +# Using pnpm (10.9 +) +pnpm i jsr:@hebilicious/cssforge ``` -For CLI usage, you can also run CSS Forge directly from JSR or install it globally: +Then to run CSS forge, add the following script to your `package.json` or `deno.json` : -```bash -# Run directly from jsr with npx -npx jsr run @hebilicious/cssforge/cli +```json +{ + "scripts": { + "cssforge": "node node_modules/@hebilicious/cssforge/src/cli" + } +} +``` -# Run directly from jsr with deno -deno run -A jsr:@hebilicious/cssforge/cli +then run : -# Or install globally -deno install -A -n cssforge jsr:@hebilicious/cssforge/cli +```bash +#npm +npm run cssforge +#pnpm +pnpm run cssforge ``` ## Quick Start @@ -96,13 +102,19 @@ export default defineConfig({ palette: { value: { coral: { - 100: { hex: "#FF7F50" }, + value: { + 100: { hex: "#FF7F50" }, + }, }, mint: { - 100: { hex: "#4ADE80" }, + value: { + 100: { hex: "#4ADE80" }, + }, }, indigo: { - 100: { hex: "#4F46E5" }, + value: { + 100: { hex: "#4F46E5" }, + }, }, }, }, @@ -112,7 +124,7 @@ export default defineConfig({ 2. Run CSS Forge with the CLI : -If you're using a package.json, you can add the follwing into your scripts : +If you're using a package.json, add the follwing to your scripts : ```json { @@ -163,13 +175,16 @@ For example, you can import as a layer : } ``` +> !IMPORTANT Do not manually edit the generated CSS file, edit the configuration file +> instead and regenerate. + 4. Use the generated css in your JS/TS : ```typescript import { cssForge } from "./.cssforge/output.ts"; // Use like this anywhere : -//`cssForge.colors.palette.value.basic.white` is fully typed { key: --myKey, value: white, variable: --key: white } +//`cssForge.colors.palette.basic.white` is fully typed { key: --myKey, value: white, variable: --key: white } export { cssForge }; ``` @@ -187,12 +202,23 @@ export default defineConfig({ palette: { value: { simple: { - white: "oklch(100% 0 0)", - black: "#000", - green: { rgb: [0, 255, 0] }, - blue: { hsl: [240, 100, 50] }, - violet: { oklch: "oklch(0.7 0.2 270)" }, - red: { hex: "#FF0000" }, + value: { + white: "oklch(100% 0 0)", + black: "#000", + green: { rgb: [0, 255, 0] }, + blue: { hsl: [240, 100, 50] }, + violet: { oklch: "oklch(0.7 0.2 270)" }, + red: { hex: "#FF0000" }, + }, + }, + another: { + value: { + yellow: { hex: "#FFFF00" }, + cyan: { hex: "#00FFFF" }, + }, + settings: { + condition: ".Another", + }, }, }, }, @@ -203,8 +229,8 @@ export default defineConfig({ primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "palette.value.simple.white", - "c2": "palette.value.simple.green", + "c1": "palette.simple.white", + "c2": "palette.simple.green", }, }, }, @@ -212,19 +238,36 @@ export default defineConfig({ }, }, theme: { - value: { - light: { + light: { + value: { + background: { + value: { + primary: "var(--1)", + secondary: "var(--2)", + }, + variables: { + 1: "palette.simple.white", + 2: "gradients.white-green.primary", + }, + }, + }, + }, + dark: { + value: { background: { value: { primary: "var(--1)", secondary: "var(--2)", }, variables: { - 1: "palette.value.simple.white", - 2: "gradients.value.white-green", //Reference the color name directly. + 1: "palette.another.yellow", + 2: "palette.another.cyan", }, }, }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, }, }, }, @@ -237,12 +280,23 @@ export default defineConfig({ palette: { value: { simple: { - white: "oklch(100% 0 0)", - black: "#000", - green: { rgb: [0, 255, 0] }, - blue: { hsl: [240, 100, 50] }, - violet: { oklch: "oklch(0.7 0.2 270)" }, - red: { hex: "#FF0000" }, + value: { + white: "oklch(100% 0 0)", + black: "#000", + green: { rgb: [0, 255, 0] }, + blue: { hsl: [240, 100, 50] }, + violet: { oklch: "oklch(0.7 0.2 270)" }, + red: { hex: "#FF0000" }, + }, + }, + another: { + value: { + yellow: { hex: "#FFFF00" }, + cyan: { hex: "#00FFFF" }, + }, + settings: { + condition: ".Another", + }, }, }, }, @@ -253,8 +307,8 @@ export default defineConfig({ primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "palette.value.simple.white", - "c2": "palette.value.simple.green", + "c1": "palette.simple.white", + "c2": "palette.simple.green", }, }, }, @@ -262,19 +316,36 @@ export default defineConfig({ }, }, theme: { - value: { - light: { + light: { + value: { + background: { + value: { + primary: "var(--1)", + secondary: "var(--2)", + }, + variables: { + 1: "palette.simple.white", + 2: "gradients.white-green", //Reference the color name directly. + }, + }, + }, + }, + dark: { + value: { background: { value: { primary: "var(--1)", secondary: "var(--2)", }, variables: { - 1: "palette.value.simple.white", - 2: "gradients.value.white-green", //Reference the color name directly. + 1: "palette.another.yellow", + 2: "palette.another.cyan", }, }, }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, }, }, }, @@ -288,20 +359,33 @@ This will generate the following CSS : :root { /*____ Colors ____*/ /* Palette */ + /* simple */ --palette-simple-white: oklch(100% 0 0); --palette-simple-black: oklch(0% 0 0); --palette-simple-green: oklch(86.644% 0.29483 142.49535); --palette-simple-blue: oklch(45.201% 0.31321 264.05202); --palette-simple-violet: oklch(70% 0.2 270); --palette-simple-red: oklch(62.796% 0.25768 29.23388); + .Another { + /* another */ + --palette-another-yellow: oklch(96.798% 0.21101 109.76924); + --palette-another-cyan: oklch(90.54% 0.15455 194.76896); + } /* Gradients */ --gradients-white-green-primary: linear-gradient( to right, var(--palette-simple-white), var(--palette-simple-green) ); + /* Themes */ /* Theme: light */ /* background */ + @media (prefers-color-scheme: dark) { + /* Theme: dark */ + /* background */ + --theme-dark-background-primary: var(--palette-another-yellow); + --theme-dark-background-secondary: var(--palette-another-cyan); + } } ``` @@ -508,22 +592,6 @@ This will generate the following CSS : -#### Referencing Fluid Spacing - -To reference fluid spacing, use the `@` symbol and the label of the scale; ie: -`spacing_fluid-base@xs`. Do not include the prefix in the reference. The labels follow the -following convention : - -- 3xs -- 2xs -- xs -- s -- m -- l -- xl -- 2xl -- 3xl - ### Typography Define your typography, with fluid typescales powered by @@ -689,22 +757,6 @@ This will generate the following CSS : -#### Referencing Fluid Typography - -To reference fluid typography, use the `@` symbol and the label of the scale; ie: -`typography_fluid.comicsans@a`. Do not include the prefix in the reference. The labels -follow the following convention : - -- 3xs -- 2xs -- xs -- s -- m -- l -- xl -- 2xl -- 3xl - ### Primitives More flexible than other types, primitives allow you to define any type of token by @@ -751,8 +803,8 @@ export default defineConfig({ }, variables: { "base": "typography_fluid.arial@m", - "2": "spacing.custom.size.value.2", - "3": "spacing.custom.size.value.3", + "2": "spacing.custom.size.2", + "3": "spacing.custom.size.3", }, }, }, @@ -803,8 +855,8 @@ export default defineConfig({ }, variables: { "base": "typography_fluid.arial@m", - "2": "spacing.custom.size.value.2", - "3": "spacing.custom.size.value.3", + "2": "spacing.custom.size.2", + "3": "spacing.custom.size.3", }, }, }, @@ -843,17 +895,77 @@ This will generate the following CSS : +## Referencing Variables + +### Basic Referencing + +To reference any variable, use the `.` notation to navigate through the schema without +using `.value`. + +For example, if an object has the following structure : + +```typescript +{ + spacing: { + custom: { + size: { + value: { + 1: "0.25rem", + }, + }, + }, + }, +} +``` + +The reference would be : `spacing.custom.size.1`, not `spacing.custom.size.value.1`. + +#### Referencing Fluid Spacing + +To reference fluid spacing, use the `@` symbol and the label of the scale; ie: +`spacing_fluid-base@xs`. Do not include the prefix in the reference. The labels follow the +following convention : + +- 3xs +- 2xs +- xs +- s +- m +- l +- xl +- 2xl +- 3xl + +### Referencing Fluid Typography + +To reference fluid typography, use the `@` symbol and the label of the scale; ie: +`typography_fluid.comicsans@a`. Do not include the prefix in the reference. The labels +follow the following convention : + +- 3xs +- 2xs +- xs +- s +- m +- l +- xl +- 2xl +- 3xl + ## CLI Usage +Assuming you have added the script to your `package.json`, you can run CSS Forge like this +: + ```bash # Basic usage -cssforge +npm run cssforge # Watch mode -cssforge --watch +npm run cssforge -- --watch # Custom paths and output -cssforge --config ./foo/bar/custom-path.ts --css ./dist/design-tokens.css --ts ./dist/design-tokens.ts --json ./dist/design-tokens.json --mode all +npm run cssforge --config ./foo/bar/custom-path.ts --css ./dist/design-tokens.css --ts ./dist/design-tokens.ts --json ./dist/design-tokens.json --mode all ``` ## Programmatic Usage @@ -867,10 +979,20 @@ import { generateCSS } from "jsr:@hebilicious/cssforge"; const css = generateCSS(config); ``` +## Agentic usage + +CSSForge is intentionally designed to be extremely simple and integrate well with various +agents, such as Github Copilot, Gemini, Claude Code ... While there's no documentation +yet, you can add the README file to the agent context directly. + +For most agents, this syntax works +`@https://raw.githubusercontent.com/Hebilicious/cssforge/refs/heads/main/README.md`. + ## Best Practices - **Version Control**: Commit your generated CSS files - **CSS Layers**: Use `@layer` to manage specificity +- **Config First**: Always edit the config file, never edit the generated files ## Examples diff --git a/deno.json b/deno.json index 9d0c951..8e686d8 100644 --- a/deno.json +++ b/deno.json @@ -15,6 +15,7 @@ "chokidar": "npm:chokidar@4.0.3", "@std/collections": "jsr:@std/collections@^1.1.3", "@std/assert": "jsr:@std/assert@^1.0.14", + "@std/testing": "jsr:@std/testing@^1.0.14", "colorjs.io": "npm:colorjs.io@0.5.2", "utopia-core": "npm:utopia-core@1.6.0", "mdbox": "npm:mdbox@0.1.1" @@ -28,6 +29,7 @@ "tasks": { "cli": "deno run -A ./src/cli.ts", "example:cli": "deno run -A ./src/cli.ts --prefix ./example/basic --mode css", - "readme:update": "deno run -A ./scripts/update-readme.ts" + "readme:update": "deno run -A ./scripts/update-readme.ts", + "update:snapshot": "deno test -A -- --update" } } diff --git a/deno.lock b/deno.lock index 1b29ffc..b741530 100644 --- a/deno.lock +++ b/deno.lock @@ -2,14 +2,21 @@ "version": "5", "specifiers": { "jsr:@std/assert@*": "1.0.9", - "jsr:@std/assert@^1.0.14": "1.0.14", + "jsr:@std/assert@^1.0.14": "1.0.15", + "jsr:@std/assert@^1.0.15": "1.0.15", + "jsr:@std/async@^1.0.15": "1.0.15", "jsr:@std/collections@^1.1.3": "1.1.3", + "jsr:@std/data-structures@^1.0.9": "1.0.9", "jsr:@std/fs@1": "1.0.19", - "jsr:@std/internal@^1.0.10": "1.0.10", + "jsr:@std/fs@^1.0.19": "1.0.19", + "jsr:@std/internal@^1.0.10": "1.0.12", + "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/internal@^1.0.5": "1.0.5", "jsr:@std/internal@^1.0.9": "1.0.10", "jsr:@std/path@1": "1.1.2", "jsr:@std/path@^1.1.1": "1.1.2", + "jsr:@std/path@^1.1.2": "1.1.2", + "jsr:@std/testing@^1.0.14": "1.0.16", "npm:@changesets/cli@*": "2.29.6", "npm:@types/node@*": "24.2.0", "npm:chokidar@4.0.3": "4.0.3", @@ -31,9 +38,21 @@ "jsr:@std/internal@^1.0.10" ] }, + "@std/assert@1.0.15": { + "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", + "dependencies": [ + "jsr:@std/internal@^1.0.12" + ] + }, + "@std/async@1.0.15": { + "integrity": "55d1d9d04f99403fe5730ab16bdcc3c47f658a6bf054cafb38a50f046238116e" + }, "@std/collections@1.1.3": { "integrity": "bf8b0818886df6a32b64c7d3b037a425111f28278d69fd0995aeb62777c986b0" }, + "@std/data-structures@1.0.9": { + "integrity": "033d6e17e64bf1f84a614e647c1b015fa2576ae3312305821e1a4cb20674bb4d" + }, "@std/fs@1.0.19": { "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", "dependencies": [ @@ -47,6 +66,9 @@ "@std/internal@1.0.10": { "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, "@std/path@1.0.8": { "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" }, @@ -55,6 +77,17 @@ "dependencies": [ "jsr:@std/internal@^1.0.10" ] + }, + "@std/testing@1.0.16": { + "integrity": "a917ffdeb5924c9be436dc78bc32e511760e14d3a96e49c607fc5ecca86d0092", + "dependencies": [ + "jsr:@std/assert@^1.0.15", + "jsr:@std/async", + "jsr:@std/data-structures", + "jsr:@std/fs@^1.0.19", + "jsr:@std/internal@^1.0.12", + "jsr:@std/path@^1.1.2" + ] } }, "npm": { @@ -669,6 +702,7 @@ "dependencies": [ "jsr:@std/assert@^1.0.14", "jsr:@std/collections@^1.1.3", + "jsr:@std/testing@^1.0.14", "npm:chokidar@4.0.3", "npm:citty@0.1.6", "npm:colorjs.io@0.5.2", diff --git a/example/basic/.cssforge/output.css b/example/basic/.cssforge/output.css index 25fe6df..17f0d1f 100644 --- a/example/basic/.cssforge/output.css +++ b/example/basic/.cssforge/output.css @@ -2,9 +2,29 @@ :root { /*____ Colors ____*/ /* Palette */ + /* coral */ --palette-coral-100: oklch(73.511% 0.16799 40.24666); + /* mint */ --palette-mint-100: oklch(80.035% 0.18206 151.71104); + /* indigo */ --palette-indigo-100: oklch(51.057% 0.23005 276.96564); + @media (prefers-color-scheme: dark) { + /* coral_dark */ + --palette-coral_dark-100: oklch(69.622% 0.19552 32.32143); + } + @media (prefers-color-scheme: dark) { + /* mint_dark */ + --palette-mint_dark-100: oklch(72.275% 0.19201 149.57934); + } + @media (prefers-color-scheme: dark) { + /* indigo_dark */ + --palette-indigo_dark-100: oklch(45.678% 0.21458 277.0229); + } + /* Themes */ + /* Theme: main */ + /* background */ + --theme-main-background-primary: var(--palette-coral-100); + --theme-main-background-secondary: var(--palette-mint-100); /*____ Spacing ____*/ --spacing_fluid-base-3xs: clamp(0.0625rem, -0.0417rem + 0.5208vw, 0.375rem); --spacing_fluid-base-2xs: clamp(0.125rem, -0.0833rem + 1.0417vw, 0.75rem); @@ -15,18 +35,26 @@ --spacing_fluid-base-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); --spacing_fluid-base-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); --spacing_fluid-base-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); + --spacing_fluid-base-3xs-2xs: clamp(0.0625rem, -0.1667rem + 1.1458vw, 0.75rem); + --spacing_fluid-base-2xs-xs: clamp(0.125rem, -0.2083rem + 1.6667vw, 1.125rem); + --spacing_fluid-base-xs-s: clamp(0.1875rem, -0.25rem + 2.1875vw, 1.5rem); + --spacing_fluid-base-s-m: clamp(0.25rem, -0.4167rem + 3.3333vw, 2.25rem); + --spacing_fluid-base-m-l: clamp(0.375rem, -0.5rem + 4.375vw, 3rem); + --spacing_fluid-base-l-xl: clamp(0.5rem, -0.8333rem + 6.6667vw, 4.5rem); + --spacing_fluid-base-xl-2xl: clamp(0.75rem, -1rem + 8.75vw, 6rem); + --spacing_fluid-base-2xl-3xl: clamp(1rem, -1.6667rem + 13.3333vw, 9rem); --spacing-size-1: 0.25rem; --spacing-size-2: 0.5rem; --spacing-size-3: 0.75rem; --spacing-size-4: 1rem; /*____ Typography ____*/ - --typography_fluid-font-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); - --typography_fluid-font-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); - --typography_fluid-font-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); - --typography_fluid-font-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); - --typography_fluid-font-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); - --typography_fluid-font-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); - --typography_fluid-font-sm: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); - --typography_fluid-font-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); - --typography_fluid-font-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem); + --typography_fluid-base-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); + --typography_fluid-base-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); + --typography_fluid-base-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); + --typography_fluid-base-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); + --typography_fluid-base-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); + --typography_fluid-base-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); + --typography_fluid-base-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); + --typography_fluid-base-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); + --typography_fluid-base-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem); } diff --git a/example/basic/cssforge.config.ts b/example/basic/cssforge.config.ts index f46b7b3..323e5a8 100644 --- a/example/basic/cssforge.config.ts +++ b/example/basic/cssforge.config.ts @@ -27,8 +27,8 @@ export default defineConfig( }, }, typography: { - font: { - fluid: { + fluid: { + base: { value: { minWidth: 320, minFontSize: 14, @@ -46,13 +46,60 @@ export default defineConfig( palette: { value: { coral: { - 100: { hex: "#FF7F50" }, + value: { + 100: { hex: "#FF7F50" }, + }, }, mint: { - 100: { hex: "#4ADE80" }, + value: { + 100: { hex: "#4ADE80" }, + }, }, indigo: { - 100: { hex: "#4F46E5" }, + value: { + 100: { hex: "#4F46E5" }, + }, + }, + // dark-mode overrides wrapped in a condition + coral_dark: { + value: { + 100: { hex: "#FF6347" }, + }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, + }, + mint_dark: { + value: { + 100: { hex: "#22C55E" }, + }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, + }, + indigo_dark: { + value: { + 100: { hex: "#4338CA" }, + }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, + }, + }, + }, + theme: { + main: { + value: { + background: { + value: { + primary: "var(--coral)", + secondary: "var(--mint)", + }, + variables: { + coral: "palette.coral.100", + mint: "palette.mint.100", + }, + }, }, }, }, diff --git a/src/config.ts b/src/config.ts index f0d43a7..2fa2432 100644 --- a/src/config.ts +++ b/src/config.ts @@ -79,8 +79,6 @@ export interface CSSForgeConfig { ); * ``` */ -export function defineConfig>( - config: C, -): C { +export function defineConfig>(config: C): C { return config as C; } diff --git a/src/lib.ts b/src/lib.ts index bc8e1e5..f3a3208 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -71,7 +71,11 @@ function resolveVariable( if (!colors) throw new Error("The colors object must be passed."); const result = colors.resolveMap.get(varPath); if (!result) { - throw new Error(`The color path ${varPath} could not be resolved.`); + throw new Error( + `The color path ${varPath} could not be resolved. Map contains ${ + Array.from(colors.resolveMap.keys()).map((key) => `\n${key}`) + }`, + ); } return result.key; } diff --git a/src/modules/colors.ts b/src/modules/colors.ts index c5dd85b..a9a0567 100644 --- a/src/modules/colors.ts +++ b/src/modules/colors.ts @@ -29,11 +29,40 @@ interface ColorVariants { [key: string]: ColorValueOrString; } +export interface WithCondition { + /** + * CSS condition to wrap variables in (e.g., ".MyClass", "@media (prefers-color-scheme: dark)") + */ + condition?: string; +} +/** + * Settings for palette colors, including optional conditions like media queries. + */ +export interface PaletteColorSettings extends WithCondition {} + +/** + * Settings for gradients, including optional conditions like media queries. + */ +export interface GradientSettings extends WithCondition {} + +/** + * Settings for themes, including optional conditions like media queries. + */ +export interface ThemeSettings extends WithCondition {} + +/** + * A single palette color configuration with its values and optional settings. + */ +export interface PaletteColorConfig { + value: ColorVariants; + settings?: PaletteColorSettings; +} + /** * A palette of colors, organized by name and variants. */ export interface ColorPalette { - [key: string]: ColorVariants; + [key: string]: PaletteColorConfig; } interface GradientDefinition { @@ -51,7 +80,7 @@ interface GradientValue { */ export interface Gradient { value: GradientValue; - settings?: unknown; + settings?: GradientSettings; } /** @@ -65,12 +94,21 @@ interface ColorInThemeValues { [key: string]: string; } -interface ColorTheme { - [key: string]: { - value: ColorInThemeValues; - variables?: Variables; - settings?: unknown; +interface ColorInTheme { + value: ColorInThemeValues; + variables?: Variables; + settings?: unknown; +} + +export interface ThemeConfig { + value: { + [colorName: string]: ColorInTheme; }; + settings?: ThemeSettings; +} + +export interface ColorTheme { + [themeName: string]: ThemeConfig; } /** @@ -95,10 +133,7 @@ export interface ColorConfig { * A collection of themes. */ theme?: { - value: { - [key: string]: ColorTheme; - }; - settings?: unknown; + [themeName: string]: ThemeConfig; }; } @@ -182,35 +217,78 @@ export function processColors(colors: ColorConfig): Output { const resolveMap: ResolveMap = new Map(); cssOutput.push(`/* Palette */`); const moduleKey = "palette"; - for (const [colorName, variants] of Object.entries(colors.palette.value)) { + + function conditionalBuilder( + settings: WithCondition | undefined, + initialComment: string, + ) { + const comments: string[] = []; + const vars: string[] = []; + + if (settings?.condition) { + comments.push(initialComment); + } else { + cssOutput.push(initialComment); + } + + return { + addComment(c: string) { + if (settings?.condition) comments.push(c); + else cssOutput.push(c); + }, + pushVariable(v: string) { + if (settings?.condition) vars.push(v); + else cssOutput.push(v); + }, + finalize() { + if (settings?.condition && vars.length > 0) { + cssOutput.push(`${settings.condition} {`); + cssOutput.push(...comments.map((c) => ` ${c}`)); + cssOutput.push(...vars.map((v) => ` ${v}`)); + cssOutput.push(`}`); + } + }, + }; + } + + for (const [colorName, colorConfig] of Object.entries(colors.palette.value)) { validateName(colorName); - for (const [variantId, colorValue] of Object.entries(variants)) { - try { + + try { + const handler = conditionalBuilder( + colorConfig.settings, + `/* ${colorName} */`, + ); + + for (const [variantId, colorValue] of Object.entries(colorConfig.value)) { validateName(variantId); const key = `--${moduleKey}-${colorName}-${variantId}`; const value = colorValueToOklch(colorValue); const variable = `${key}: ${value};`; - cssOutput.push(variable); + handler.pushVariable(variable); + resolveMap.set( - `${moduleKey}.value.${colorName}.${variantId}`, + `${moduleKey}.${colorName}.${variantId}`, { key, value, variable }, ); - } catch (error) { - console.error( - `Error processing color ${colorName}-${variantId}:`, - error, - ); } + + handler.finalize(); + } catch (error) { + console.error(`Error processing color ${colorName}:`, error); } } if (colors.gradients) { - cssOutput.push(`/* Gradients */`); + cssOutput.push(`/* Gradients */`); const moduleKey = "gradients"; const palette = { css: cssOutput.join("\n"), resolveMap }; + for (const [gradientName, gradient] of Object.entries(colors.gradients.value)) { validateName(gradientName); + const handler = conditionalBuilder(gradient.settings, `/* ${gradientName} */`); + for ( const [variantName, { value, variables }] of Object.entries( gradient.value, @@ -227,8 +305,10 @@ export function processColors(colors: ColorConfig): Output { const key = `--${moduleKey}-${gradientName}-${variantName}`; const variable = `${key}: ${gradientValue};`; - cssOutput.push(variable); - resolveMap.set(`${moduleKey}.value.${gradientName}.${variantName}`, { + + handler.pushVariable(variable); + + resolveMap.set(`${moduleKey}.${gradientName}.${variantName}`, { variable, key, value: gradientValue, @@ -241,49 +321,56 @@ export function processColors(colors: ColorConfig): Output { throw error; } } + + handler.finalize(); } } if (colors.theme) { + cssOutput.push(`/* Themes */`); const moduleKey = "theme"; - for (const [themeName, theme] of Object.entries(colors.theme.value)) { + const palette = { css: cssOutput.join("\n"), resolveMap }; + + for (const [themeName, themeConfig] of Object.entries(colors.theme)) { validateName(themeName); - cssOutput.push(`/* Theme: ${themeName} */`); - const palette = { css: cssOutput.join("\n"), resolveMap }; - for ( - const [colorName, { value: colorInThemeValues, variables }] of Object - .entries(theme) - ) { - validateName(colorName); - cssOutput.push(`/* ${colorName} */`); - try { + const handler = conditionalBuilder( + themeConfig.settings, + `/* Theme: ${themeName} */`, + ); + + try { + for (const [colorName, colorInTheme] of Object.entries(themeConfig.value)) { + validateName(colorName); + const colorComment = `/* ${colorName} */`; + handler.addComment(colorComment); + const resolvedMap = getResolvedVariablesMap({ - variables, + variables: colorInTheme.variables, colors: palette, }); - for ( - const [variantName, variantValue] of Object.entries( - colorInThemeValues, - ) - ) { + for (const [variantName, variantValue] of Object.entries(colorInTheme.value)) { validateName(variantName); - const resolvedValue = resolveValue({ map: resolvedMap, value: variantValue }); + const resolvedValue = resolveValue({ + map: resolvedMap, + value: variantValue as string, + }); const key = `--${moduleKey}-${themeName}-${colorName}-${variantName}`; const variable = `${key}: ${resolvedValue};`; - cssOutput.push(`${key}: ${resolvedValue};`); + + handler.pushVariable(variable); + resolveMap.set( - `${moduleKey}.value.${themeName}.${colorName}.${variantName}`, + `${moduleKey}.${themeName}.${colorName}.${variantName}`, { key, value: resolvedValue, variable }, ); } - } catch (error) { - console.error( - `Error processing color ${themeName}-${colorName}:`, - error, - ); } + + handler.finalize(); + } catch (error) { + console.error(`Error processing theme ${themeName}:`, error); } } } diff --git a/src/modules/primitive.ts b/src/modules/primitive.ts index 47ee552..20c766a 100644 --- a/src/modules/primitive.ts +++ b/src/modules/primitive.ts @@ -125,7 +125,7 @@ export function processPrimitives( const variable = `${key}: ${resolvedValue};`; cssOutput.push(variable); resolveMap.set( - `${moduleKey}.${primitiveName}.value.${variantName}.${propName}`, + `${moduleKey}.${primitiveName}.${variantName}.${propName}`, { key, value: resolvedValue, diff --git a/src/modules/spacing.ts b/src/modules/spacing.ts index 95466b6..3bbf018 100644 --- a/src/modules/spacing.ts +++ b/src/modules/spacing.ts @@ -115,7 +115,7 @@ export function processSpacing(spacing: SpacingConfig): Output { const varName = `--${moduleKey}-${scaleName}-${scaleKey}`; const variable = `${varName}: ${convertedValue};`; cssOutput.push(variable); - resolveMap.set(`${moduleKey}.custom.${scaleName}.value.${scaleKey}`, { + resolveMap.set(`${moduleKey}.custom.${scaleName}.${scaleKey}`, { variable, key: varName, value: convertedValue, diff --git a/src/modules/typography.ts b/src/modules/typography.ts index ad2c475..a5a4da3 100644 --- a/src/modules/typography.ts +++ b/src/modules/typography.ts @@ -110,7 +110,7 @@ export function processTypography(config: TypographyConfig): Output { const key = `--${moduleKey}-weight-${weightName}-${token}`; const variable = `${key}: ${weightValue};`; cssOutput.push(variable); - resolveMap.set(`${moduleKey}.weight.${weightName}.value.${token}`, { + resolveMap.set(`${moduleKey}.weight.${weightName}.${token}`, { variable, key, value: weightValue, diff --git a/tests/__snapshots__/colors.test.ts.snap b/tests/__snapshots__/colors.test.ts.snap new file mode 100644 index 0000000..b800da7 --- /dev/null +++ b/tests/__snapshots__/colors.test.ts.snap @@ -0,0 +1,376 @@ +export const snapshot = {}; + +snapshot[`processColors - converts hex to oklch 1`] = ` +"/* Palette */ +/* coral */ +--palette-coral-100: oklch(73.511% 0.16799 40.24666); +--palette-coral-200: oklch(69.622% 0.19552 32.32143);" +`; + +snapshot[`processColors - converts hex to oklch 2`] = ` +[ + [ + "palette.coral.100", + { + key: "--palette-coral-100", + value: "oklch(73.511% 0.16799 40.24666)", + variable: "--palette-coral-100: oklch(73.511% 0.16799 40.24666);", + }, + ], + [ + "palette.coral.200", + { + key: "--palette-coral-200", + value: "oklch(69.622% 0.19552 32.32143)", + variable: "--palette-coral-200: oklch(69.622% 0.19552 32.32143);", + }, + ], +] +`; + +snapshot[`processColors - handles different color formats 1`] = ` +"/* Palette */ +/* brand */ +--palette-brand-200: oklch(86.644% 0.29483 142.49535); +--palette-brand-300: oklch(45.201% 0.31321 264.05202); +--palette-brand-400: oklch(70% 0.2 270); +--palette-brand-default: oklch(62.796% 0.25768 29.23388);" +`; + +snapshot[`processColors - handles different color formats 2`] = ` +[ + [ + "palette.brand.200", + { + key: "--palette-brand-200", + value: "oklch(86.644% 0.29483 142.49535)", + variable: "--palette-brand-200: oklch(86.644% 0.29483 142.49535);", + }, + ], + [ + "palette.brand.300", + { + key: "--palette-brand-300", + value: "oklch(45.201% 0.31321 264.05202)", + variable: "--palette-brand-300: oklch(45.201% 0.31321 264.05202);", + }, + ], + [ + "palette.brand.400", + { + key: "--palette-brand-400", + value: "oklch(70% 0.2 270)", + variable: "--palette-brand-400: oklch(70% 0.2 270);", + }, + ], + [ + "palette.brand.default", + { + key: "--palette-brand-default", + value: "oklch(62.796% 0.25768 29.23388)", + variable: "--palette-brand-default: oklch(62.796% 0.25768 29.23388);", + }, + ], +] +`; + +snapshot[`processColors - handles string values 1`] = ` +"/* Palette */ +/* simple */ +--palette-simple-white: oklch(100% 0 0); +--palette-simple-black: oklch(0% 0 0);" +`; + +snapshot[`processColors - handles string values 2`] = ` +[ + [ + "palette.simple.white", + { + key: "--palette-simple-white", + value: "oklch(100% 0 0)", + variable: "--palette-simple-white: oklch(100% 0 0);", + }, + ], + [ + "palette.simple.black", + { + key: "--palette-simple-black", + value: "oklch(0% 0 0)", + variable: "--palette-simple-black: oklch(0% 0 0);", + }, + ], +] +`; + +snapshot[`processColors - handles themes 1`] = ` +"/* Palette */ +/* simple */ +--palette-simple-white: oklch(100% 0 0); +--palette-simple-black: oklch(0% 0 0); +/* Themes */ +/* Theme: light */ +/* background */ +--theme-light-background-primary: var(--palette-simple-white); +--theme-light-background-secondary: var(--palette-simple-black);" +`; + +snapshot[`processColors - handles themes 2`] = ` +[ + [ + "palette.simple.white", + { + key: "--palette-simple-white", + value: "oklch(100% 0 0)", + variable: "--palette-simple-white: oklch(100% 0 0);", + }, + ], + [ + "palette.simple.black", + { + key: "--palette-simple-black", + value: "oklch(0% 0 0)", + variable: "--palette-simple-black: oklch(0% 0 0);", + }, + ], + [ + "theme.light.background.primary", + { + key: "--theme-light-background-primary", + value: "var(--palette-simple-white)", + variable: "--theme-light-background-primary: var(--palette-simple-white);", + }, + ], + [ + "theme.light.background.secondary", + { + key: "--theme-light-background-secondary", + value: "var(--palette-simple-black)", + variable: "--theme-light-background-secondary: var(--palette-simple-black);", + }, + ], +] +`; + +snapshot[`processColors - handles transparency 1`] = ` +"/* Palette */ +/* alpha */ +--palette-alpha-softGray1: oklch(14.48% 0 0 / 12%); +--palette-alpha-softGray2: oklch(14.48% 0 0 / 24%);" +`; + +snapshot[`processColors - handles transparency 2`] = ` +[ + [ + "palette.alpha.softGray1", + { + key: "--palette-alpha-softGray1", + value: "oklch(14.48% 0 0 / 12%)", + variable: "--palette-alpha-softGray1: oklch(14.48% 0 0 / 12%);", + }, + ], + [ + "palette.alpha.softGray2", + { + key: "--palette-alpha-softGray2", + value: "oklch(14.48% 0 0 / 24%)", + variable: "--palette-alpha-softGray2: oklch(14.48% 0 0 / 24%);", + }, + ], +] +`; + +snapshot[`processColors - generates gradient with color variables 1`] = ` +"/* Palette */ +/* coral */ +--palette-coral-50: oklch(73.58% 0.16378 34.33822); +--palette-coral-90: oklch(73.341% 0.17338 44.64289); +--palette-coral-100: oklch(69.622% 0.19552 32.32143); +/* indigo */ +--palette-indigo-100: oklch(51.057% 0.23005 276.96564); +/* Gradients */ +/* orangeGradient */ +--gradients-orangeGradient-primary: linear-gradient(261.78deg, var(--palette-coral-50) 33.1%, var(--palette-coral-90) 56.3%, var(--palette-coral-100) 65.78%, var(--palette-indigo-100) 84.23%);" +`; + +snapshot[`processColors - generates gradient with color variables 2`] = ` +[ + [ + "palette.coral.50", + { + key: "--palette-coral-50", + value: "oklch(73.58% 0.16378 34.33822)", + variable: "--palette-coral-50: oklch(73.58% 0.16378 34.33822);", + }, + ], + [ + "palette.coral.90", + { + key: "--palette-coral-90", + value: "oklch(73.341% 0.17338 44.64289)", + variable: "--palette-coral-90: oklch(73.341% 0.17338 44.64289);", + }, + ], + [ + "palette.coral.100", + { + key: "--palette-coral-100", + value: "oklch(69.622% 0.19552 32.32143)", + variable: "--palette-coral-100: oklch(69.622% 0.19552 32.32143);", + }, + ], + [ + "palette.indigo.100", + { + key: "--palette-indigo-100", + value: "oklch(51.057% 0.23005 276.96564)", + variable: "--palette-indigo-100: oklch(51.057% 0.23005 276.96564);", + }, + ], + [ + "gradients.orangeGradient.primary", + { + key: "--gradients-orangeGradient-primary", + value: "linear-gradient(261.78deg, var(--palette-coral-50) 33.1%, var(--palette-coral-90) 56.3%, var(--palette-coral-100) 65.78%, var(--palette-indigo-100) 84.23%)", + variable: "--gradients-orangeGradient-primary: linear-gradient(261.78deg, var(--palette-coral-50) 33.1%, var(--palette-coral-90) 56.3%, var(--palette-coral-100) 65.78%, var(--palette-indigo-100) 84.23%);", + }, + ], +] +`; + +snapshot[`processColors - handles themes referencing gradients 1`] = ` +"/* Palette */ +/* coral */ +--palette-coral-50: oklch(73.58% 0.16378 34.33822); +/* Gradients */ +/* orangeGradient */ +--gradients-orangeGradient-primary: linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50)); +/* Themes */ +/* Theme: light */ +/* background */ +--theme-light-background-primary: var(--gradients-orangeGradient-primary);" +`; + +snapshot[`processColors - handles themes referencing gradients 2`] = ` +[ + [ + "palette.coral.50", + { + key: "--palette-coral-50", + value: "oklch(73.58% 0.16378 34.33822)", + variable: "--palette-coral-50: oklch(73.58% 0.16378 34.33822);", + }, + ], + [ + "gradients.orangeGradient.primary", + { + key: "--gradients-orangeGradient-primary", + value: "linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50))", + variable: "--gradients-orangeGradient-primary: linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50));", + }, + ], + [ + "theme.light.background.primary", + { + key: "--theme-light-background-primary", + value: "var(--gradients-orangeGradient-primary)", + variable: "--theme-light-background-primary: var(--gradients-orangeGradient-primary);", + }, + ], +] +`; + +snapshot[`processColors - handles gradients with conditions 1`] = ` +"/* Palette */ +/* coral */ +--palette-coral-50: oklch(73.58% 0.16378 34.33822); +/* Gradients */ +@media (prefers-color-scheme: dark) { + /* orangeGradient */ + --gradients-orangeGradient-primary: linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50)); +}" +`; + +snapshot[`processColors - handles gradients with conditions 2`] = ` +[ + [ + "palette.coral.50", + { + key: "--palette-coral-50", + value: "oklch(73.58% 0.16378 34.33822)", + variable: "--palette-coral-50: oklch(73.58% 0.16378 34.33822);", + }, + ], + [ + "gradients.orangeGradient.primary", + { + key: "--gradients-orangeGradient-primary", + value: "linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50))", + variable: "--gradients-orangeGradient-primary: linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50));", + }, + ], +] +`; + +snapshot[`processColors - handles palette colors with conditions 1`] = ` +"/* Palette */ +/* background */ +--palette-background-light: oklch(100% 0 0); +@media (prefers-color-scheme: dark) { + /* backgroundDark */ + --palette-backgroundDark-dark: oklch(0% 0 0); +}" +`; + +snapshot[`processColors - handles palette colors with conditions 2`] = ` +[ + [ + "palette.background.light", + { + key: "--palette-background-light", + value: "oklch(100% 0 0)", + variable: "--palette-background-light: oklch(100% 0 0);", + }, + ], + [ + "palette.backgroundDark.dark", + { + key: "--palette-backgroundDark-dark", + value: "oklch(0% 0 0)", + variable: "--palette-backgroundDark-dark: oklch(0% 0 0);", + }, + ], +] +`; + +snapshot[`processColors - handles themes with class condition 1`] = ` +"/* Palette */ +/* base */ +--palette-base-primary: oklch(73.511% 0.16799 40.24666); +/* Themes */ +.dark-theme { + /* Theme: dark */ + /* background */ + --theme-dark-background-primary: var(--palette-base-primary); +}" +`; + +snapshot[`processColors - handles themes with class condition 2`] = ` +[ + [ + "palette.base.primary", + { + key: "--palette-base-primary", + value: "oklch(73.511% 0.16799 40.24666)", + variable: "--palette-base-primary: oklch(73.511% 0.16799 40.24666);", + }, + ], + [ + "theme.dark.background.primary", + { + key: "--theme-dark-background-primary", + value: "var(--palette-base-primary)", + variable: "--theme-dark-background-primary: var(--palette-base-primary);", + }, + ], +] +`; diff --git a/tests/__snapshots__/primitive.test.ts.snap b/tests/__snapshots__/primitive.test.ts.snap new file mode 100644 index 0000000..9b7d113 --- /dev/null +++ b/tests/__snapshots__/primitive.test.ts.snap @@ -0,0 +1,172 @@ +export const snapshot = {}; + +snapshot[`processPrimitives - processes button with variables 1`] = ` +"/* button */ +--button-small-width: 120px; +--button-small-height: 40px; +--button-small-fontSize: var(--typography_fluid-arial-foo-m); +--button-small-radius: 8px; +--button-small-padding: var(--spacing-size-2) var(--spacing-size-3);" +`; + +snapshot[`processPrimitives - processes button with variables 2`] = ` +[ + [ + "primitives.button.small.width", + { + key: "--button-small-width", + value: "120px", + variable: "--button-small-width: 120px;", + }, + ], + [ + "primitives.button.small.height", + { + key: "--button-small-height", + value: "40px", + variable: "--button-small-height: 40px;", + }, + ], + [ + "primitives.button.small.fontSize", + { + key: "--button-small-fontSize", + value: "var(--typography_fluid-arial-foo-m)", + variable: "--button-small-fontSize: var(--typography_fluid-arial-foo-m);", + }, + ], + [ + "primitives.button.small.radius", + { + key: "--button-small-radius", + value: "8px", + variable: "--button-small-radius: 8px;", + }, + ], + [ + "primitives.button.small.padding", + { + key: "--button-small-padding", + value: "var(--spacing-size-2) var(--spacing-size-3)", + variable: "--button-small-padding: var(--spacing-size-2) var(--spacing-size-3);", + }, + ], +] +`; + +snapshot[`processPrimitives - processes buttons with settings 1`] = ` +"/* button */ +--button-small-width: 120px; +--button-small-height: 40px; +--button-small-radius: 8px; +--button-big-width: 15rem; +--button-big-height: 5rem; +--button-big-radius: 1rem;" +`; + +snapshot[`processPrimitives - processes buttons with settings 2`] = ` +[ + [ + "primitives.button.small.width", + { + key: "--button-small-width", + value: "120px", + variable: "--button-small-width: 120px;", + }, + ], + [ + "primitives.button.small.height", + { + key: "--button-small-height", + value: "40px", + variable: "--button-small-height: 40px;", + }, + ], + [ + "primitives.button.small.radius", + { + key: "--button-small-radius", + value: "8px", + variable: "--button-small-radius: 8px;", + }, + ], + [ + "primitives.button.big.width", + { + key: "--button-big-width", + value: "15rem", + variable: "--button-big-width: 15rem;", + }, + ], + [ + "primitives.button.big.height", + { + key: "--button-big-height", + value: "5rem", + variable: "--button-big-height: 5rem;", + }, + ], + [ + "primitives.button.big.radius", + { + key: "--button-big-radius", + value: "1rem", + variable: "--button-big-radius: 1rem;", + }, + ], +] +`; + +snapshot[`processPrimitives - references colors, gradients, and themes 1`] = ` +"/* card */ +--card-default-background-color: var(--theme-light-background-primary); +--card-default-background-image: var(--gradients-orangeGradient-primary); +--card-default-border-color: var(--palette-coral-50);" +`; + +snapshot[`processPrimitives - references colors, gradients, and themes 2`] = ` +[ + [ + "primitives.card.default.background-color", + { + key: "--card-default-background-color", + value: "var(--theme-light-background-primary)", + variable: "--card-default-background-color: var(--theme-light-background-primary);", + }, + ], + [ + "primitives.card.default.background-image", + { + key: "--card-default-background-image", + value: "var(--gradients-orangeGradient-primary)", + variable: "--card-default-background-image: var(--gradients-orangeGradient-primary);", + }, + ], + [ + "primitives.card.default.border-color", + { + key: "--card-default-border-color", + value: "var(--palette-coral-50)", + variable: "--card-default-border-color: var(--palette-coral-50);", + }, + ], +] +`; + +snapshot[`processPrimitives - uses fluid spacing references 1`] = ` +"/* box */ +--box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);" +`; + +snapshot[`processPrimitives - uses fluid spacing references 2`] = ` +[ + [ + "primitives.box.default.padding", + { + key: "--box-default-padding", + value: "var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m)", + variable: "--box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);", + }, + ], +] +`; diff --git a/tests/__snapshots__/spacing.test.ts.snap b/tests/__snapshots__/spacing.test.ts.snap new file mode 100644 index 0000000..5426fd0 --- /dev/null +++ b/tests/__snapshots__/spacing.test.ts.snap @@ -0,0 +1,293 @@ +export const snapshot = {}; + +snapshot[`processSpacing - generates correct spacing scale 1`] = ` +"--spacing-size-1: 0.25rem; +--spacing-size-2: 0.5rem; +--spacing-size-3: 0.75rem; +--spacing-size-s: 1rem;" +`; + +snapshot[`processSpacing - generates correct spacing scale 2`] = ` +[ + [ + "spacing.custom.size.1", + { + key: "--spacing-size-1", + value: "0.25rem", + variable: "--spacing-size-1: 0.25rem;", + }, + ], + [ + "spacing.custom.size.2", + { + key: "--spacing-size-2", + value: "0.5rem", + variable: "--spacing-size-2: 0.5rem;", + }, + ], + [ + "spacing.custom.size.3", + { + key: "--spacing-size-3", + value: "0.75rem", + variable: "--spacing-size-3: 0.75rem;", + }, + ], + [ + "spacing.custom.size.s", + { + key: "--spacing-size-s", + value: "1rem", + variable: "--spacing-size-s: 1rem;", + }, + ], +] +`; + +snapshot[`processSpacing - handles settings 1`] = ` +"--spacing-size-1: 1rem; +--spacing-size-2: 0.5rem; +--spacing-scale-md: 8px; +--spacing-scale-lg: 0.75rem;" +`; + +snapshot[`processSpacing - handles settings 2`] = ` +[ + [ + "spacing.custom.size.1", + { + key: "--spacing-size-1", + value: "1rem", + variable: "--spacing-size-1: 1rem;", + }, + ], + [ + "spacing.custom.size.2", + { + key: "--spacing-size-2", + value: "0.5rem", + variable: "--spacing-size-2: 0.5rem;", + }, + ], + [ + "spacing.custom.scale.md", + { + key: "--spacing-scale-md", + value: "8px", + variable: "--spacing-scale-md: 8px;", + }, + ], + [ + "spacing.custom.scale.lg", + { + key: "--spacing-scale-lg", + value: "0.75rem", + variable: "--spacing-scale-lg: 0.75rem;", + }, + ], +] +`; + +snapshot[`processSpacing - generates fluid spacing (prefix) 1`] = ` +"--spacing_fluid-base-foo-xs: clamp(0rem, 0rem + 0vw, 0rem); +--spacing_fluid-base-foo-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-foo-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-foo-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); +--spacing_fluid-base-foo-xs-l: clamp(0rem, -1rem + 5vw, 3rem); +--spacing_fluid-base-foo-xs-s: clamp(0rem, -0.5rem + 2.5vw, 1.5rem); +--spacing_fluid-base-foo-s-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-foo-m-l: clamp(0.25rem, -0.6667rem + 4.5833vw, 3rem);" +`; + +snapshot[`processSpacing - generates fluid spacing (prefix) 2`] = ` +[ + [ + "spacing_fluid.base@xs", + { + key: "--spacing_fluid-base-foo-xs", + value: "clamp(0rem, 0rem + 0vw, 0rem)", + variable: "--spacing_fluid-base-foo-xs: clamp(0rem, 0rem + 0vw, 0rem);", + }, + ], + [ + "spacing_fluid.base@s", + { + key: "--spacing_fluid-base-foo-s", + value: "clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem)", + variable: "--spacing_fluid-base-foo-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@m", + { + key: "--spacing_fluid-base-foo-m", + value: "clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem)", + variable: "--spacing_fluid-base-foo-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@l", + { + key: "--spacing_fluid-base-foo-l", + value: "clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem)", + variable: "--spacing_fluid-base-foo-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem);", + }, + ], + [ + "spacing_fluid.base@xs-l", + { + key: "--spacing_fluid-base-foo-xs-l", + value: "clamp(0rem, -1rem + 5vw, 3rem)", + variable: "--spacing_fluid-base-foo-xs-l: clamp(0rem, -1rem + 5vw, 3rem);", + }, + ], + [ + "spacing_fluid.base@xs-s", + { + key: "--spacing_fluid-base-foo-xs-s", + value: "clamp(0rem, -0.5rem + 2.5vw, 1.5rem)", + variable: "--spacing_fluid-base-foo-xs-s: clamp(0rem, -0.5rem + 2.5vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@s-m", + { + key: "--spacing_fluid-base-foo-s-m", + value: "clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem)", + variable: "--spacing_fluid-base-foo-s-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@m-l", + { + key: "--spacing_fluid-base-foo-m-l", + value: "clamp(0.25rem, -0.6667rem + 4.5833vw, 3rem)", + variable: "--spacing_fluid-base-foo-m-l: clamp(0.25rem, -0.6667rem + 4.5833vw, 3rem);", + }, + ], +] +`; + +snapshot[`processSpacing - fluid without prefix falls back to scale name 1`] = ` +"--spacing_fluid-rhythm-xs: clamp(0rem, 0rem + 0vw, 0rem); +--spacing_fluid-rhythm-s: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem); +--spacing_fluid-rhythm-m: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem); +--spacing_fluid-rhythm-xs-s: clamp(0rem, -0.4167rem + 2.0833vw, 1.25rem); +--spacing_fluid-rhythm-s-m: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem);" +`; + +snapshot[`processSpacing - fluid without prefix falls back to scale name 2`] = ` +[ + [ + "spacing_fluid.rhythm@xs", + { + key: "--spacing_fluid-rhythm-xs", + value: "clamp(0rem, 0rem + 0vw, 0rem)", + variable: "--spacing_fluid-rhythm-xs: clamp(0rem, 0rem + 0vw, 0rem);", + }, + ], + [ + "spacing_fluid.rhythm@s", + { + key: "--spacing_fluid-rhythm-s", + value: "clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem)", + variable: "--spacing_fluid-rhythm-s: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem);", + }, + ], + [ + "spacing_fluid.rhythm@m", + { + key: "--spacing_fluid-rhythm-m", + value: "clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem)", + variable: "--spacing_fluid-rhythm-m: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem);", + }, + ], + [ + "spacing_fluid.rhythm@xs-s", + { + key: "--spacing_fluid-rhythm-xs-s", + value: "clamp(0rem, -0.4167rem + 2.0833vw, 1.25rem)", + variable: "--spacing_fluid-rhythm-xs-s: clamp(0rem, -0.4167rem + 2.0833vw, 1.25rem);", + }, + ], + [ + "spacing_fluid.rhythm@s-m", + { + key: "--spacing_fluid-rhythm-s-m", + value: "clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem)", + variable: "--spacing_fluid-rhythm-s-m: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem);", + }, + ], +] +`; + +snapshot[`processSpacing - combines fluid and custom spacing 1`] = ` +"--spacing_fluid-base-flux-xs: clamp(0rem, 0rem + 0vw, 0rem); +--spacing_fluid-base-flux-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-flux-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-flux-xs-s: clamp(0rem, -0.5rem + 2.5vw, 1.5rem); +--spacing_fluid-base-flux-s-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing-gap-1: 4px; +--spacing-gap-2: 8px;" +`; + +snapshot[`processSpacing - combines fluid and custom spacing 2`] = ` +[ + [ + "spacing_fluid.base@xs", + { + key: "--spacing_fluid-base-flux-xs", + value: "clamp(0rem, 0rem + 0vw, 0rem)", + variable: "--spacing_fluid-base-flux-xs: clamp(0rem, 0rem + 0vw, 0rem);", + }, + ], + [ + "spacing_fluid.base@s", + { + key: "--spacing_fluid-base-flux-s", + value: "clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem)", + variable: "--spacing_fluid-base-flux-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@m", + { + key: "--spacing_fluid-base-flux-m", + value: "clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem)", + variable: "--spacing_fluid-base-flux-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@xs-s", + { + key: "--spacing_fluid-base-flux-xs-s", + value: "clamp(0rem, -0.5rem + 2.5vw, 1.5rem)", + variable: "--spacing_fluid-base-flux-xs-s: clamp(0rem, -0.5rem + 2.5vw, 1.5rem);", + }, + ], + [ + "spacing_fluid.base@s-m", + { + key: "--spacing_fluid-base-flux-s-m", + value: "clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem)", + variable: "--spacing_fluid-base-flux-s-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", + }, + ], + [ + "spacing.custom.gap.1", + { + key: "--spacing-gap-1", + value: "4px", + variable: "--spacing-gap-1: 4px;", + }, + ], + [ + "spacing.custom.gap.2", + { + key: "--spacing-gap-2", + value: "8px", + variable: "--spacing-gap-2: 8px;", + }, + ], +] +`; diff --git a/tests/__snapshots__/typography.test.ts.snap b/tests/__snapshots__/typography.test.ts.snap new file mode 100644 index 0000000..0ce6ae6 --- /dev/null +++ b/tests/__snapshots__/typography.test.ts.snap @@ -0,0 +1,268 @@ +export const snapshot = {}; + +snapshot[`processTypography - generates correct CSS variables 1`] = ` +"--typography_fluid-arial-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); +--typography_fluid-arial-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); +--typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); +--typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); +--typography_fluid-arial-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); +--typography_fluid-arial-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); +--typography_fluid-arial-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); +--typography_fluid-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); +--typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem);" +`; + +snapshot[`processTypography - generates correct CSS variables 2`] = ` +[ + [ + "typography_fluid.arial@4xl", + { + key: "--typography_fluid-arial-4xl", + value: "clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem)", + variable: "--typography_fluid-arial-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem);", + }, + ], + [ + "typography_fluid.arial@3xl", + { + key: "--typography_fluid-arial-3xl", + value: "clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem)", + variable: "--typography_fluid-arial-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem);", + }, + ], + [ + "typography_fluid.arial@2xl", + { + key: "--typography_fluid-arial-2xl", + value: "clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem)", + variable: "--typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem);", + }, + ], + [ + "typography_fluid.arial@xl", + { + key: "--typography_fluid-arial-xl", + value: "clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem)", + variable: "--typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", + }, + ], + [ + "typography_fluid.arial@l", + { + key: "--typography_fluid-arial-l", + value: "clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem)", + variable: "--typography_fluid-arial-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem);", + }, + ], + [ + "typography_fluid.arial@m", + { + key: "--typography_fluid-arial-m", + value: "clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem)", + variable: "--typography_fluid-arial-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem);", + }, + ], + [ + "typography_fluid.arial@s", + { + key: "--typography_fluid-arial-s", + value: "clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem)", + variable: "--typography_fluid-arial-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem);", + }, + ], + [ + "typography_fluid.arial@xs", + { + key: "--typography_fluid-arial-xs", + value: "clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem)", + variable: "--typography_fluid-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem);", + }, + ], + [ + "typography_fluid.arial@2xs", + { + key: "--typography_fluid-arial-2xs", + value: "clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem)", + variable: "--typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem);", + }, + ], +] +`; + +snapshot[`typography - can handle custom labels and prefixes 1`] = ` +"--typography_fluid-arial-text-h1: clamp(4.1723rem, 4.0013rem + 0.8553vw, 4.7684rem); +--typography_fluid-arial-text-h2: clamp(3.3379rem, 3.201rem + 0.6843vw, 3.8147rem); +--typography_fluid-arial-text-h3: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); +--typography_fluid-arial-text-h4: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); +--typography_fluid-arial-text-xxl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); +--typography_fluid-arial-text-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); +--typography_fluid-arial-text-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); +--typography_fluid-arial-text-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); +--typography_fluid-arial-text-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); +--typography_fluid-arial-text-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem);" +`; + +snapshot[`typography - can handle custom labels and prefixes 2`] = ` +[ + [ + "typography_fluid.arial@h1", + { + key: "--typography_fluid-arial-text-h1", + value: "clamp(4.1723rem, 4.0013rem + 0.8553vw, 4.7684rem)", + variable: "--typography_fluid-arial-text-h1: clamp(4.1723rem, 4.0013rem + 0.8553vw, 4.7684rem);", + }, + ], + [ + "typography_fluid.arial@h2", + { + key: "--typography_fluid-arial-text-h2", + value: "clamp(3.3379rem, 3.201rem + 0.6843vw, 3.8147rem)", + variable: "--typography_fluid-arial-text-h2: clamp(3.3379rem, 3.201rem + 0.6843vw, 3.8147rem);", + }, + ], + [ + "typography_fluid.arial@h3", + { + key: "--typography_fluid-arial-text-h3", + value: "clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem)", + variable: "--typography_fluid-arial-text-h3: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem);", + }, + ], + [ + "typography_fluid.arial@h4", + { + key: "--typography_fluid-arial-text-h4", + value: "clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem)", + variable: "--typography_fluid-arial-text-h4: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem);", + }, + ], + [ + "typography_fluid.arial@xxl", + { + key: "--typography_fluid-arial-text-xxl", + value: "clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem)", + variable: "--typography_fluid-arial-text-xxl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem);", + }, + ], + [ + "typography_fluid.arial@xl", + { + key: "--typography_fluid-arial-text-xl", + value: "clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem)", + variable: "--typography_fluid-arial-text-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", + }, + ], + [ + "typography_fluid.arial@l", + { + key: "--typography_fluid-arial-text-l", + value: "clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem)", + variable: "--typography_fluid-arial-text-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem);", + }, + ], + [ + "typography_fluid.arial@m", + { + key: "--typography_fluid-arial-text-m", + value: "clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem)", + variable: "--typography_fluid-arial-text-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem);", + }, + ], + [ + "typography_fluid.arial@s", + { + key: "--typography_fluid-arial-text-s", + value: "clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem)", + variable: "--typography_fluid-arial-text-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem);", + }, + ], + [ + "typography_fluid.arial@xs", + { + key: "--typography_fluid-arial-text-xs", + value: "clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem)", + variable: "--typography_fluid-arial-text-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem);", + }, + ], +] +`; + +snapshot[`processTypography - can process weights 1`] = ` +"--typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); +--typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); +--typography_fluid-arial-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); +--typography_fluid-arial-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); +--typography_fluid-arial-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); +--typography_fluid-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); +--typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem); +--typography-weight-arial-regular: 500;" +`; + +snapshot[`processTypography - can process weights 2`] = ` +[ + [ + "typography_fluid.arial@2xl", + { + key: "--typography_fluid-arial-2xl", + value: "clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem)", + variable: "--typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem);", + }, + ], + [ + "typography_fluid.arial@xl", + { + key: "--typography_fluid-arial-xl", + value: "clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem)", + variable: "--typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", + }, + ], + [ + "typography_fluid.arial@l", + { + key: "--typography_fluid-arial-l", + value: "clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem)", + variable: "--typography_fluid-arial-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem);", + }, + ], + [ + "typography_fluid.arial@m", + { + key: "--typography_fluid-arial-m", + value: "clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem)", + variable: "--typography_fluid-arial-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem);", + }, + ], + [ + "typography_fluid.arial@s", + { + key: "--typography_fluid-arial-s", + value: "clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem)", + variable: "--typography_fluid-arial-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem);", + }, + ], + [ + "typography_fluid.arial@xs", + { + key: "--typography_fluid-arial-xs", + value: "clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem)", + variable: "--typography_fluid-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem);", + }, + ], + [ + "typography_fluid.arial@2xs", + { + key: "--typography_fluid-arial-2xs", + value: "clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem)", + variable: "--typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem);", + }, + ], + [ + "typography.weight.arial.regular", + { + key: "--typography-weight-arial-regular", + value: "500", + variable: "--typography-weight-arial-regular: 500;", + }, + ], +] +`; diff --git a/tests/colors.test.ts b/tests/colors.test.ts index 14688a7..266964a 100644 --- a/tests/colors.test.ts +++ b/tests/colors.test.ts @@ -1,15 +1,18 @@ -import { assertEquals } from "@std/assert"; +import { assert, assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; import { defineConfig, processColors } from "../src/mod.ts"; import { getLines } from "./helpers.ts"; -Deno.test("processColors - converts hex to oklch", () => { +Deno.test("processColors - converts hex to oklch", async (t) => { const config = defineConfig({ colors: { palette: { value: { coral: { - 100: { hex: "#FF7F50" }, - 200: { hex: "#FF6347" }, + value: { + 100: { hex: "#FF7F50" }, + 200: { hex: "#FF6347" }, + }, }, }, }, @@ -19,30 +22,34 @@ Deno.test("processColors - converts hex to oklch", () => { const { css, resolveMap } = processColors(config.colors); const lines = getLines(css); assertEquals( - lines[1].trim(), + lines[2].trim(), "--palette-coral-100: oklch(73.511% 0.16799 40.24666);", ); assertEquals( - lines[2].trim(), + lines[3].trim(), "--palette-coral-200: oklch(69.622% 0.19552 32.32143);", ); assertEquals(Array.from(resolveMap.keys()), [ - "palette.value.coral.100", - "palette.value.coral.200", + "palette.coral.100", + "palette.coral.200", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles different color formats", () => { +Deno.test("processColors - handles different color formats", async (t) => { const config = defineConfig( { colors: { palette: { value: { brand: { - default: { hex: "#FF0000" }, - 200: { rgb: [0, 255, 0] }, - 300: { hsl: [240, 100, 50] }, - 400: { oklch: "oklch(0.7 0.2 270)" }, + value: { + default: { hex: "#FF0000" }, + 200: { rgb: [0, 255, 0] }, + 300: { hsl: [240, 100, 50] }, + 400: { oklch: "oklch(0.7 0.2 270)" }, + }, }, }, }, @@ -53,35 +60,39 @@ Deno.test("processColors - handles different color formats", () => { const lines = getLines(css); assertEquals( - lines[1].trim(), + lines[2].trim(), "--palette-brand-200: oklch(86.644% 0.29483 142.49535);", ); assertEquals( - lines[2].trim(), + lines[3].trim(), "--palette-brand-300: oklch(45.201% 0.31321 264.05202);", ); - assertEquals(lines[3].trim(), "--palette-brand-400: oklch(70% 0.2 270);"); + assertEquals(lines[4].trim(), "--palette-brand-400: oklch(70% 0.2 270);"); assertEquals( - lines[4].trim(), + lines[5].trim(), "--palette-brand-default: oklch(62.796% 0.25768 29.23388);", ); assertEquals(Array.from(resolveMap.keys()), [ - "palette.value.brand.200", - "palette.value.brand.300", - "palette.value.brand.400", - "palette.value.brand.default", + "palette.brand.200", + "palette.brand.300", + "palette.brand.400", + "palette.brand.default", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles string values", () => { +Deno.test("processColors - handles string values", async (t) => { const config = defineConfig({ colors: { palette: { value: { simple: { - white: "oklch(100% 0 0)", - black: "#000", + value: { + white: "oklch(100% 0 0)", + black: "#000", + }, }, }, }, @@ -90,41 +101,45 @@ Deno.test("processColors - handles string values", () => { const { css, resolveMap } = processColors(config.colors); const lines = getLines(css); assertEquals( - lines[1].trim(), + lines[2].trim(), "--palette-simple-white: oklch(100% 0 0);", ); assertEquals( - lines[2].trim(), + lines[3].trim(), "--palette-simple-black: oklch(0% 0 0);", ); assertEquals(Array.from(resolveMap.keys()), [ - "palette.value.simple.white", - "palette.value.simple.black", + "palette.simple.white", + "palette.simple.black", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles themes", () => { +Deno.test("processColors - handles themes", async (t) => { const config = defineConfig({ colors: { palette: { value: { simple: { - white: "oklch(100% 0 0)", - black: { hex: "#000" }, + value: { + white: "oklch(100% 0 0)", + black: { hex: "#000" }, + }, }, }, }, theme: { - value: { - light: { + light: { + value: { background: { value: { primary: "var(--1)", secondary: "var(--2)", }, variables: { - 1: "palette.value.simple.white", - 2: "palette.value.simple.black", + 1: "palette.simple.white", + 2: "palette.simple.black", }, }, }, @@ -135,29 +150,33 @@ Deno.test("processColors - handles themes", () => { const { css, resolveMap } = processColors(config.colors); const lines = getLines(css); assertEquals( - lines[5].trim(), + lines[7].trim(), "--theme-light-background-primary: var(--palette-simple-white);", ); assertEquals( - lines[6].trim(), + lines[8].trim(), "--theme-light-background-secondary: var(--palette-simple-black);", ); assertEquals(Array.from(resolveMap.keys()), [ - "palette.value.simple.white", - "palette.value.simple.black", - "theme.value.light.background.primary", - "theme.value.light.background.secondary", + "palette.simple.white", + "palette.simple.black", + "theme.light.background.primary", + "theme.light.background.secondary", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles transparency", () => { +Deno.test("processColors - handles transparency", async (t) => { const config = defineConfig({ colors: { palette: { value: { alpha: { - softGray1: "oklch(14.48% 0 0 / 12%)", - softGray2: "oklch(14.48% 0 0 / 24%)", + value: { + softGray1: "oklch(14.48% 0 0 / 12%)", + softGray2: "oklch(14.48% 0 0 / 24%)", + }, }, }, }, @@ -166,31 +185,37 @@ Deno.test("processColors - handles transparency", () => { const { css, resolveMap } = processColors(config.colors); const lines = getLines(css); assertEquals( - lines[1], + lines[2].trim(), "--palette-alpha-softGray1: oklch(14.48% 0 0 / 12%);", ); assertEquals( - lines[2], + lines[3].trim(), "--palette-alpha-softGray2: oklch(14.48% 0 0 / 24%);", ); assertEquals(Array.from(resolveMap.keys()), [ - "palette.value.alpha.softGray1", - "palette.value.alpha.softGray2", + "palette.alpha.softGray1", + "palette.alpha.softGray2", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - generates gradient with color variables", () => { +Deno.test("processColors - generates gradient with color variables", async (t) => { const config = defineConfig({ colors: { palette: { value: { coral: { - "50": { hex: "#FF7E60" }, - "90": { hex: "#FF7F40" }, - "100": { hex: "#FF6347" }, + value: { + "50": { hex: "#FF7E60" }, + "90": { hex: "#FF7F40" }, + "100": { hex: "#FF6347" }, + }, }, indigo: { - "100": { hex: "#4F46E5" }, + value: { + "100": { hex: "#4F46E5" }, + }, }, }, }, @@ -202,10 +227,10 @@ Deno.test("processColors - generates gradient with color variables", () => { value: "linear-gradient(261.78deg, var(--1) 33.1%, var(--2) 56.3%, var(--3) 65.78%, var(--4) 84.23%)", variables: { - "1": "palette.value.coral.50", - "2": "palette.value.coral.90", - "3": "palette.value.coral.100", - "4": "palette.value.indigo.100", + "1": "palette.coral.50", + "2": "palette.coral.90", + "3": "palette.coral.100", + "4": "palette.indigo.100", }, }, }, @@ -223,21 +248,25 @@ Deno.test("processColors - generates gradient with color variables", () => { assertEquals(lines.at(-1), expected); assertEquals(Array.from(result.resolveMap.keys()), [ - "palette.value.coral.50", - "palette.value.coral.90", - "palette.value.coral.100", - "palette.value.indigo.100", - "gradients.value.orangeGradient.primary", + "palette.coral.50", + "palette.coral.90", + "palette.coral.100", + "palette.indigo.100", + "gradients.orangeGradient.primary", ]); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("processColors - handles themes referencing gradients", () => { +Deno.test("processColors - handles themes referencing gradients", async (t) => { const config = defineConfig({ colors: { palette: { value: { coral: { - "50": { hex: "#FF7E60" }, + value: { + "50": { hex: "#FF7E60" }, + }, }, }, }, @@ -248,8 +277,8 @@ Deno.test("processColors - handles themes referencing gradients", () => { primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "palette.value.coral.50", - "c2": "palette.value.coral.50", + "c1": "palette.coral.50", + "c2": "palette.coral.50", }, }, }, @@ -257,14 +286,14 @@ Deno.test("processColors - handles themes referencing gradients", () => { }, }, theme: { - value: { - light: { + light: { + value: { background: { value: { primary: "var(--grad)", }, variables: { - "grad": "gradients.value.orangeGradient.primary", + "grad": "gradients.orangeGradient.primary", }, }, }, @@ -281,8 +310,184 @@ Deno.test("processColors - handles themes referencing gradients", () => { "--theme-light-background-primary: var(--gradients-orangeGradient-primary);", ); assertEquals(Array.from(resolveMap.keys()), [ - "palette.value.coral.50", - "gradients.value.orangeGradient.primary", - "theme.value.light.background.primary", + "palette.coral.50", + "gradients.orangeGradient.primary", + "theme.light.background.primary", + ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); +}); + +Deno.test("processColors - handles gradients with conditions", async (t) => { + const config = defineConfig({ + colors: { + palette: { + value: { + coral: { + value: { + "50": { hex: "#FF7E60" }, + }, + }, + }, + }, + gradients: { + value: { + orangeGradient: { + value: { + primary: { + value: "linear-gradient(to right, var(--c1), var(--c2))", + variables: { + "c1": "palette.coral.50", + "c2": "palette.coral.50", + }, + }, + }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, + }, + }, + }, + }, + }); + + const { css, resolveMap } = processColors(config.colors); + const lines = getLines(css); + + const mediaQueryIndex = lines.findIndex((line) => + line.includes("@media (prefers-color-scheme: dark)") + ); + + assertEquals(lines[mediaQueryIndex], "@media (prefers-color-scheme: dark) {"); + + const varIndex = lines.findIndex((line, idx) => + idx > mediaQueryIndex && line.includes("--gradients-orangeGradient-primary:") + ); + + assert( + varIndex > mediaQueryIndex, + "Gradient variable should be inside the media query block", + ); + + assertEquals( + lines[varIndex].trim(), + "--gradients-orangeGradient-primary: linear-gradient(to right, var(--palette-coral-50), var(--palette-coral-50));", + ); + + assertEquals(Array.from(resolveMap.keys()), [ + "palette.coral.50", + "gradients.orangeGradient.primary", + ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); +}); + +Deno.test("processColors - handles palette colors with conditions", async (t) => { + const config = defineConfig({ + colors: { + palette: { + value: { + background: { + value: { + light: "oklch(100% 0 0)", + }, + }, + backgroundDark: { + value: { + dark: { hex: "#000" }, + }, + settings: { + condition: "@media (prefers-color-scheme: dark)", + }, + }, + }, + }, + }, + }); + const { css, resolveMap } = processColors(config.colors); + const lines = getLines(css); + + assertEquals( + lines[2].trim(), + "--palette-background-light: oklch(100% 0 0);", + ); + + const mediaQueryIndex = lines.findIndex((line) => + line.includes("@media (prefers-color-scheme: dark)") + ); + + assertEquals(lines[mediaQueryIndex], "@media (prefers-color-scheme: dark) {"); + + const darkVarIndex = lines.findIndex((line) => + line.includes("--palette-backgroundDark-dark:") + ); + + assertEquals( + lines[darkVarIndex].trim(), + "--palette-backgroundDark-dark: oklch(0% 0 0);", + ); + + const closingBraceIndex = lines.findIndex((line, idx) => + idx > mediaQueryIndex && line === "}" + ); + assert( + closingBraceIndex > darkVarIndex, + "Closing brace should exist after variables", + ); + + assertEquals(Array.from(resolveMap.keys()), [ + "palette.background.light", + "palette.backgroundDark.dark", + ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); +}); + +Deno.test("processColors - handles themes with class condition", async (t) => { + const config = defineConfig({ + colors: { + palette: { + value: { + base: { + value: { + primary: { hex: "#FF7F50" }, + }, + }, + }, + }, + theme: { + dark: { + value: { + background: { + value: { + primary: "var(--1)", + }, + variables: { + "1": "palette.base.primary", + }, + }, + }, + settings: { + condition: ".dark-theme", + }, + }, + }, + }, + }); + + const { css, resolveMap } = processColors(config.colors); + const lines = getLines(css); + + const classIndex = lines.findIndex((l) => l.includes(".dark-theme")); + assertEquals(lines[classIndex], ".dark-theme {"); + + const varIndex = lines.findIndex((l) => l.includes("--theme-dark-background-primary:")); + assert(varIndex > classIndex, "theme variable should be inside the class block"); + + assertEquals(Array.from(resolveMap.keys()), [ + "palette.base.primary", + "theme.dark.background.primary", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); diff --git a/tests/primitive.test.ts b/tests/primitive.test.ts index c52388a..519c77f 100644 --- a/tests/primitive.test.ts +++ b/tests/primitive.test.ts @@ -1,8 +1,9 @@ import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; import { processPrimitives } from "../src/modules/primitive.ts"; import { defineConfig } from "../src/mod.ts"; -Deno.test("processPrimitives - processes button with variables", () => { +Deno.test("processPrimitives - processes button with variables", async (t) => { const config = defineConfig({ typography: { fluid: { @@ -44,8 +45,8 @@ Deno.test("processPrimitives - processes button with variables", () => { }, variables: { "base": "typography_fluid.arial@m", - "2": "spacing.custom.size.value.2", - "3": "spacing.custom.size.value.3", + "2": "spacing.custom.size.2", + "3": "spacing.custom.size.3", }, settings: { pxToRem: false }, }, @@ -65,9 +66,11 @@ Deno.test("processPrimitives - processes button with variables", () => { ].join("\n"); assertEquals(result.css, expected); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("processPrimitives - processes buttons with settings", () => { +Deno.test("processPrimitives - processes buttons with settings", async (t) => { const config = defineConfig({ primitives: { button: { @@ -105,15 +108,19 @@ Deno.test("processPrimitives - processes buttons with settings", () => { ].join("\n"); assertEquals(result.css, expected); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("processPrimitives - references colors, gradients, and themes", () => { +Deno.test("processPrimitives - references colors, gradients, and themes", async (t) => { const config = defineConfig({ colors: { palette: { value: { coral: { - "50": { hex: "#FF7E60" }, + value: { + "50": { hex: "#FF7E60" }, + }, }, }, }, @@ -124,8 +131,8 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "palette.value.coral.50", - "c2": "palette.value.coral.50", + "c1": "palette.coral.50", + "c2": "palette.coral.50", }, }, }, @@ -133,14 +140,14 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => }, }, theme: { - value: { - light: { + light: { + value: { background: { value: { primary: "var(--grad)", }, variables: { - "grad": "gradients.value.orangeGradient.primary", + "grad": "gradients.orangeGradient.primary", }, }, }, @@ -157,9 +164,9 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => "border-color": "var(--border)", }, variables: { - "bg": "theme.value.light.background.primary", - "grad": "gradients.value.orangeGradient.primary", - "border": "palette.value.coral.50", + "bg": "theme.light.background.primary", + "grad": "gradients.orangeGradient.primary", + "border": "palette.coral.50", }, }, }, @@ -176,9 +183,11 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => ].join("\n"); assertEquals(result.css, expected); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("processPrimitives - uses fluid spacing references", () => { +Deno.test("processPrimitives - uses fluid spacing references", async (t) => { const config = defineConfig({ spacing: { fluid: { @@ -218,4 +227,6 @@ Deno.test("processPrimitives - uses fluid spacing references", () => { "--box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);", ].join("\n"); assertEquals(primitives.css, expected); + await assertSnapshot(t, primitives.css); + await assertSnapshot(t, Array.from(primitives.resolveMap.entries())); }); diff --git a/tests/spacing.test.ts b/tests/spacing.test.ts index ccad980..2156e92 100644 --- a/tests/spacing.test.ts +++ b/tests/spacing.test.ts @@ -1,8 +1,9 @@ import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; import { processSpacing } from "../src/modules/spacing.ts"; import { defineConfig } from "../src/config.ts"; -Deno.test("processSpacing - generates correct spacing scale", () => { +Deno.test("processSpacing - generates correct spacing scale", async (t) => { const config = defineConfig({ spacing: { custom: { @@ -23,15 +24,17 @@ Deno.test("processSpacing - generates correct spacing scale", () => { assertEquals(result.css, expected); assertEquals(Array.from(result.resolveMap.keys()), [ - "spacing.custom.size.value.1", - "spacing.custom.size.value.2", - "spacing.custom.size.value.3", - "spacing.custom.size.value.s", + "spacing.custom.size.1", + "spacing.custom.size.2", + "spacing.custom.size.3", + "spacing.custom.size.s", ]); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); // Test with string keys -Deno.test("processSpacing - handles settings", () => { +Deno.test("processSpacing - handles settings", async (t) => { const config = defineConfig({ spacing: { custom: { @@ -58,14 +61,16 @@ Deno.test("processSpacing - handles settings", () => { assertEquals(result.css, expected); assertEquals(Array.from(result.resolveMap.keys()), [ - "spacing.custom.size.value.1", - "spacing.custom.size.value.2", - "spacing.custom.scale.value.md", - "spacing.custom.scale.value.lg", + "spacing.custom.size.1", + "spacing.custom.size.2", + "spacing.custom.scale.md", + "spacing.custom.scale.lg", ]); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("processSpacing - generates fluid spacing (prefix)", () => { +Deno.test("processSpacing - generates fluid spacing (prefix)", async (t) => { const config = defineConfig({ spacing: { fluid: { @@ -108,9 +113,11 @@ Deno.test("processSpacing - generates fluid spacing (prefix)", () => { "spacing_fluid.base@s-m", "spacing_fluid.base@m-l", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processSpacing - fluid without prefix falls back to scale name", () => { +Deno.test("processSpacing - fluid without prefix falls back to scale name", async (t) => { const config = defineConfig({ spacing: { fluid: { @@ -144,9 +151,11 @@ Deno.test("processSpacing - fluid without prefix falls back to scale name", () = "spacing_fluid.rhythm@xs-s", "spacing_fluid.rhythm@s-m", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processSpacing - combines fluid and custom spacing", () => { +Deno.test("processSpacing - combines fluid and custom spacing", async (t) => { const config = defineConfig({ spacing: { fluid: { @@ -187,7 +196,9 @@ Deno.test("processSpacing - combines fluid and custom spacing", () => { "spacing_fluid.base@m", "spacing_fluid.base@xs-s", "spacing_fluid.base@s-m", - "spacing.custom.gap.value.1", - "spacing.custom.gap.value.2", + "spacing.custom.gap.1", + "spacing.custom.gap.2", ]); + await assertSnapshot(t, css); + await assertSnapshot(t, Array.from(resolveMap.entries())); }); diff --git a/tests/typography.test.ts b/tests/typography.test.ts index 04a19b0..01b2d78 100644 --- a/tests/typography.test.ts +++ b/tests/typography.test.ts @@ -1,8 +1,9 @@ import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; import { defineConfig, processTypography } from "../src/mod.ts"; import { getLines } from "./helpers.ts"; -Deno.test("processTypography - generates correct CSS variables", () => { +Deno.test("processTypography - generates correct CSS variables", async (t) => { const config = defineConfig({ typography: { fluid: { @@ -56,9 +57,11 @@ Deno.test("processTypography - generates correct CSS variables", () => { ); assertEquals(hasSize, true, `Missing size ${size}`); }); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("typography - can handle custom labels and prefixes", () => { +Deno.test("typography - can handle custom labels and prefixes", async (t) => { const config = defineConfig({ typography: { fluid: { @@ -128,9 +131,11 @@ Deno.test("typography - can handle custom labels and prefixes", () => { ); assertEquals(hasSize, true, `Missing size ${size}`); }); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); -Deno.test("processTypography - can process weights", () => { +Deno.test("processTypography - can process weights", async (t) => { const positiveSteps = 3; const negativeSteps = 3; const config = defineConfig({ @@ -169,7 +174,7 @@ Deno.test("processTypography - can process weights", () => { "typography_fluid.arial@s", "typography_fluid.arial@xs", "typography_fluid.arial@2xs", - "typography.weight.arial.value.regular", + "typography.weight.arial.regular", ], ); // Test that we have the weight variable @@ -177,4 +182,6 @@ Deno.test("processTypography - can process weights", () => { line.includes("--typography-weight-arial-regular:") ); assertEquals(xlLine, "--typography-weight-arial-regular: 500;"); + await assertSnapshot(t, result.css); + await assertSnapshot(t, Array.from(result.resolveMap.entries())); });