From 68e466ebad4bbb223d8391f34bd5450d682ed983 Mon Sep 17 00:00:00 2001 From: Hebilicious Date: Tue, 23 Sep 2025 20:04:19 +0700 Subject: [PATCH 1/5] feat: add spacing module and refactor schema --- .gitignore | 4 +- README.md | 563 ++++++++++++++++----- deno.json | 6 +- deno.lock | 82 ++- example/basic/.cssforge/output.css | 49 +- example/basic/cssforge.config.ts | 30 +- package.json | 2 +- sync-version.js => scripts/sync-version.js | 0 scripts/update-readme.ts | 191 +++++++ src/cli.ts | 4 +- src/config.ts | 11 +- src/helpers.ts | 19 +- src/lib.ts | 86 +++- src/modules/colors.ts | 68 +-- src/modules/primitive.ts | 52 +- src/modules/spacing.ts | 116 +++-- src/modules/typography.ts | 120 ++--- tests/colors.test.ts | 57 ++- tests/primitive.test.ts | 102 +++- tests/spacing.test.ts | 131 ++++- tests/typography.test.ts | 44 +- 21 files changed, 1305 insertions(+), 432 deletions(-) rename sync-version.js => scripts/sync-version.js (100%) create mode 100644 scripts/update-readme.ts diff --git a/.gitignore b/.gitignore index 33f404e..66d9451 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules generated +convert.ts /cssforge.config.ts -/.cssforge \ No newline at end of file +/.cssforge +TODO.md \ No newline at end of file diff --git a/README.md b/README.md index 1e69a99..6bb6492 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,27 @@ 0 runtime design tokens generator for modern style systems. +> [!WARNING] +> CSSForge is an experimental library and its API *will* change while I figure out a schema that makes sense. That being said, when the schema change, you should be able to search and replace your variables names. + + ## Why -CSS forge is a suite of tools that leverages modern CSS features and conventions to help -you generate design tokens. All operations happen at build time, so you can use the -generated tokens in your stylesheets without any runtime overhead. Cssforge accepts a -single, serializable configuration object. +CSS forge is library that leverages modern CSS features and conventions to help +you generate CSS custom properties (css variables). +At the core of CSSforge is the schema : A serializable configuration object. +CSSforge has 0 runtime and generate at build time raw CSS, Typescript or JSON. +This intentionally keeps things simple and flexible, and allows you to integrate it with any framework or CSS workflow. +CSSforge will try to integrate with design tools such as Figma, cooors etc in the future. ## Features - šŸŽØ **Colors**: Create palettes, gradients and themes. Automatically convert to OKLCH. -- šŸ“ **Type Scale**: Generate fluid typography with `clamp()` -- šŸ“ **Spacing Scale**: Organise spacing utilities +- šŸ“ **Typography**: Generate fluid typography +- šŸ“ **Spacing**: Organise spacing utilities - šŸ“¦ **Primitives**: Define custom design tokens -- šŸ”„ **Watch Mode**: Auto-regenerate when your config changes - šŸŽÆ **Zero Runtime**: All processing happens at build time +- šŸ”„ **Watch Mode**: Auto-regenerate when your config changes - šŸ”Œ **Framework Agnostic**: Use with any CSS workflow ## Installation @@ -53,18 +59,20 @@ import { defineConfig } from "jsr:@hebilicious/cssforge"; export default defineConfig({ spacing: { - size: { - value: { - 1: "0.25rem", - 2: "0.5rem", - 3: "0.75rem", - 4: "1rem", + custom: { + size: { + value: { + 1: "0.25rem", + 2: "0.5rem", + 3: "0.75rem", + 4: "1rem", + }, }, }, }, typography: { - arial: { - typescale: { + fluid: { + arial: { value: { minWidth: 320, minFontSize: 14, @@ -103,15 +111,19 @@ If you're using a package.json, you can add the follwing into your scripts : ```json { "scripts": { - "cssforge": "npx @hebilicious/cssforge/cli" + "cssforge": "node node_modules/@hebilicious/cssforge/src/cli" } } ``` +_In the future JSR will support using `npx @hebilicious/cssforge` directly._ + Then run the following command : ```bash -npx cssforge --watch +npm run cssforge # Basic usage +npm run cssforge --help # To see all options +npm run cssforge --watch # To watch for changes ``` If you're using a deno.json : @@ -124,14 +136,18 @@ If you're using a deno.json : } ``` -The run the following command : +Then : ```bash -deno run cssforge --watch +deno task cssforge # Basic usage +deno task cssforge --help # To see all options +deno task cssforge --watch # To watch for changes ``` 3. Use the generated variables in your CSS: +For example, you can import as a layer : + ```css @import "cssforge.output.css" layer(cssforge); @@ -144,7 +160,6 @@ deno run cssforge --watch 4. Use the generated css in your JS/TS : ```typescript -import { generateTS } from "jsr:@hebilicious/cssforge"; import { cssForge } from "./.cssforge/output.ts"; // Use like this anywhere : @@ -160,6 +175,55 @@ export { cssForge }; Define colors in any format - they'll be automatically converted to OKLCH. You can compose colors from the palette into gradients and themes. + ```typescript export default defineConfig({ colors: { @@ -182,8 +246,8 @@ export default defineConfig({ primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "colors.palette.value.simple.white", - "c2": "colors.palette.value.simple.green", + "c1": "palette.value.simple.white", + "c2": "palette.value.simple.green", }, }, }, @@ -199,8 +263,8 @@ export default defineConfig({ secondary: "var(--2)", }, variables: { - 1: "colors.palette.value.simple.white", - 2: "colors.gradients.value.white-green", //Reference the color name directly. + 1: "palette.value.simple.white", + 2: "gradients.value.white-green", //Reference the color name directly. }, }, }, @@ -210,31 +274,26 @@ export default defineConfig({ }); ``` -This will generate the following css : +This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { - /*____ Colors ____*/ - /* Palette */ - --color-simple-white: oklch(100% 0 0); - --color-simple-black: oklch(0% 0 0); - --color-simple-green: oklch(86.644% 0.29483 142.49535); - --color-simple-blue: oklch(45.201% 0.31321 264.05202); - --color-simple-violet: oklch(70% 0.2 270); - --color-simple-red: oklch(62.796% 0.25768 29.23388); - /* Gradients */ - --gradient-white-green-primary: linear-gradient( - to right, - var(--color-simple-white), - var(--color-simple-green) - ); - /* Theme: light */ - /* background */ - --theme-light-background-primary: var(--color-simple-white); - --theme-light-background-secondary: var(--gradient-white-green-primary); +/*____ Colors ____*/ +/* Palette */ +--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); +/* Gradients */ +--gradients-white-green-primary: linear-gradient(to right, var(--palette-simple-white), var(--palette-simple-green)); +/* Theme: light */ +/* background */ } ``` + ### Spacing @@ -242,50 +301,230 @@ Define custom spacing scale, that can be referenced for other types, such as pri By default all spacing values are converted to from `px` to `rem`. This can be disabled with the settings. + ```typescript export default defineConfig({ spacing: { - size: { - value: { - 1: "0.25rem", - 2: "0.5rem", - 3: "0.75rem", - 4: "16px", + custom: { + size: { + value: { + 1: "0.25rem", + 2: "0.5rem", + 3: "0.75rem", + 4: "16px", + }, + settings: { pxToRem: true, rem: 16 }, // Optional, default settings + }, + }, + }, +}); +``` + +This will generate the following CSS : + +```css +/*____ CSSForge ____*/ +:root { +/*____ Spacing ____*/ +--spacing-size-1: 0.25rem; +--spacing-size-2: 0.5rem; +--spacing-size-3: 0.75rem; +--spacing-size-4: 1rem; +} +``` + + + +#### Fluid Spacing (Utopia) + +You can generate fluid spacing scales powered by [Utopia](https://utopia.fyi). Fluid scales +output `clamp()` expressions which interpolate between a minimum and maximum size across a +viewport range. + + +```typescript +export default defineConfig({ + spacing: { + fluid: { + base: { + value: { + minSize: 4, + maxSize: 24, + minWidth: 320, + maxWidth: 1280, + negativeSteps: [0], + positiveSteps: [3], + prefix: "hi", + }, + }, + }, + }, +}); +``` + +This will generate the following CSS : + +```css +/*____ CSSForge ____*/ +:root { +/*____ Spacing ____*/ +--spacing_fluid-base-hi-xs: clamp(0rem, 0rem + 0vw, 0rem); +--spacing_fluid-base-hi-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-hi-m: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); +} +``` + + +You can combine fluid and static spacing: + + +```typescript +export default defineConfig({ + spacing: { + fluid: { + base: { + value: { + minSize: 4, + maxSize: 24, + minWidth: 320, + maxWidth: 1280, + positiveSteps: [1.5, 2, 3, 4, 6], + negativeSteps: [0.75, 0.5, 0.25], + prefix: "fluid", + }, + }, + }, + custom: { + gap: { + value: { 1: "4px", 2: "8px" }, }, - settings: { pxToRem: true, rem: 16 }, // Optional, default settings }, }, }); ``` -This will generate the following css : +This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { - /*____ Spacing ____*/ - --size-1: 0.25rem; - --size-2: 0.5rem; - --size-3: 0.75rem; - --size-4: 1rem; +/*____ Spacing ____*/ +--spacing_fluid-base-fluid-3xs: clamp(0.0625rem, -0.0417rem + 0.5208vw, 0.375rem); +--spacing_fluid-base-fluid-2xs: clamp(0.125rem, -0.0833rem + 1.0417vw, 0.75rem); +--spacing_fluid-base-fluid-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); +--spacing_fluid-base-fluid-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-fluid-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); +--spacing_fluid-base-fluid-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); +--spacing_fluid-base-fluid-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); +--spacing_fluid-base-fluid-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); +--spacing_fluid-base-fluid-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); +--spacing-gap-1: 0.25rem; +--spacing-gap-2: 0.5rem; } ``` + ### Typography Define your typography, with typescales powered by [utopia](https://utopia.fyi/type/calculator): + ```typescript export default defineConfig({ typography: { - arial: { - weight: { + weight: { + arial: { value: { - regular: "500", + regular: "600", }, }, - typescale: { + }, + fluid: { + arial: { value: { minWidth: 320, minFontSize: 14, @@ -302,35 +541,66 @@ export default defineConfig({ }); ``` -This will generate the following css : +This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { - /*____ Typography ____*/ - --typography-arial-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); - --typography-arial-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); - --typography-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); - --typography-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); - --typography-arial-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); - --typography-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); - --typography-arial-sm: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); - --typography-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); - --typography-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem); - --weight-arial-regular: 500; +/*____ Typography ____*/ +--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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); +--typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); +--typography_fluid-arial-sm: 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: 600; } ``` + -### Customizing Typescales +### Customizing Fluid Typography Scales You can customize the typescale by providing your prefix and custom labels. The prefix will overwrite the name of the key that you are using to define your typography. + ```typescript const config = defineConfig({ typography: { - comicsans: { - typescale: { + fluid: { + comicsans: { value: { minWidth: 320, minFontSize: 14, @@ -357,24 +627,25 @@ const config = defineConfig({ }); ``` -Will generate the following css variables : +This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { - /*____ Typography ____*/ - --typography-text-e: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); - --typography-text-d: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); - --typography-text-c: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); - --typography-text-b: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); - --typography-text-a: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); +/*____ Typography ____*/ +--typography_fluid-comicsans-text-e: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); +--typography_fluid-comicsans-text-d: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); +--typography_fluid-comicsans-text-c: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); +--typography_fluid-comicsans-text-b: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); +--typography_fluid-comicsans-text-a: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); } ``` + -#### Referencing Typescale +#### Referencing Fluid Typography -To reference typescales, use the `@` symbol and the label of the typescale; ie: -`typography.text.typescale@a`. By default the labels follow the Tailwind convention : +To reference fluid typography, use the `@` symbol and the label of the scale; ie: +`typography_fluid.text@a`. By default the labels follow the Tailwind convention : - 2xs - xs @@ -389,11 +660,11 @@ To reference typescales, use the `@` symbol and the label of the typescale; ie: More flexible than other types, primitives allow you to define any type of token by composing the base types. -```typescript + +```typescript +export default defineConfig({ + typography: { + fluid: { + arial: { + value: { + minWidth: 320, + minFontSize: 14, + minTypeScale: 1.25, + maxWidth: 1435, + maxFontSize: 16, + maxTypeScale: 1.25, + positiveSteps: 5, + negativeSteps: 3, + }, + }, + }, + }, + spacing: { + custom: { + size: { + value: { + 2: "0.5rem", + 3: "0.75rem", + }, }, }, }, @@ -427,9 +751,9 @@ export default defineConfig({ padding: "var(--2) var(--3)", }, variables: { - "base": "typography.arial.typescale@base", - "2": "spacing.size.value.2", - "3": "spacing.size.value.3", + "base": "typography_fluid.arial@base", + "2": "spacing.custom.size.value.2", + "3": "spacing.custom.size.value.3", }, }, }, @@ -438,33 +762,34 @@ export default defineConfig({ }); ``` -This will generate the following css : +This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { - /*____ Spacing ____*/ - --size-2: 0.5rem; - --size-3: 0.75rem; - /*____ Typography ____*/ - --typography-arial-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); - --typography-arial-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); - --typography-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); - --typography-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); - --typography-arial-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); - --typography-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); - --typography-arial-sm: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); - --typography-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); - --typography-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem); - /*____ Primitives ____*/ - /* button */ - --button-small-width: 7.5rem; - --button-small-height: 2.5rem; - --button-small-fontSize: var(--typography-arial-base); - --button-small-radius: 0.5rem; - --button-small-padding: var(--size-2) var(--size-3); +/*____ Spacing ____*/ +--spacing-size-2: 0.5rem; +--spacing-size-3: 0.75rem; +/*____ Typography ____*/ +--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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); +--typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); +--typography_fluid-arial-sm: 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); +/*____ Primitives ____*/ +/* button */ +--button-small-width: 7.5rem; +--button-small-height: 2.5rem; +--button-small-fontSize: var(--typography_fluid-arial-base); +--button-small-radius: 0.5rem; +--button-small-padding: var(--spacing-size-2) var(--spacing-size-3); } ``` + ## CLI Usage @@ -500,8 +825,14 @@ const css = generateCSS(config); Check out our examples: - [Basic Setup](./examples/basic) +- [] Tailwind + +## TODO -TODO Examples : [] Nuxt [] Tailwind [] Vite +- [] Custom Media Queries https://www.w3.org/TR/mediaqueries-5/#at-ruledef-custom-media +- [] Line Height +- [] Bundlers Plugin (Vite, Rollup, Webpack ...) +- [] Nuxt Module ## License diff --git a/deno.json b/deno.json index 44c8df9..325fc61 100644 --- a/deno.json +++ b/deno.json @@ -14,8 +14,10 @@ "citty": "npm:citty@0.1.6", "chokidar": "npm:chokidar@4.0.3", "@std/collections": "jsr:@std/collections@^1.1.3", + "@std/assert": "jsr:@std/assert@^1.0.14", "colorjs.io": "npm:colorjs.io@0.5.2", - "utopia-core": "npm:utopia-core@1.6.0" + "utopia-core": "npm:utopia-core@1.6.0", + "mdbox": "npm:mdbox@0.1.1" }, "publish": { "include": [ @@ -26,6 +28,6 @@ "tasks": { "cli": "deno run -A ./src/cli.ts", "example:cli": "deno run -A ./src/cli.ts --prefix ./example/basic --mode css", - "test:cli": "deno run -A ./src/cli.ts" + "readme:update": "deno run -A ./scripts/update-readme.ts" } } diff --git a/deno.lock b/deno.lock index c2ce327..1b29ffc 100644 --- a/deno.lock +++ b/deno.lock @@ -1,27 +1,60 @@ { - "version": "4", + "version": "5", "specifiers": { "jsr:@std/assert@*": "1.0.9", + "jsr:@std/assert@^1.0.14": "1.0.14", "jsr:@std/collections@^1.1.3": "1.1.3", + "jsr:@std/fs@1": "1.0.19", + "jsr:@std/internal@^1.0.10": "1.0.10", "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", "npm:@changesets/cli@*": "2.29.6", + "npm:@types/node@*": "24.2.0", "npm:chokidar@4.0.3": "4.0.3", "npm:citty@0.1.6": "0.1.6", "npm:colorjs.io@0.5.2": "0.5.2", + "npm:mdbox@0.1.1": "0.1.1", "npm:utopia-core@1.6.0": "1.6.0" }, "jsr": { "@std/assert@1.0.9": { "integrity": "a9f0c611a869cc791b26f523eec54c7e187aab7932c2c8e8bea0622d13680dcd", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.5" + ] + }, + "@std/assert@1.0.14": { + "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", + "dependencies": [ + "jsr:@std/internal@^1.0.10" ] }, "@std/collections@1.1.3": { "integrity": "bf8b0818886df6a32b64c7d3b037a425111f28278d69fd0995aeb62777c986b0" }, + "@std/fs@1.0.19": { + "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", + "dependencies": [ + "jsr:@std/internal@^1.0.9", + "jsr:@std/path@^1.1.1" + ] + }, "@std/internal@1.0.5": { "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/internal@1.0.10": { + "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" + }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + }, + "@std/path@1.1.2": { + "integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", + "dependencies": [ + "jsr:@std/internal@^1.0.10" + ] } }, "npm": { @@ -94,7 +127,8 @@ "semver", "spawndamnit", "term-size" - ] + ], + "bin": true }, "@changesets/config@3.1.1": { "integrity": "sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==", @@ -214,7 +248,7 @@ "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", "dependencies": [ "@babel/runtime", - "@types/node", + "@types/node@12.20.55", "find-up", "fs-extra@8.1.0" ] @@ -250,6 +284,12 @@ "@types/node@12.20.55": { "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, + "@types/node@24.2.0": { + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dependencies": [ + "undici-types" + ] + }, "ansi-colors@4.1.3": { "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" }, @@ -326,7 +366,8 @@ ] }, "esprima@4.0.1": { - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": true }, "extendable-error@0.1.7": { "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==" @@ -397,7 +438,8 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "human-id@4.1.1": { - "integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==" + "integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==", + "bin": true }, "iconv-lite@0.6.3": { "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", @@ -437,11 +479,12 @@ "dependencies": [ "argparse", "esprima" - ] + ], + "bin": true }, "jsonfile@4.0.0": { "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dependencies": [ + "optionalDependencies": [ "graceful-fs" ] }, @@ -454,6 +497,15 @@ "lodash.startcase@4.4.0": { "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==" }, + "md4w@0.2.7": { + "integrity": "sha512-lFM7vwk3d4MzkV2mija7aPkK6OjKXZDQsH2beX+e2cvccBoqc6RraheMtAO0Wcr/gjj5L+WS5zhb+06AmuGZrg==" + }, + "mdbox@0.1.1": { + "integrity": "sha512-jvLISenzbLRPWWamTG3THlhTcMbKWzJQNyTi61AVXhCBOC+gsldNTUfUNH8d3Vay83zGehFw3wZpF3xChzkTIQ==", + "dependencies": [ + "md4w" + ] + }, "merge2@1.4.1": { "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, @@ -516,7 +568,8 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "prettier@2.8.8": { - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==" + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "bin": true }, "queue-microtask@1.2.3": { "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" @@ -549,7 +602,8 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver@7.7.1": { - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": true }, "shebang-command@2.0.0": { "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", @@ -594,6 +648,9 @@ "is-number" ] }, + "undici-types@7.10.0": { + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" + }, "universalify@0.1.2": { "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, @@ -604,15 +661,18 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ "isexe" - ] + ], + "bin": true } }, "workspace": { "dependencies": [ + "jsr:@std/assert@^1.0.14", "jsr:@std/collections@^1.1.3", "npm:chokidar@4.0.3", "npm:citty@0.1.6", "npm:colorjs.io@0.5.2", + "npm:mdbox@0.1.1", "npm:utopia-core@1.6.0" ] } diff --git a/example/basic/.cssforge/output.css b/example/basic/.cssforge/output.css index 5741ed9..7f86092 100644 --- a/example/basic/.cssforge/output.css +++ b/example/basic/.cssforge/output.css @@ -1,21 +1,32 @@ /*____ CSSForge ____*/ :root { - /*____ Colors ____*/ - /* Palette */ - --color-simple-white: oklch(100% 0 0); - --color-simple-black: oklch(0% 0 0); - --color-simple-green: oklch(86.644% 0.29483 142.49535); - --color-simple-blue: oklch(45.201% 0.31321 264.05202); - --color-simple-violet: oklch(70% 0.2 270); - --color-simple-red: oklch(62.796% 0.25768 29.23388); - /* Gradients */ - --gradient-white-green-primary: linear-gradient( - to right, - var(--color-simple-white), - var(--color-simple-green) - ); - /* Theme: light */ - /* background */ - --theme-light-background-primary: var(--color-simple-white); - --theme-light-background-secondary: var(--gradient-white-green-primary); -} +/*____ Colors ____*/ +/* Palette */ +--palette-coral-100: oklch(73.511% 0.16799 40.24666); +--palette-mint-100: oklch(80.035% 0.18206 151.71104); +--palette-indigo-100: oklch(51.057% 0.23005 276.96564); +/*____ 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); +--spacing_fluid-base-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); +--spacing_fluid-base-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); +--spacing_fluid-base-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); +--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-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); +} \ No newline at end of file diff --git a/example/basic/cssforge.config.ts b/example/basic/cssforge.config.ts index 7c18100..f46b7b3 100644 --- a/example/basic/cssforge.config.ts +++ b/example/basic/cssforge.config.ts @@ -3,18 +3,32 @@ import { defineConfig } from "../../src/mod.ts"; export default defineConfig( { spacing: { - size: { - value: { - 1: "0.25rem", - 2: "0.5rem", - 3: "0.75rem", - 4: "1rem", + fluid: { + base: { + value: { + minSize: 4, + maxSize: 24, + minWidth: 320, + maxWidth: 1280, + positiveSteps: [1.5, 2, 3, 4, 6], + negativeSteps: [0.75, 0.5, 0.25], + }, + }, + }, + custom: { + size: { + value: { + 1: "0.25rem", + 2: "0.5rem", + 3: "0.75rem", + 4: "1rem", + }, }, }, }, typography: { - default: { - typescale: { + font: { + fluid: { value: { minWidth: 320, minFontSize: 14, diff --git a/package.json b/package.json index 4095a91..5ae5b3f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,6 @@ "version": "0.1.1", "type": "module", "scripts": { - "version": "npx @changesets/cli version && node sync-version.js" + "version": "npx @changesets/cli version && node scripts/sync-version.js" } } diff --git a/sync-version.js b/scripts/sync-version.js similarity index 100% rename from sync-version.js rename to scripts/sync-version.js diff --git a/scripts/update-readme.ts b/scripts/update-readme.ts new file mode 100644 index 0000000..983d6e9 --- /dev/null +++ b/scripts/update-readme.ts @@ -0,0 +1,191 @@ +/** + * Script to parse README.md and generate documentation within delimiters. + * + * Delimiters: + * startDefinition: + * end: + */ + +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; +import { md } from "mdbox"; + +const README_PATH = join(Deno.cwd(), "README.md"); + +const START_DEFINITION = ""; // marks end of TS header section +const END_BLOCK = ""; // marks end of whole block + +// Template generator +function buildReplacement(tsSource: string, css: string): string { + const tsBlock = md.codeBlock(tsSource.trim(), "typescript"); + const cssBlock = md.codeBlock(css.trim(), "css"); + // Only return the inner content to place between START_CODE and END_BLOCK + return [ + tsBlock, + "", + "This will generate the following CSS :", + "", + cssBlock, + ].join("\n"); +} + +function wrapSource(tsSource: string): string { + const hasDefaultExport = /export\s+default\s+/m.test(tsSource); + const hasDefineConfigCall = /defineConfig\s*\(/m.test(tsSource); + const hasConfigVar = /\bconst\s+config\b|\blet\s+config\b|\bvar\s+config\b/m.test( + tsSource, + ); + const srcModUrl = pathToFileURL(join(Deno.cwd(), "src", "mod.ts")).href; + const importLine = `import { defineConfig } from "${srcModUrl}";\n`; + + // Normalize to create a default export expression + let normalized = tsSource.trim(); + if (!hasDefaultExport) { + if (hasConfigVar) { + normalized = `${normalized}\nexport default config;`; + } else if (hasDefineConfigCall) { + // The snippet likely ends with defineConfig({...}) without export; wrap by assigning to config + normalized = `const config = (${ + normalized.replace(/;?\s*$/, "") + });\nexport default config;`; + } else { + // As a last resort, try to wrap the whole snippet into defineConfig + normalized = `const config = defineConfig(${normalized});\nexport default config;`; + } + } + + return `${importLine}${normalized}\n`; +} + +// Parse all blocks functionally +async function processReadme(): Promise { + const original = await Deno.readTextFile(README_PATH); + const lines = original.split(/\r?\n/); + + type Block = { startIdx: number; codeIdx: number; endIdx: number }; + const blocks: Block[] = []; + + // Single pass to collect indices + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith(START_DEFINITION)) { + let codeIdx = -1; + let endIdx = -1; + for (let j = i + 1; j < lines.length; j++) { + if (lines[j].trim() === START_CODE) { + codeIdx = j; + } else if (lines[j].trim() === END_BLOCK) { + endIdx = j; + break; + } + } + if (codeIdx !== -1 && endIdx !== -1) { + blocks.push({ startIdx: i, codeIdx, endIdx }); + i = endIdx; // jump forward + } + } + } + + if (blocks.length === 0) { + console.log("No md:generate blocks found."); + return false; + } + + // We'll rebuild the README with replacements + const resultLines: string[] = []; + let cursor = 0; + + // Prepare temp dir + const tempRoot = await Deno.makeTempDir({ prefix: "cssforge-readme-" }); + // temp dir already created by makeTempDir + + for (const block of blocks) { + // Push lines before the block untouched + while (cursor < block.startIdx) { + resultLines.push(lines[cursor++]); + } + + // Extract TS between startDefinition (exclusive) and START_CODE (exclusive) + const tsPreLines = lines.slice(block.startIdx + 1, block.codeIdx); + const tsSource = tsPreLines.filter((l) => !l.trim().startsWith("```")).join("\n") + .trim(); + + // Create a temporary config file containing the snippet and an import for defineConfig. + const tempConfigPath = join(tempRoot, `config-${crypto.randomUUID()}.ts`); + + const wrappedSource = wrapSource(tsSource); + + await Deno.writeTextFile(tempConfigPath, wrappedSource); + + // Run CLI to generate CSS output into a temp file + const tempCssPath = join(tempRoot, `output-${crypto.randomUUID()}.css`); + const cliPath = join(Deno.cwd(), "src", "cli.ts"); + const cmd = new Deno.Command("deno", { + args: [ + "run", + "-A", + cliPath, + "--config", + tempConfigPath, + "--mode", + "css", + "--css", + tempCssPath, + ], + stdout: "piped", + stderr: "piped", + }); + const res = await cmd.output(); + if (!res.success) { + const stdErr = new TextDecoder().decode(res.stderr); + console.error("CLI error:\n", stdErr); + throw new Error("CLI execution failed"); + } + const css = await Deno.readTextFile(tempCssPath); + + // Build newly generated content (to be placed between START_CODE and END_BLOCK) + const replacement = buildReplacement(tsSource, css); + + // Keep startDefinition line + resultLines.push(lines[block.startIdx]); + // Preserve original snippet as source of truth + for (let k = block.startIdx + 1; k < block.codeIdx; k++) { + resultLines.push(lines[k]); + } + // Keep START_CODE line + resultLines.push(lines[block.codeIdx]); + // Insert fresh generated content (replace anything previously between START_CODE and END_BLOCK) + if (replacement.length) { + resultLines.push(replacement); + } + // Keep END_BLOCK line + resultLines.push(lines[block.endIdx]); + + cursor = block.endIdx + 1; // Skip past the entire old block + } + + // Append any trailing lines + while (cursor < lines.length) { + resultLines.push(lines[cursor++]); + } + + // Cleanup temp dir + try { + await Deno.remove(tempRoot, { recursive: true }); + } catch (e) { + console.warn("Failed to remove temp directory", e); + } + + const updated = resultLines.join("\n"); + if (updated !== original) { + await Deno.writeTextFile(README_PATH, updated); + console.log("README.md updated successfully."); + return true; + } else { + console.log("README.md had no changes."); + return false; + } +} + +await processReadme(); diff --git a/src/cli.ts b/src/cli.ts index 7d99fd8..fc5cf4e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -179,8 +179,8 @@ const mainCommand = defineCommand({ const cleanup = await watch(settings); // Handle process termination - process.on("SIGINT", async () => { - await cleanup(); + process.on("SIGINT", () => { + cleanup(); console.log("\nšŸ›‘ Stopped watching"); process.exit(0); }); diff --git a/src/config.ts b/src/config.ts index 79fa937..f0d43a7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,8 +42,8 @@ export interface CSSForgeConfig { }, }, typography: { - default: { - typescale: { + fluid: { + base: { value: { minWidth: 320, minFontSize: 14, @@ -56,6 +56,9 @@ export interface CSSForgeConfig { }, }, }, + weight: { + arial: { value: { regular: "400", bold: "700" } }, + }, }, colors: { palette: { @@ -76,8 +79,8 @@ export interface CSSForgeConfig { ); * ``` */ -export function defineConfig( - config: Partial, +export function defineConfig>( + config: C, ): C { return config as C; } diff --git a/src/helpers.ts b/src/helpers.ts index 66e796e..6174a4d 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,3 +1,11 @@ +const reservedKeyWords = [ + "spacing", + "spacing_fluid", + "typography", + "typography_fluid", + "theme", + "color", +]; /** * Validates a name to ensure it doesn't contain invalid characters. * Throws an error if the name is invalid. @@ -8,11 +16,20 @@ * ``` */ export function validateName(name: string): boolean { - if (name.length === 0 || name.includes(".") || name === "value") { + if ( + name.length === 0 || name.includes(".") || name === "value" + ) { throw new Error( `Invalid name: ${name}. Names must be non-empty strings that do not contain periods.`, ); } + if (reservedKeyWords.includes(name)) { + throw new Error( + `Invalid name: ${name}. Names cannot be one of the reserved keywords: ${ + reservedKeyWords.join(", ") + }`, + ); + } return true; } diff --git a/src/lib.ts b/src/lib.ts index f2cf7a6..bc8e1e5 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -18,6 +18,23 @@ export interface Output { resolveMap: ResolveMap; } +export interface Variables { + [key: string]: string; +} + +interface Modules { + colors?: Output | null; + typography?: Output | null; + spacing?: Output | null; +} + +interface ResolveVariableParams extends Modules { + varPath: string; +} + +interface GetResolvedVariablesMapParams extends Modules { + variables: Variables | undefined; +} /** * Resolves a variable path to its corresponding CSS variable name. * This function is used to resolve references to other design tokens. @@ -32,19 +49,25 @@ export interface Output { * // resolved: "--color-red-100" * ``` */ -export function resolveVariable( - { varPath, colors, typography, spacing }: { - varPath: string; - colors?: Output | null; - typography?: Output | null; - spacing?: Output | null; - }, +function resolveVariable( + { varPath, colors, typography, spacing }: ResolveVariableParams, ) { const path = varPath.split("."); const module = path[0]; - + //TODO: Make this configurable + const keyMap = { + palette: "palette", + gradients: "gradients", + theme: "theme", + typography: "typography", + typography_fluid: "typography_fluid", + spacing: "spacing", + spacing_fluid: "spacing_fluid", + }; switch (module) { - case "colors": { + case keyMap.palette: + case keyMap.gradients: + case keyMap.theme: { if (!colors) throw new Error("The colors object must be passed."); const result = colors.resolveMap.get(varPath); if (!result) { @@ -52,7 +75,8 @@ export function resolveVariable( } return result.key; } - case "typography": { + case keyMap.typography_fluid: + case keyMap.typography: { if (!typography) throw new Error("The typography object must be passed."); const result = typography.resolveMap.get(varPath); if (!result) { @@ -64,11 +88,16 @@ export function resolveVariable( } return result.key; } - case "spacing": { + case keyMap.spacing: + case keyMap.spacing_fluid: { if (!spacing) throw new Error("The spacing object must be passed."); const result = spacing.resolveMap.get(varPath); if (!result) { - throw new Error(`The spacing path ${varPath} could not be resolved.`); + throw new Error( + `The spacing path ${varPath} could not be resolved. Map contains ${ + Array.from(spacing.resolveMap.keys()).map((key) => `\n${key}`) + }`, + ); } return result.key; } @@ -76,3 +105,36 @@ export function resolveVariable( throw new Error(`${module} is not implemented and can't be resolved.`); } } + +/** + * Generates a map of resolved CSS variable names from a variables object. + * It uses the provided modules (colors, typography, spacing) to resolve the paths. + */ +export const getResolvedVariablesMap = ( + { variables, ...params }: GetResolvedVariablesMapParams, +): Map<`--${string}`, string> => { + const resolvedVariables = variables + ? Object.entries(variables).map( + ([varKey, varPath]) => { + const resolved = resolveVariable({ varPath, ...params }); + return [`--${varKey}`, resolved] as const; + }, + ) + : []; + + const resolvedMap = new Map(resolvedVariables); + return resolvedMap; +}; + +/** + * Resolves a CSS variable value using a map of variable paths. + * It replaces occurrences of `var(--key)` in the value with the corresponding + * CSS variable from the map. + */ +export const resolveValue = ( + { map, value }: { map: Map; value: string }, +) => + value.replace( + /var\(--(\w+)\)/g, + (_, key: string) => `var(${map.get(`--${key}`) ?? `--${key}`})`, + ); diff --git a/src/modules/colors.ts b/src/modules/colors.ts index 86318f6..c5dd85b 100644 --- a/src/modules/colors.ts +++ b/src/modules/colors.ts @@ -1,6 +1,8 @@ import Color from "colorjs.io"; -import { type Output, type ResolveMap, resolveVariable } from "../lib.ts"; +import { type Output, type ResolveMap, resolveValue } from "../lib.ts"; import { validateName } from "../helpers.ts"; +import { getResolvedVariablesMap } from "../lib.ts"; +import type { Variables } from "../lib.ts"; type ExactlyOne = { [K in keyof T]: @@ -34,10 +36,6 @@ export interface ColorPalette { [key: string]: ColorVariants; } -interface Variables { - [key: string]: string; -} - interface GradientDefinition { value: string; variables?: Variables; @@ -183,18 +181,19 @@ export function processColors(colors: ColorConfig): Output { const cssOutput: string[] = []; const resolveMap: ResolveMap = new Map(); cssOutput.push(`/* Palette */`); + const moduleKey = "palette"; for (const [colorName, variants] of Object.entries(colors.palette.value)) { validateName(colorName); for (const [variantId, colorValue] of Object.entries(variants)) { try { validateName(variantId); - const key = `--color-${colorName}-${variantId}`; + const key = `--${moduleKey}-${colorName}-${variantId}`; const value = colorValueToOklch(colorValue); const variable = `${key}: ${value};`; cssOutput.push(variable); resolveMap.set( - `colors.palette.value.${colorName}.${variantId}`, + `${moduleKey}.value.${colorName}.${variantId}`, { key, value, variable }, ); } catch (error) { @@ -208,6 +207,7 @@ export function processColors(colors: ColorConfig): Output { if (colors.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); @@ -218,30 +218,17 @@ export function processColors(colors: ColorConfig): Output { ) { validateName(variantName); try { - const resolvedVariables = variables - ? Object.entries(variables).map( - ([varKey, varPath]) => { - const resolved = resolveVariable({ - varPath, - colors: palette, - }); - return [`--${varKey}`, resolved] as const; - }, - ) - : []; + const resolvedMapForGradient = getResolvedVariablesMap({ + variables, + colors: palette, + }); - const resolvedMapForGradient = new Map(resolvedVariables); + const gradientValue = resolveValue({ map: resolvedMapForGradient, value }); - const gradientValue = value.replace( - /var\(--(\w+)\)/g, - (_, key: string) => - `var(${resolvedMapForGradient.get(`--${key}`) ?? `--${key}`})`, - ); - - const key = `--gradient-${gradientName}-${variantName}`; + const key = `--${moduleKey}-${gradientName}-${variantName}`; const variable = `${key}: ${gradientValue};`; cssOutput.push(variable); - resolveMap.set(`colors.gradients.value.${gradientName}.${variantName}`, { + resolveMap.set(`${moduleKey}.value.${gradientName}.${variantName}`, { variable, key, value: gradientValue, @@ -258,6 +245,7 @@ export function processColors(colors: ColorConfig): Output { } if (colors.theme) { + const moduleKey = "theme"; for (const [themeName, theme] of Object.entries(colors.theme.value)) { validateName(themeName); cssOutput.push(`/* Theme: ${themeName} */`); @@ -269,19 +257,10 @@ export function processColors(colors: ColorConfig): Output { validateName(colorName); cssOutput.push(`/* ${colorName} */`); try { - const resolvedVariables = variables - ? Object.entries(variables).map( - ([varKey, varPath]) => { - const resolved = resolveVariable({ - varPath, - colors: palette, - }); - return [`--${varKey}`, resolved] as const; - }, - ) - : []; - - const resolvedMap = new Map(resolvedVariables); + const resolvedMap = getResolvedVariablesMap({ + variables, + colors: palette, + }); for ( const [variantName, variantValue] of Object.entries( @@ -289,16 +268,13 @@ export function processColors(colors: ColorConfig): Output { ) ) { validateName(variantName); - const resolvedValue = variantValue.replace( - /var\(--(\w+)\)/g, - (_, key: string) => `var(${resolvedMap.get(`--${key}`) ?? `--${key}`})`, - ); + const resolvedValue = resolveValue({ map: resolvedMap, value: variantValue }); - const key = `--theme-${themeName}-${colorName}-${variantName}`; + const key = `--${moduleKey}-${themeName}-${colorName}-${variantName}`; const variable = `${key}: ${resolvedValue};`; cssOutput.push(`${key}: ${resolvedValue};`); resolveMap.set( - `colors.theme.value.${themeName}.${colorName}.${variantName}`, + `${moduleKey}.value.${themeName}.${colorName}.${variantName}`, { key, value: resolvedValue, variable }, ); } diff --git a/src/modules/primitive.ts b/src/modules/primitive.ts index 853b23d..47ee552 100644 --- a/src/modules/primitive.ts +++ b/src/modules/primitive.ts @@ -4,14 +4,15 @@ import type { TypographyConfig } from "./typography.ts"; import { processTypography } from "./typography.ts"; import type { PixelSettings, SpacingConfig } from "./spacing.ts"; import { processSpacing } from "./spacing.ts"; -import { type Output, resolveVariable } from "../lib.ts"; +import { + getResolvedVariablesMap, + type Output, + resolveValue, + type Variables, +} from "../lib.ts"; import { validateName } from "../helpers.ts"; import { pxToRem } from "../helpers.ts"; -interface Variables { - [key: string]: string; -} - interface PrimitiveProperties { [key: string]: string; } @@ -85,12 +86,12 @@ export function processPrimitives( ): Output { const cssOutput: string[] = []; const resolveMap = new Map(); + const moduleKey = "primitives"; const colorVariables = config.colors ? processColors(config.colors) : null; const typographyVariables = config.typography ? processTypography(config.typography) : null; const spacingVariables = config.spacing ? processSpacing(config.spacing) : null; - for (const [primitiveName, primitive] of Object.entries(config.primitives)) { validateName(primitiveName); cssOutput.push(`/* ${primitiveName} */`); @@ -104,21 +105,12 @@ export function processPrimitives( ) { validateName(variantName); try { - const resolvedVariables = variables - ? Object.entries(variables).map( - ([varKey, varPath]) => { - const resolved = resolveVariable({ - varPath, - colors: colorVariables, - typography: typographyVariables, - spacing: spacingVariables, - }); - return [`--${varKey}`, resolved] as const; - }, - ) - : []; - - const resolvedMap = new Map(resolvedVariables); + const resolvedMap = getResolvedVariablesMap({ + variables, + colors: colorVariables, + typography: typographyVariables, + spacing: spacingVariables, + }); for (const [propName, propValue] of Object.entries(properties)) { validateName(propName); @@ -127,19 +119,19 @@ export function processPrimitives( ? pxToRem({ value: propValue, rem: settings.rem }) : propValue; - const resolvedValue = convertedValue.replace( - /var\(--(\w+)\)/g, - (_, key: string) => `var(${resolvedMap.get(`--${key}`) ?? `--${key}`})`, - ); + const resolvedValue = resolveValue({ map: resolvedMap, value: convertedValue }); const key = `--${primitiveName}-${variantName}-${propName}`; const variable = `${key}: ${resolvedValue};`; cssOutput.push(variable); - resolveMap.set(`primitives.${primitiveName}.value.${variantName}.${propName}`, { - key, - value: resolvedValue, - variable, - }); + resolveMap.set( + `${moduleKey}.${primitiveName}.value.${variantName}.${propName}`, + { + key, + value: resolvedValue, + variable, + }, + ); } } catch (error) { console.error( diff --git a/src/modules/spacing.ts b/src/modules/spacing.ts index 96ae7b7..4261c25 100644 --- a/src/modules/spacing.ts +++ b/src/modules/spacing.ts @@ -1,6 +1,17 @@ +import type { UtopiaSpaceConfig } from "utopia-core"; +import { calculateSpaceScale } from "utopia-core"; import { pxToRem, validateName } from "../helpers.ts"; import type { Output, ResolveMap } from "../lib.ts"; +export interface FluidSpaceConfig { + value: UtopiaSpaceConfig & { + /** + * An optional prefix for the generated CSS variables. + */ + prefix?: string; + }; +} + type SpacingValue = string; interface SpacingScale { @@ -27,51 +38,88 @@ export interface PixelSettings { * The spacing configuration object. */ export interface SpacingConfig { - [key: string]: { value: SpacingScale; settings?: PixelSettings }; + /** + * Fluid spacing scales powered by utopia `calculateSpaceScale`. + * Each key defines one scale. Tokens are derived from the returned sizes' labels. + */ + fluid?: { + [key: string]: FluidSpaceConfig; + }; + /** + * Static spacing scales defined under the `custom` key. + * Each scale's `value` map contains token -> size (string) pairs. + */ + custom?: { + [key: string]: { value: SpacingScale; settings?: PixelSettings }; + }; } /** - * Processes the spacing configuration to generate CSS variables. + * Convert spacing configuration into CSS custom properties. + * + * Generated variable naming: + * - Custom scale: `--spacing-{scale}-{token}` (renamed from --space-) + * - Fluid scale: `--spacing_fluid-{prefixOrScale}-{token}` (renamed from --fluidspace-) + + * * @example - * ```ts - * const spacing = { - * size: { - * value: { - * 1: "0.25rem", - * 2: "0.5rem", - * }, - * }, - * }; - * const output = processSpacing(spacing); - * // output.css: "--size-1: 0.25rem;\n--size-2: 0.5rem;" - * ``` + * // Static + fluid combined + * const { css } = processSpacing({ + * fluid: { + * base: { value: { minSize: 4, maxSize: 24, minWidth: 320, maxWidth: 1280, negativeSteps: 0, positiveSteps: 2, prefix: "space" } } + * }, + * custom: { + * gap: { value: { 1: "4px", 2: "8px" } } + * } + * }); */ export function processSpacing(spacing: SpacingConfig): Output { const cssOutput: string[] = []; const resolveMap: ResolveMap = new Map(); - for ( - const [scaleName, { value, settings = { pxToRem: true, rem: 16 } }] of Object.entries( - spacing, - ) - ) { - validateName(scaleName); - for (const [scaleKey, scaleValue] of Object.entries(value)) { - validateName(scaleKey); - - const convertedValue = settings.pxToRem - ? pxToRem({ value: scaleValue, rem: settings.rem }) - : scaleValue; + if (spacing.fluid) { + const moduleKey = "spacing_fluid"; + for (const [scaleName, { value }] of Object.entries(spacing.fluid)) { + validateName(scaleName); + const { prefix, ...utopiaConfig } = value; + if (prefix) validateName(prefix); - const key = `--${scaleName}-${scaleKey}`; - const variable = `${key}: ${convertedValue};`; + const scale = calculateSpaceScale(utopiaConfig); + for (const { label, clamp } of scale.sizes) { + const resolvedPrefix = prefix ? `${scaleName}-${prefix}` : scaleName; + const variableName = `--${moduleKey}-${resolvedPrefix}-${label}`; + const cssVar = `${variableName}: ${clamp};`; + cssOutput.push(cssVar); + resolveMap.set(`${moduleKey}.${scaleName}@${label}`, { + variable: cssVar, + key: variableName, + value: clamp, + }); + } + } + } - cssOutput.push(variable); - resolveMap.set(`spacing.${scaleName}.value.${scaleKey}`, { - variable, - key, - value: convertedValue, - }); + if (spacing.custom) { + const moduleKey = "spacing"; + for ( + const [scaleName, { value, settings = { pxToRem: true, rem: 16 } }] of Object + .entries(spacing.custom) + ) { + validateName(scaleName); + for (const [scaleKey, scaleValue] of Object.entries(value)) { + validateName(scaleKey); + const convertedValue = settings.pxToRem + ? pxToRem({ value: scaleValue, rem: settings.rem }) + : scaleValue; + const varName = `--${moduleKey}-${scaleName}-${scaleKey}`; + const variable = `${varName}: ${convertedValue};`; + cssOutput.push(variable); + resolveMap.set(`${moduleKey}.custom.${scaleName}.value.${scaleKey}`, { + variable, + key: varName, + value: convertedValue, + }); + } } } diff --git a/src/modules/typography.ts b/src/modules/typography.ts index d4c81f6..13287d6 100644 --- a/src/modules/typography.ts +++ b/src/modules/typography.ts @@ -2,10 +2,7 @@ import { calculateTypeScale, type UtopiaTypeConfig } from "utopia-core"; import type { Output, ResolveMap } from "../lib.ts"; import { validateName } from "../helpers.ts"; -/** - * The definition for a type scale. - */ -export interface TypeScaleDefinition { +export interface FluidTypeScaleDefinition { /** * The configuration for the type scale generation. */ @@ -32,83 +29,88 @@ interface TypographyWeight { }; } -/** - * The typography configuration object. - */ export interface TypographyConfig { - [key: string]: { - /** - * A collection of font weights. - */ - weight?: TypographyWeight; - /** - * The type scale definition. - */ - typescale?: TypeScaleDefinition; - }; + /** + * A fluid typescale definition. + */ + fluid?: { [scaleName: string]: FluidTypeScaleDefinition }; + /** + * A collection of font weights. + */ + weight?: { [groupName: string]: TypographyWeight }; } /** - * Processes the typography configuration to generate CSS variables for type scales and weights. + * Generate CSS Custom Properties for typography. + * * @example * ```ts - * const typography = { - * default: { - * typescale: { - * value: { - * minWidth: 320, - * minFontSize: 14, - * minTypeScale: 1.25, - * maxWidth: 1435, - * maxFontSize: 16, - * maxTypeScale: 1.25, - * positiveSteps: 1, - * negativeSteps: 1, - * }, - * }, - * weight: { - * value: { - * regular: "400", - * }, - * }, - * }, + * const typography: TypographyConfig = { + * fluid: { + * base: { + * value: { + * minWidth: 320, + * minFontSize: 14, + * minTypeScale: 1.25, + * maxWidth: 1435, + * maxFontSize: 16, + * maxTypeScale: 1.25, + * positiveSteps: 2, + * negativeSteps: 1, + * prefix: "text", + * }, + * }, + * }, + * weight: { arial: { value: { regular: "400", bold: "700" } } }, * }; - * const output = processTypography(typography); - * // output.css: "--typography-default-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem);\n--typography-default-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem);\n--typography-default-sm: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem);\n--weight-default-regular: 400;" + * const { css } = processTypography(typography); * ``` */ export function processTypography(config: TypographyConfig): Output { const cssOutput: string[] = []; const resolveMap: ResolveMap = new Map(); - for (const [typography, { typescale, weight }] of Object.entries(config)) { - validateName(typography); - if (typescale) { + + if (config.fluid) { + const moduleKey = "typography_fluid"; + for (const [scaleName, definition] of Object.entries(config.fluid)) { + validateName(scaleName); + const { value, settings } = definition; + const { prefix, ...utopiaConfig } = value; + if (prefix) validateName(prefix); + const scale = calculateTypeScale({ - labelStyle: typescale.settings?.customLabel ? "utopia" : "tailwind", - ...typescale.value, + labelStyle: settings?.customLabel ? "utopia" : "tailwind", + ...utopiaConfig, }); - const prefix = typescale.value.prefix || typography; - for (const { label: baseLabel, clamp } of scale) { - const label = typescale.settings?.customLabel - ? typescale.settings.customLabel[baseLabel] ?? baseLabel - : baseLabel; - const key = `--typography-${prefix}-${label}`; + + const resolvedPrefix = prefix ? `${scaleName}-${prefix}` : scaleName; + for (const { label, clamp } of scale) { + const resolvedLabel = settings?.customLabel + ? settings.customLabel[label] ?? label + : label; + const key = `--${moduleKey}-${resolvedPrefix}-${resolvedLabel}`; const variable = `${key}: ${clamp};`; cssOutput.push(variable); - resolveMap.set(`typography.${typography}.typescale@${label}`, { + resolveMap.set(`${moduleKey}.${scaleName}@${resolvedLabel}`, { variable, key, value: clamp, }); } } - if (weight) { - for (const [weightName, weightValue] of Object.entries(weight.value)) { - validateName(weightName); - const key = `--weight-${typography}-${weightName}`; + } + + // Weights + if (config.weight) { + const moduleKey = "typography"; + for (const [weightName, { value }] of Object.entries(config.weight)) { + validateName(weightName); + for (const [token, weightValue] of Object.entries(value)) { + validateName(token); + const key = `--${moduleKey}-weight-${weightName}-${token}`; const variable = `${key}: ${weightValue};`; cssOutput.push(variable); - resolveMap.set(`typography.${typography}.weight.value.${weightName}`, { + resolveMap.set(`${moduleKey}.weight.${weightName}.value.${token}`, { variable, key, value: weightValue, @@ -116,6 +118,6 @@ export function processTypography(config: TypographyConfig): Output { } } } - const output = { css: cssOutput.join("\n"), resolveMap }; - return output; + + return { css: cssOutput.join("\n"), resolveMap }; } diff --git a/tests/colors.test.ts b/tests/colors.test.ts index a41cd42..4d430af 100644 --- a/tests/colors.test.ts +++ b/tests/colors.test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "jsr:@std/assert"; +import { assertEquals } from "@std/assert"; import { defineConfig, processColors } from "../src/mod.ts"; import { getLines } from "./helpers.ts"; @@ -20,11 +20,11 @@ Deno.test("processColors - converts hex to oklch", () => { const lines = getLines(css); assertEquals( lines[1].trim(), - "--color-coral-100: oklch(73.511% 0.16799 40.24666);", + "--palette-coral-100: oklch(73.511% 0.16799 40.24666);", ); assertEquals( lines[2].trim(), - "--color-coral-200: oklch(69.622% 0.19552 32.32143);", + "--palette-coral-200: oklch(69.622% 0.19552 32.32143);", ); }); @@ -50,16 +50,16 @@ Deno.test("processColors - handles different color formats", () => { assertEquals( lines[1].trim(), - "--color-brand-200: oklch(86.644% 0.29483 142.49535);", + "--palette-brand-200: oklch(86.644% 0.29483 142.49535);", ); assertEquals( lines[2].trim(), - "--color-brand-300: oklch(45.201% 0.31321 264.05202);", + "--palette-brand-300: oklch(45.201% 0.31321 264.05202);", ); - assertEquals(lines[3].trim(), "--color-brand-400: oklch(70% 0.2 270);"); + assertEquals(lines[3].trim(), "--palette-brand-400: oklch(70% 0.2 270);"); assertEquals( lines[4].trim(), - "--color-brand-default: oklch(62.796% 0.25768 29.23388);", + "--palette-brand-default: oklch(62.796% 0.25768 29.23388);", ); }); @@ -80,11 +80,11 @@ Deno.test("processColors - handles string values", () => { const lines = getLines(css); assertEquals( lines[1].trim(), - "--color-simple-white: oklch(100% 0 0);", + "--palette-simple-white: oklch(100% 0 0);", ); assertEquals( lines[2].trim(), - "--color-simple-black: oklch(0% 0 0);", + "--palette-simple-black: oklch(0% 0 0);", ); }); @@ -108,8 +108,8 @@ Deno.test("processColors - handles themes", () => { secondary: "var(--2)", }, variables: { - 1: "colors.palette.value.simple.white", - 2: "colors.palette.value.simple.black", + 1: "palette.value.simple.white", + 2: "palette.value.simple.black", }, }, }, @@ -121,11 +121,11 @@ Deno.test("processColors - handles themes", () => { const lines = getLines(css); assertEquals( lines[5].trim(), - "--theme-light-background-primary: var(--color-simple-white);", + "--theme-light-background-primary: var(--palette-simple-white);", ); assertEquals( lines[6].trim(), - "--theme-light-background-secondary: var(--color-simple-black);", + "--theme-light-background-secondary: var(--palette-simple-black);", ); }); @@ -145,12 +145,12 @@ Deno.test("processColors - handles transparency", () => { const { css } = processColors(config.colors); const lines = getLines(css); assertEquals( - lines[1].trim(), - "--color-alpha-softGray1: oklch(14.48% 0 0 / 12%);", + lines[1], + "--palette-alpha-softGray1: oklch(14.48% 0 0 / 12%);", ); assertEquals( - lines[2].trim(), - "--color-alpha-softGray2: oklch(14.48% 0 0 / 24%);", + lines[2], + "--palette-alpha-softGray2: oklch(14.48% 0 0 / 24%);", ); }); @@ -177,10 +177,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": "colors.palette.value.coral.50", - "2": "colors.palette.value.coral.90", - "3": "colors.palette.value.coral.100", - "4": "colors.palette.value.indigo.100", + "1": "palette.value.coral.50", + "2": "palette.value.coral.90", + "3": "palette.value.coral.100", + "4": "palette.value.indigo.100", }, }, }, @@ -192,8 +192,9 @@ Deno.test("processColors - generates gradient with color variables", () => { const result = processColors(config.colors); const lines = getLines(result.css); - const expected = - `--gradient-orangeGradient-primary: linear-gradient(261.78deg, var(--color-coral-50) 33.1%, var(--color-coral-90) 56.3%, var(--color-coral-100) 65.78%, var(--color-indigo-100) 84.23%);`; + const expected = [ + "--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%);", + ].join("\n"); assertEquals(lines.at(-1), expected); }); @@ -215,8 +216,8 @@ Deno.test("processColors - handles themes referencing gradients", () => { primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "colors.palette.value.coral.50", - "c2": "colors.palette.value.coral.50", + "c1": "palette.value.coral.50", + "c2": "palette.value.coral.50", }, }, }, @@ -231,7 +232,7 @@ Deno.test("processColors - handles themes referencing gradients", () => { primary: "var(--grad)", }, variables: { - "grad": "colors.gradients.value.orangeGradient.primary", + "grad": "gradients.value.orangeGradient.primary", }, }, }, @@ -242,9 +243,9 @@ Deno.test("processColors - handles themes referencing gradients", () => { const { css } = processColors(config.colors); const lines = getLines(css); - const lastLine = lines.pop()?.trim(); + const lastLine = lines.pop(); assertEquals( lastLine, - "--theme-light-background-primary: var(--gradient-orangeGradient-primary);", + "--theme-light-background-primary: var(--gradients-orangeGradient-primary);", ); }); diff --git a/tests/primitive.test.ts b/tests/primitive.test.ts index faf7363..884a846 100644 --- a/tests/primitive.test.ts +++ b/tests/primitive.test.ts @@ -1,13 +1,12 @@ -// primitives_test.ts -import { assertEquals } from "jsr:@std/assert"; +import { assertEquals } from "@std/assert"; import { processPrimitives } from "../src/modules/primitive.ts"; import { defineConfig } from "../src/mod.ts"; Deno.test("processPrimitives - processes button with variables", () => { const config = defineConfig({ typography: { - arial: { - typescale: { + fluid: { + arial: { value: { minWidth: 320, minFontSize: 14, @@ -22,10 +21,12 @@ Deno.test("processPrimitives - processes button with variables", () => { }, }, spacing: { - size: { - value: { - 2: "0.5rem", - 3: "0.75rem", + custom: { + size: { + value: { + 2: "0.5rem", + 3: "0.75rem", + }, }, }, }, @@ -41,9 +42,9 @@ Deno.test("processPrimitives - processes button with variables", () => { padding: "var(--2) var(--3)", }, variables: { - "base": "typography.arial.typescale@base", - "2": "spacing.size.value.2", - "3": "spacing.size.value.3", + "base": "typography_fluid.arial@base", + "2": "spacing.custom.size.value.2", + "3": "spacing.custom.size.value.3", }, settings: { pxToRem: false }, }, @@ -53,8 +54,14 @@ Deno.test("processPrimitives - processes button with variables", () => { }); const result = processPrimitives(config); - const expected = - "/* button */\n--button-small-width: 120px;\n--button-small-height: 40px;\n--button-small-fontSize: var(--typography-arial-base);\n--button-small-radius: 8px;\n--button-small-padding: var(--size-2) var(--size-3);"; + const expected = [ + "/* button */", + "--button-small-width: 120px;", + "--button-small-height: 40px;", + "--button-small-fontSize: var(--typography_fluid-arial-base);", + "--button-small-radius: 8px;", + "--button-small-padding: var(--spacing-size-2) var(--spacing-size-3);", + ].join("\n"); assertEquals(result.css, expected); }); @@ -86,8 +93,15 @@ Deno.test("processPrimitives - processes buttons with settings", () => { }); const result = processPrimitives(config); - const expected = - "/* button */\n--button-small-width: 120px;\n--button-small-height: 40px;\n--button-small-radius: 8px;\n--button-big-width: 15rem;\n--button-big-height: 5rem;\n--button-big-radius: 1rem;"; + const expected = [ + "/* 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;", + ].join("\n"); assertEquals(result.css, expected); }); @@ -109,8 +123,8 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => primary: { value: "linear-gradient(to right, var(--c1), var(--c2))", variables: { - "c1": "colors.palette.value.coral.50", - "c2": "colors.palette.value.coral.50", + "c1": "palette.value.coral.50", + "c2": "palette.value.coral.50", }, }, }, @@ -125,7 +139,7 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => primary: "var(--grad)", }, variables: { - "grad": "colors.gradients.value.orangeGradient.primary", + "grad": "gradients.value.orangeGradient.primary", }, }, }, @@ -142,9 +156,9 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => "border-color": "var(--border)", }, variables: { - "bg": "colors.theme.value.light.background.primary", - "grad": "colors.gradients.value.orangeGradient.primary", - "border": "colors.palette.value.coral.50", + "bg": "theme.value.light.background.primary", + "grad": "gradients.value.orangeGradient.primary", + "border": "palette.value.coral.50", }, }, }, @@ -156,9 +170,51 @@ Deno.test("processPrimitives - references colors, gradients, and themes", () => const expected = [ "/* card */", "--card-default-background-color: var(--theme-light-background-primary);", - "--card-default-background-image: var(--gradient-orangeGradient-primary);", - "--card-default-border-color: var(--color-coral-50);", + "--card-default-background-image: var(--gradients-orangeGradient-primary);", + "--card-default-border-color: var(--palette-coral-50);", ].join("\n"); assertEquals(result.css, expected); }); + +Deno.test("processPrimitives - uses fluid spacing references", () => { + const config = defineConfig({ + spacing: { + fluid: { + gap: { + value: { + minSize: 4, + maxSize: 24, + minWidth: 320, + maxWidth: 1280, + negativeSteps: [0], + positiveSteps: [1], + prefix: "gs", + }, + }, + }, + }, + primitives: { + box: { + value: { + default: { + value: { + padding: "var(--s) var(--m)", + }, + variables: { + "s": "spacing_fluid.gap@s", + "m": "spacing_fluid.gap@m", + }, + settings: { pxToRem: false }, + }, + }, + }, + }, + }); + const primitives = processPrimitives(config); + const expected = [ + "/* box */", + "--box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);", + ].join("\n"); + assertEquals(primitives.css, expected); +}); diff --git a/tests/spacing.test.ts b/tests/spacing.test.ts index f18a94f..e004415 100644 --- a/tests/spacing.test.ts +++ b/tests/spacing.test.ts @@ -1,20 +1,25 @@ -// spacing_test.ts -import { assertEquals } from "jsr:@std/assert"; +import { assertEquals } from "@std/assert"; import { processSpacing } from "../src/modules/spacing.ts"; import { defineConfig } from "../src/config.ts"; Deno.test("processSpacing - generates correct spacing scale", () => { const config = defineConfig({ spacing: { - size: { - value: { 1: "0.25rem", 2: "0.5rem", 3: "0.75rem", s: "16px" }, + custom: { + size: { + value: { 1: "0.25rem", 2: "0.5rem", 3: "0.75rem", s: "16px" }, + }, }, }, }); const result = processSpacing(config.spacing); - const expected = - "--size-1: 0.25rem;\n--size-2: 0.5rem;\n--size-3: 0.75rem;\n--size-s: 1rem;"; + const expected = [ + "--spacing-size-1: 0.25rem;", + "--spacing-size-2: 0.5rem;", + "--spacing-size-3: 0.75rem;", + "--spacing-size-s: 1rem;", + ].join("\n"); assertEquals(result.css, expected); }); @@ -22,20 +27,116 @@ Deno.test("processSpacing - generates correct spacing scale", () => { Deno.test("processSpacing - handles settings", () => { const config = defineConfig({ spacing: { - size: { - value: { 1: "16px", 2: "0.5rem" }, - settings: { pxToRem: true }, - }, - scale: { - value: { "md": "8px", "lg": "0.75rem" }, - settings: { pxToRem: false }, + custom: { + size: { + value: { 1: "16px", 2: "0.5rem" }, + settings: { pxToRem: true }, + }, + scale: { + value: { "md": "8px", "lg": "0.75rem" }, + settings: { pxToRem: false }, + }, }, }, }); const result = processSpacing(config.spacing); - const expected = - "--size-1: 1rem;\n--size-2: 0.5rem;\n--scale-md: 8px;\n--scale-lg: 0.75rem;"; + const expected = [ + "--spacing-size-1: 1rem;", + "--spacing-size-2: 0.5rem;", + "--spacing-scale-md: 8px;", + "--spacing-scale-lg: 0.75rem;", + ].join("\n"); assertEquals(result.css, expected); }); + +Deno.test("processSpacing - generates fluid spacing (prefix)", () => { + const config = defineConfig({ + spacing: { + fluid: { + base: { + value: { + minSize: 4, + maxSize: 24, + minWidth: 320, + maxWidth: 1280, + negativeSteps: [0], + positiveSteps: [1, 2], + prefix: "foo", + }, + }, + }, + }, + }); + + const { css } = processSpacing(config.spacing); + const expected = [ + "--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);", + ].join("\n"); + assertEquals(css, expected); +}); + +Deno.test("processSpacing - fluid without prefix falls back to scale name", () => { + const config = defineConfig({ + spacing: { + fluid: { + rhythm: { + value: { + minSize: 2, + maxSize: 20, + minWidth: 320, + maxWidth: 1280, + negativeSteps: [0], + positiveSteps: [1], + }, + }, + }, + }, + }); + const { css } = processSpacing(config.spacing); + const expected = [ + "--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);", + ].join("\n"); + assertEquals(css, expected); +}); + +Deno.test("processSpacing - combines fluid and custom spacing", () => { + const config = defineConfig({ + spacing: { + fluid: { + base: { + value: { + minSize: 4, + maxSize: 24, + minWidth: 320, + maxWidth: 1280, + negativeSteps: [0], + positiveSteps: [1], + prefix: "flux", + }, + }, + }, + custom: { + gap: { + value: { 1: "4px", 2: "8px" }, + settings: { pxToRem: false }, + }, + }, + }, + }); + const { css } = processSpacing(config.spacing!); + const expected = [ + "--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-gap-1: 4px;", + "--spacing-gap-2: 8px;", + ].join("\n"); + assertEquals(css, expected); +}); diff --git a/tests/typography.test.ts b/tests/typography.test.ts index 77edeca..ad17ef2 100644 --- a/tests/typography.test.ts +++ b/tests/typography.test.ts @@ -1,12 +1,12 @@ -import { assertEquals } from "jsr:@std/assert"; +import { assertEquals } from "@std/assert"; import { defineConfig, processTypography } from "../src/mod.ts"; import { getLines } from "./helpers.ts"; Deno.test("processTypography - generates correct CSS variables", () => { const config = defineConfig({ typography: { - arial: { - typescale: { + fluid: { + arial: { value: { minWidth: 320, minFontSize: 14, @@ -38,15 +38,17 @@ Deno.test("processTypography - generates correct CSS variables", () => { assertEquals(lines.length, expectedSizes.length); // Test a specific value for precision - const xlLine = lines.find((line) => line.includes("--typography-arial-xl:")); + const xlLine = lines.find((line) => line.includes("--typography_fluid-arial-xl:")); assertEquals( xlLine?.trim(), - "--typography-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", + "--typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", ); // Test that we have all the expected size variables expectedSizes.forEach((size) => { - const hasSize = lines.some((line) => line.includes(`--typography-arial-${size}:`)); + const hasSize = lines.some((line) => + line.includes(`--typography_fluid-arial-${size}:`) + ); assertEquals(hasSize, true, `Missing size ${size}`); }); }); @@ -54,8 +56,8 @@ Deno.test("processTypography - generates correct CSS variables", () => { Deno.test("typography - can handle custom labels and prefixes", () => { const config = defineConfig({ typography: { - arial: { - typescale: { + fluid: { + arial: { value: { minWidth: 320, minFontSize: 14, @@ -103,15 +105,17 @@ Deno.test("typography - can handle custom labels and prefixes", () => { assertEquals(lines.length, expectedSizes.length); // Test a specific value for precision - const xlLine = lines.find((line) => line.includes("--typography-text-xl:")); + const xlLine = lines.find((line) => line.includes("--typography_fluid-arial-text-xl:")); assertEquals( xlLine?.trim(), - "--typography-text-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", + "--typography_fluid-arial-text-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", ); // Test that we have all the expected size variables expectedSizes.forEach((size) => { - const hasSize = lines.some((line) => line.includes(`--typography-text-${size}:`)); + const hasSize = lines.some((line) => + line.includes(`--typography_fluid-arial-text-${size}:`) + ); assertEquals(hasSize, true, `Missing size ${size}`); }); }); @@ -121,13 +125,8 @@ Deno.test("processTypography - can process weights", () => { const negativeSteps = 3; const config = defineConfig({ typography: { - arial: { - weight: { - value: { - regular: "500", - }, - }, - typescale: { + fluid: { + arial: { value: { minWidth: 320, minFontSize: 14, @@ -140,6 +139,9 @@ Deno.test("processTypography - can process weights", () => { }, }, }, + weight: { + arial: { value: { regular: "500" } }, + }, }, }); @@ -148,9 +150,11 @@ Deno.test("processTypography - can process weights", () => { assertEquals(lines.length, positiveSteps + negativeSteps + 1 + 1); // 1 for base size and 1 for weight // Test that we have the weight variable - const xlLine = lines.find((line) => line.includes("--weight-arial-regular:")); + const xlLine = lines.find((line) => + line.includes("--typography-weight-arial-regular:") + ); assertEquals( xlLine?.trim(), - "--weight-arial-regular: 500;", + "--typography-weight-arial-regular: 500;", ); }); From f3a4299bdd34421e55a0cc5fccaa509414cc0e78 Mon Sep 17 00:00:00 2001 From: Hebilicious Date: Tue, 23 Sep 2025 20:05:39 +0700 Subject: [PATCH 2/5] chore: changeset --- .changeset/eight-tigers-pay.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eight-tigers-pay.md diff --git a/.changeset/eight-tigers-pay.md b/.changeset/eight-tigers-pay.md new file mode 100644 index 0000000..cbb3fc4 --- /dev/null +++ b/.changeset/eight-tigers-pay.md @@ -0,0 +1,5 @@ +--- +"@hebilicious/cssforge": minor +--- + +feat: add fluid spacing From f7e471452e6f26289e3d65764c95bb6b1adfe328 Mon Sep 17 00:00:00 2001 From: Hebilicious Date: Tue, 23 Sep 2025 20:10:17 +0700 Subject: [PATCH 3/5] chore: update readme --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6bb6492..6375a54 100644 --- a/README.md +++ b/README.md @@ -423,7 +423,7 @@ export default defineConfig({ maxWidth: 1280, positiveSteps: [1.5, 2, 3, 4, 6], negativeSteps: [0.75, 0.5, 0.25], - prefix: "fluid", + prefix: "smooth", }, }, }, @@ -447,7 +447,7 @@ export default defineConfig({ maxWidth: 1280, positiveSteps: [1.5, 2, 3, 4, 6], negativeSteps: [0.75, 0.5, 0.25], - prefix: "fluid", + prefix: "smooth", }, }, }, @@ -466,24 +466,28 @@ This will generate the following CSS : /*____ CSSForge ____*/ :root { /*____ Spacing ____*/ ---spacing_fluid-base-fluid-3xs: clamp(0.0625rem, -0.0417rem + 0.5208vw, 0.375rem); ---spacing_fluid-base-fluid-2xs: clamp(0.125rem, -0.0833rem + 1.0417vw, 0.75rem); ---spacing_fluid-base-fluid-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); ---spacing_fluid-base-fluid-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); ---spacing_fluid-base-fluid-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); ---spacing_fluid-base-fluid-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); ---spacing_fluid-base-fluid-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); ---spacing_fluid-base-fluid-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); ---spacing_fluid-base-fluid-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); +--spacing_fluid-base-smooth-3xs: clamp(0.0625rem, -0.0417rem + 0.5208vw, 0.375rem); +--spacing_fluid-base-smooth-2xs: clamp(0.125rem, -0.0833rem + 1.0417vw, 0.75rem); +--spacing_fluid-base-smooth-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); +--spacing_fluid-base-smooth-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); +--spacing_fluid-base-smooth-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); +--spacing_fluid-base-smooth-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); +--spacing_fluid-base-smooth-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); +--spacing_fluid-base-smooth-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); +--spacing_fluid-base-smooth-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); --spacing-gap-1: 0.25rem; --spacing-gap-2: 0.5rem; } ``` +#### Referencing Fluid Spacing + +To reference fluid spacing, use the `@` symbol and the label of the scale; ie: `spacing_fluid-base-smooth@xs`. + ### Typography -Define your typography, with typescales powered by +Define your typography, with fluid typescales powered by [utopia](https://utopia.fyi/type/calculator): + ```typescript export default defineConfig({ colors: { @@ -279,20 +281,25 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Colors ____*/ -/* Palette */ ---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); -/* Gradients */ ---gradients-white-green-primary: linear-gradient(to right, var(--palette-simple-white), var(--palette-simple-green)); -/* Theme: light */ -/* background */ + /*____ Colors ____*/ + /* Palette */ + --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); + /* Gradients */ + --gradients-white-green-primary: linear-gradient( + to right, + var(--palette-simple-white), + var(--palette-simple-green) + ); + /* Theme: light */ + /* background */ } ``` + ### Spacing @@ -318,6 +325,7 @@ export default defineConfig({ }, }); --> + ```typescript export default defineConfig({ spacing: { @@ -341,21 +349,21 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Spacing ____*/ ---spacing-size-1: 0.25rem; ---spacing-size-2: 0.5rem; ---spacing-size-3: 0.75rem; ---spacing-size-4: 1rem; + /*____ Spacing ____*/ + --spacing-size-1: 0.25rem; + --spacing-size-2: 0.5rem; + --spacing-size-3: 0.75rem; + --spacing-size-4: 1rem; } ``` - + #### Fluid Spacing (Utopia) -You can generate fluid spacing scales powered by [Utopia](https://utopia.fyi). Fluid scales -output `clamp()` expressions which interpolate between a minimum and maximum size across a -viewport range. +You can generate fluid spacing scales powered by [Utopia](https://utopia.fyi). Fluid +scales output `clamp()` expressions which interpolate between a minimum and maximum size +across a viewport range. + ```typescript export default defineConfig({ spacing: { @@ -401,12 +410,13 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Spacing ____*/ ---spacing_fluid-base-hi-xs: clamp(0rem, 0rem + 0vw, 0rem); ---spacing_fluid-base-hi-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); ---spacing_fluid-base-hi-m: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); + /*____ Spacing ____*/ + --spacing_fluid-base-hi-xs: clamp(0rem, 0rem + 0vw, 0rem); + --spacing_fluid-base-hi-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); + --spacing_fluid-base-hi-m: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); } ``` + You can combine fluid and static spacing: @@ -435,6 +445,7 @@ export default defineConfig({ }, }); --> + ```typescript export default defineConfig({ spacing: { @@ -465,25 +476,27 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Spacing ____*/ ---spacing_fluid-base-smooth-3xs: clamp(0.0625rem, -0.0417rem + 0.5208vw, 0.375rem); ---spacing_fluid-base-smooth-2xs: clamp(0.125rem, -0.0833rem + 1.0417vw, 0.75rem); ---spacing_fluid-base-smooth-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); ---spacing_fluid-base-smooth-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); ---spacing_fluid-base-smooth-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); ---spacing_fluid-base-smooth-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); ---spacing_fluid-base-smooth-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); ---spacing_fluid-base-smooth-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); ---spacing_fluid-base-smooth-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); ---spacing-gap-1: 0.25rem; ---spacing-gap-2: 0.5rem; + /*____ Spacing ____*/ + --spacing_fluid-base-smooth-3xs: clamp(0.0625rem, -0.0417rem + 0.5208vw, 0.375rem); + --spacing_fluid-base-smooth-2xs: clamp(0.125rem, -0.0833rem + 1.0417vw, 0.75rem); + --spacing_fluid-base-smooth-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); + --spacing_fluid-base-smooth-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); + --spacing_fluid-base-smooth-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); + --spacing_fluid-base-smooth-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); + --spacing_fluid-base-smooth-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); + --spacing_fluid-base-smooth-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); + --spacing_fluid-base-smooth-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); + --spacing-gap-1: 0.25rem; + --spacing-gap-2: 0.5rem; } ``` + #### Referencing Fluid Spacing -To reference fluid spacing, use the `@` symbol and the label of the scale; ie: `spacing_fluid-base-smooth@xs`. +To reference fluid spacing, use the `@` symbol and the label of the scale; ie: +`spacing_fluid-base-smooth@xs`. ### Typography @@ -517,6 +530,7 @@ export default defineConfig({ }, }); --> + ```typescript export default defineConfig({ typography: { @@ -550,19 +564,20 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Typography ____*/ ---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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); ---typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); ---typography_fluid-arial-sm: 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: 600; + /*____ Typography ____*/ + --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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); + --typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); + --typography_fluid-arial-sm: 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: 600; } ``` + ### Customizing Fluid Typography Scales @@ -600,6 +615,7 @@ const config = defineConfig({ }, }); --> + ```typescript const config = defineConfig({ typography: { @@ -636,20 +652,22 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Typography ____*/ ---typography_fluid-comicsans-text-e: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); ---typography_fluid-comicsans-text-d: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); ---typography_fluid-comicsans-text-c: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); ---typography_fluid-comicsans-text-b: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); ---typography_fluid-comicsans-text-a: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); + /*____ Typography ____*/ + --typography_fluid-comicsans-text-e: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); + --typography_fluid-comicsans-text-d: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); + --typography_fluid-comicsans-text-c: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); + --typography_fluid-comicsans-text-b: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); + --typography_fluid-comicsans-text-a: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); } ``` + #### Referencing Fluid Typography To reference fluid typography, use the `@` symbol and the label of the scale; ie: -`typography_fluid.comicsans-text@a`. By default the labels follow the Tailwind convention : +`typography_fluid.comicsans-text@a`. By default the labels follow the Tailwind convention +: - 2xs - xs @@ -715,6 +733,7 @@ export default defineConfig({ }); ``` --> + ```typescript export default defineConfig({ typography: { @@ -771,28 +790,29 @@ This will generate the following CSS : ```css /*____ CSSForge ____*/ :root { -/*____ Spacing ____*/ ---spacing-size-2: 0.5rem; ---spacing-size-3: 0.75rem; -/*____ Typography ____*/ ---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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); ---typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); ---typography_fluid-arial-sm: 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); -/*____ Primitives ____*/ -/* button */ ---button-small-width: 7.5rem; ---button-small-height: 2.5rem; ---button-small-fontSize: var(--typography_fluid-arial-base); ---button-small-radius: 0.5rem; ---button-small-padding: var(--spacing-size-2) var(--spacing-size-3); + /*____ Spacing ____*/ + --spacing-size-2: 0.5rem; + --spacing-size-3: 0.75rem; + /*____ Typography ____*/ + --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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); + --typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); + --typography_fluid-arial-sm: 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); + /*____ Primitives ____*/ + /* button */ + --button-small-width: 7.5rem; + --button-small-height: 2.5rem; + --button-small-fontSize: var(--typography_fluid-arial-base); + --button-small-radius: 0.5rem; + --button-small-padding: var(--spacing-size-2) var(--spacing-size-3); } ``` + ## CLI Usage @@ -831,7 +851,7 @@ Check out our examples: - [Basic Setup](./examples/basic) - [] Tailwind -## TODO +## TODO - [] Custom Media Queries https://www.w3.org/TR/mediaqueries-5/#at-ruledef-custom-media - [] Line Height diff --git a/example/basic/.cssforge/output.css b/example/basic/.cssforge/output.css index 7f86092..25fe6df 100644 --- a/example/basic/.cssforge/output.css +++ b/example/basic/.cssforge/output.css @@ -1,32 +1,32 @@ /*____ CSSForge ____*/ :root { -/*____ Colors ____*/ -/* Palette */ ---palette-coral-100: oklch(73.511% 0.16799 40.24666); ---palette-mint-100: oklch(80.035% 0.18206 151.71104); ---palette-indigo-100: oklch(51.057% 0.23005 276.96564); -/*____ 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); ---spacing_fluid-base-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); ---spacing_fluid-base-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); ---spacing_fluid-base-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); ---spacing_fluid-base-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); ---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-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); -} \ No newline at end of file + /*____ Colors ____*/ + /* Palette */ + --palette-coral-100: oklch(73.511% 0.16799 40.24666); + --palette-mint-100: oklch(80.035% 0.18206 151.71104); + --palette-indigo-100: oklch(51.057% 0.23005 276.96564); + /*____ 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); + --spacing_fluid-base-xs: clamp(0.1875rem, -0.125rem + 1.5625vw, 1.125rem); + --spacing_fluid-base-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); + --spacing_fluid-base-m: clamp(0.375rem, -0.25rem + 3.125vw, 2.25rem); + --spacing_fluid-base-l: clamp(0.5rem, -0.3333rem + 4.1667vw, 3rem); + --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-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); +} diff --git a/scripts/update-readme.ts b/scripts/update-readme.ts index 983d6e9..6ac4d32 100644 --- a/scripts/update-readme.ts +++ b/scripts/update-readme.ts @@ -23,11 +23,13 @@ function buildReplacement(tsSource: string, css: string): string { const cssBlock = md.codeBlock(css.trim(), "css"); // Only return the inner content to place between START_CODE and END_BLOCK return [ + "", tsBlock, "", "This will generate the following CSS :", "", cssBlock, + "", ].join("\n"); } From c61427268484953c98507b59f852ce8a8d20a6a3 Mon Sep 17 00:00:00 2001 From: Hebilicious Date: Tue, 23 Sep 2025 20:54:00 +0700 Subject: [PATCH 5/5] feat: add custom and one up pairs --- README.md | 55 +++++++++++++++++++++++++++------------ src/modules/spacing.ts | 7 ++--- src/modules/typography.ts | 4 +-- tests/primitive.test.ts | 5 ++-- tests/spacing.test.ts | 9 +++++++ tests/typography.test.ts | 8 +++--- 6 files changed, 61 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 4bb18a1..33071d2 100644 --- a/README.md +++ b/README.md @@ -414,6 +414,8 @@ This will generate the following CSS : --spacing_fluid-base-hi-xs: clamp(0rem, 0rem + 0vw, 0rem); --spacing_fluid-base-hi-s: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem); --spacing_fluid-base-hi-m: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); + --spacing_fluid-base-hi-xs-s: clamp(0rem, -0.5rem + 2.5vw, 1.5rem); + --spacing_fluid-base-hi-s-m: clamp(0.25rem, -1.1667rem + 7.0833vw, 4.5rem); } ``` @@ -486,6 +488,14 @@ This will generate the following CSS : --spacing_fluid-base-smooth-xl: clamp(0.75rem, -0.5rem + 6.25vw, 4.5rem); --spacing_fluid-base-smooth-2xl: clamp(1rem, -0.6667rem + 8.3333vw, 6rem); --spacing_fluid-base-smooth-3xl: clamp(1.5rem, -1rem + 12.5vw, 9rem); + --spacing_fluid-base-smooth-3xs-2xs: clamp(0.0625rem, -0.1667rem + 1.1458vw, 0.75rem); + --spacing_fluid-base-smooth-2xs-xs: clamp(0.125rem, -0.2083rem + 1.6667vw, 1.125rem); + --spacing_fluid-base-smooth-xs-s: clamp(0.1875rem, -0.25rem + 2.1875vw, 1.5rem); + --spacing_fluid-base-smooth-s-m: clamp(0.25rem, -0.4167rem + 3.3333vw, 2.25rem); + --spacing_fluid-base-smooth-m-l: clamp(0.375rem, -0.5rem + 4.375vw, 3rem); + --spacing_fluid-base-smooth-l-xl: clamp(0.5rem, -0.8333rem + 6.6667vw, 4.5rem); + --spacing_fluid-base-smooth-xl-2xl: clamp(0.75rem, -1rem + 8.75vw, 6rem); + --spacing_fluid-base-smooth-2xl-3xl: clamp(1rem, -1.6667rem + 13.3333vw, 9rem); --spacing-gap-1: 0.25rem; --spacing-gap-2: 0.5rem; } @@ -496,7 +506,18 @@ 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-smooth@xs`. +`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 @@ -569,9 +590,9 @@ This will generate the following CSS : --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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); - --typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); - --typography_fluid-arial-sm: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); + --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: 600; @@ -580,7 +601,7 @@ This will generate the following CSS : -### Customizing Fluid Typography Scales +#### Customizing Fluid Typography Scales You can customize the typescale by providing your prefix and custom labels. The prefix will overwrite the name of the key that you are using to define your typography. @@ -666,16 +687,18 @@ 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-text@a`. By default the labels follow the Tailwind convention -: +`typography_fluid.comicsans@a`. Do not include the prefix in the reference. The labels +follow the following convention : +- 3xs - 2xs - xs -- sm -- base -- lg +- s +- m +- l - xl - 2xl +- 3xl ### Primitives @@ -722,7 +745,7 @@ export default defineConfig({ padding: "var(--2) var(--3)", }, variables: { - "base": "typography_fluid.arial@base", + "base": "typography_fluid.arial@m", "2": "spacing.custom.size.value.2", "3": "spacing.custom.size.value.3", }, @@ -774,7 +797,7 @@ export default defineConfig({ padding: "var(--2) var(--3)", }, variables: { - "base": "typography_fluid.arial@base", + "base": "typography_fluid.arial@m", "2": "spacing.custom.size.value.2", "3": "spacing.custom.size.value.3", }, @@ -798,16 +821,16 @@ This will generate the following CSS : --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-lg: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); - --typography_fluid-arial-base: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); - --typography_fluid-arial-sm: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); + --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); /*____ Primitives ____*/ /* button */ --button-small-width: 7.5rem; --button-small-height: 2.5rem; - --button-small-fontSize: var(--typography_fluid-arial-base); + --button-small-fontSize: var(--typography_fluid-arial-m); --button-small-radius: 0.5rem; --button-small-padding: var(--spacing-size-2) var(--spacing-size-3); } diff --git a/src/modules/spacing.ts b/src/modules/spacing.ts index 4261c25..95466b6 100644 --- a/src/modules/spacing.ts +++ b/src/modules/spacing.ts @@ -84,9 +84,10 @@ export function processSpacing(spacing: SpacingConfig): Output { const { prefix, ...utopiaConfig } = value; if (prefix) validateName(prefix); - const scale = calculateSpaceScale(utopiaConfig); - for (const { label, clamp } of scale.sizes) { - const resolvedPrefix = prefix ? `${scaleName}-${prefix}` : scaleName; + const { sizes, customPairs, oneUpPairs } = calculateSpaceScale(utopiaConfig); + + const resolvedPrefix = prefix ? `${scaleName}-${prefix}` : scaleName; + for (const { label, clamp } of [sizes, customPairs, oneUpPairs].flat()) { const variableName = `--${moduleKey}-${resolvedPrefix}-${label}`; const cssVar = `${variableName}: ${clamp};`; cssOutput.push(cssVar); diff --git a/src/modules/typography.ts b/src/modules/typography.ts index 13287d6..ad2c475 100644 --- a/src/modules/typography.ts +++ b/src/modules/typography.ts @@ -6,7 +6,7 @@ export interface FluidTypeScaleDefinition { /** * The configuration for the type scale generation. */ - value: UtopiaTypeConfig & { + value: Omit & { /** * An optional prefix for the generated CSS variables. */ @@ -79,7 +79,7 @@ export function processTypography(config: TypographyConfig): Output { if (prefix) validateName(prefix); const scale = calculateTypeScale({ - labelStyle: settings?.customLabel ? "utopia" : "tailwind", + labelStyle: settings?.customLabel ? "utopia" : "tshirt", ...utopiaConfig, }); diff --git a/tests/primitive.test.ts b/tests/primitive.test.ts index 884a846..c52388a 100644 --- a/tests/primitive.test.ts +++ b/tests/primitive.test.ts @@ -16,6 +16,7 @@ Deno.test("processPrimitives - processes button with variables", () => { maxTypeScale: 1.25, positiveSteps: 5, negativeSteps: 3, + prefix: "foo", }, }, }, @@ -42,7 +43,7 @@ Deno.test("processPrimitives - processes button with variables", () => { padding: "var(--2) var(--3)", }, variables: { - "base": "typography_fluid.arial@base", + "base": "typography_fluid.arial@m", "2": "spacing.custom.size.value.2", "3": "spacing.custom.size.value.3", }, @@ -58,7 +59,7 @@ Deno.test("processPrimitives - processes button with variables", () => { "/* button */", "--button-small-width: 120px;", "--button-small-height: 40px;", - "--button-small-fontSize: var(--typography_fluid-arial-base);", + "--button-small-fontSize: var(--typography_fluid-arial-foo-m);", "--button-small-radius: 8px;", "--button-small-padding: var(--spacing-size-2) var(--spacing-size-3);", ].join("\n"); diff --git a/tests/spacing.test.ts b/tests/spacing.test.ts index e004415..64cd9c5 100644 --- a/tests/spacing.test.ts +++ b/tests/spacing.test.ts @@ -64,6 +64,7 @@ Deno.test("processSpacing - generates fluid spacing (prefix)", () => { negativeSteps: [0], positiveSteps: [1, 2], prefix: "foo", + customSizes: ["xs-l"], }, }, }, @@ -76,6 +77,10 @@ Deno.test("processSpacing - generates fluid spacing (prefix)", () => { "--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);", ].join("\n"); assertEquals(css, expected); }); @@ -102,6 +107,8 @@ Deno.test("processSpacing - fluid without prefix falls back to scale name", () = "--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);", ].join("\n"); assertEquals(css, expected); }); @@ -135,6 +142,8 @@ Deno.test("processSpacing - combines fluid and custom spacing", () => { "--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;", ].join("\n"); diff --git a/tests/typography.test.ts b/tests/typography.test.ts index ad17ef2..48ac2b8 100644 --- a/tests/typography.test.ts +++ b/tests/typography.test.ts @@ -27,9 +27,9 @@ Deno.test("processTypography - generates correct CSS variables", () => { "3xl", "2xl", "xl", - "lg", - "base", - "sm", + "l", + "m", + "s", "xs", "2xs", ]; @@ -40,7 +40,7 @@ Deno.test("processTypography - generates correct CSS variables", () => { // Test a specific value for precision const xlLine = lines.find((line) => line.includes("--typography_fluid-arial-xl:")); assertEquals( - xlLine?.trim(), + xlLine, "--typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem);", );