Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-comics-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hebilicious/cssforge": patch
---

fix nesting for selector and atRules
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
- ab752be: # Introduce variantNameOnly feature for themes.

When working with themes, you can choose to only include the variant name in the CSS
variable name by setting `variantNameOnly: true` in the color definition settings. This is
usually used in combination with `condition` to conditionnally apply themes.
variable name by setting `variantNameOnly: true` in the color definition settings. This
is usually used in combination with `condition` to conditionnally apply themes.

- Default: `--theme-${themeName}-${colorName}-${variantName}`
- VariantOnly Name: `--${variantName}`
Expand Down
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export default defineConfig({
cyan: { hex: "#00FFFF" },
},
settings: {
condition: ".Another",
selector: ".Another",
},
},
},
Expand Down Expand Up @@ -272,7 +272,7 @@ export default defineConfig({
},
},
settings: {
condition: "@media (prefers-color-scheme: dark)",
atRule: "@media (prefers-color-scheme: dark)",
},
},
pink: {
Expand All @@ -292,7 +292,7 @@ export default defineConfig({
},
},
settings: {
condition: ".ThemePink",
selector: ".ThemePink",
},
},
},
Expand Down Expand Up @@ -321,7 +321,7 @@ export default defineConfig({
cyan: { hex: "#00FFFF" },
},
settings: {
condition: ".Another",
selector: ".Another",
},
},
},
Expand Down Expand Up @@ -376,7 +376,7 @@ export default defineConfig({
},
},
settings: {
condition: "@media (prefers-color-scheme: dark)",
atRule: "@media (prefers-color-scheme: dark)",
},
},
pink: {
Expand All @@ -396,7 +396,7 @@ export default defineConfig({
},
},
settings: {
condition: ".ThemePink",
selector: ".ThemePink",
},
},
},
Expand All @@ -418,11 +418,6 @@ This will generate the following CSS :
--palette-simple-blue: oklch(45.201% 0.31321 264.05202);
--palette-simple-violet: oklch(70% 0.2 270);
--palette-simple-red: oklch(62.796% 0.25768 29.23388);
/* another */
.Another {
--palette-another-yellow: oklch(96.798% 0.21101 109.76924);
--palette-another-cyan: oklch(90.54% 0.15455 194.76896);
}
/* Gradients */
/* white-green */
--gradients-white-green-primary: linear-gradient(
Expand All @@ -441,28 +436,33 @@ This will generate the following CSS :
--primary: var(--palette-another-yellow);
--secondary: var(--palette-another-cyan);
}
/* Theme: pink */
.ThemePink {
/* background */
--primary: var(--palette-simple-red);
--secondary: var(--palette-simple-violet);
}
}
/* another */
.Another {
--palette-another-yellow: oklch(96.798% 0.21101 109.76924);
--palette-another-cyan: oklch(90.54% 0.15455 194.76896);
}
/* Theme: pink */
.ThemePink {
/* background */
--primary: var(--palette-simple-red);
--secondary: var(--palette-simple-violet);
}
```

<!-- /md:generate -->

#### Condition

You can conditionnally apply colors, gradients or themes by setting the `condition`
property to a selector or media query. Your variables will be wrapped within the
condition.
You can conditionnally apply colors, gradients or themes by setting the `atRule` or the
`selector` properties. Your variables will be wrapped within `:root` and the selectors
will be placed outside of it.

#### Theme: Variant Name Only

When working with themes, you can choose to only include the variant name in the CSS
variable name by setting `variantNameOnly: true` in the color definition settings. This is
usually used in combination with `condition` to conditionnally apply themes.
usually used in combination with `selector` to conditionnally apply themes.

- Default: `--theme-${themeName}-${colorName}-${variantName}`
- VariantOnly Name: `--${variantName}`
Expand Down
8 changes: 4 additions & 4 deletions example/basic/cssforge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,29 +60,29 @@ export default defineConfig(
100: { hex: "#4F46E5" },
},
},
// dark-mode overrides wrapped in a condition
// dark-mode overrides wrapped in a atRule
coral_dark: {
value: {
100: { hex: "#FF6347" },
},
settings: {
condition: "@media (prefers-color-scheme: dark)",
atRule: "@media (prefers-color-scheme: dark)",
},
},
mint_dark: {
value: {
100: { hex: "#22C55E" },
},
settings: {
condition: "@media (prefers-color-scheme: dark)",
atRule: "@media (prefers-color-scheme: dark)",
},
},
indigo_dark: {
value: {
100: { hex: "#4338CA" },
},
settings: {
condition: "@media (prefers-color-scheme: dark)",
atRule: "@media (prefers-color-scheme: dark)",
},
},
},
Expand Down
45 changes: 35 additions & 10 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,34 +143,52 @@ export function generateTS(config: Partial<CSSForgeConfig>): string {
*/
export function generateCSS(config: Partial<CSSForgeConfig>): string {
const chunks: string[] = ["/*____ CSSForge ____*/", ":root {"];
const outsideChunks: string[] = [];
const processedConfig: {
[key: string]: { css: string; resolveMap: ResolveMap } | undefined;
[key: string]:
| { css: { root?: string; outside?: string }; resolveMap: ResolveMap }
| undefined;
} = {};

// Process colors if present
if (config.colors) {
processedConfig.colors = processColors(config.colors);
if (processedConfig.colors) {
chunks.push("/*____ Colors ____*/");
chunks.push(processedConfig.colors.css);
if (processedConfig.colors.css.root) {
chunks.push("/*____ Colors ____*/");
chunks.push(processedConfig.colors.css.root);
}
if (processedConfig.colors.css.outside) {
outsideChunks.push(processedConfig.colors.css.outside);
}
}
}

// Process spacing if present
if (config.spacing) {
processedConfig.spacing = processSpacing(config.spacing);
if (processedConfig.spacing) {
chunks.push("/*____ Spacing ____*/");
chunks.push(processedConfig.spacing.css);
if (processedConfig.spacing.css.root) {
chunks.push("/*____ Spacing ____*/");
chunks.push(processedConfig.spacing.css.root);
}
if (processedConfig.spacing.css.outside) {
outsideChunks.push(processedConfig.spacing.css.outside);
}
}
}

// Process Typography if present
if (config.typography) {
processedConfig.typography = processTypography(config.typography);
if (processedConfig.typography) {
chunks.push("/*____ Typography ____*/");
chunks.push(processedConfig.typography.css);
if (processedConfig.typography.css.root) {
chunks.push("/*____ Typography ____*/");
chunks.push(processedConfig.typography.css.root);
}
if (processedConfig.typography.css.outside) {
outsideChunks.push(processedConfig.typography.css.outside);
}
}
}

Expand All @@ -182,12 +200,19 @@ export function generateCSS(config: Partial<CSSForgeConfig>): string {
spacing: config.spacing,
});
if (primitiveVars) {
chunks.push("/*____ Primitives ____*/");
chunks.push(primitiveVars.css);
if (primitiveVars.css.root) {
chunks.push("/*____ Primitives ____*/");
chunks.push(primitiveVars.css.root);
}
if (primitiveVars.css.outside) {
outsideChunks.push(primitiveVars.css.outside);
}
}
}

chunks.push("}");
// Join all chunks with double newline for readability

if (outsideChunks.length > 0) chunks.push(outsideChunks.join("\n"));

return chunks.join("\n");
}
4 changes: 2 additions & 2 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type ResolveMap = Map<
* CSS and a resolve map.
*/
export interface Output {
/** The generated CSS string. */
css: string;
/** The generated CSS strings. */
css: { root?: string; outside?: string };
/** A map for resolving variable paths. */
resolveMap: ResolveMap;
}
Expand Down
88 changes: 62 additions & 26 deletions src/modules/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ interface ColorVariants {

export interface WithCondition {
/**
* CSS condition to wrap variables in (e.g., ".MyClass", "@media (prefers-color-scheme: dark)")
* CSS selector to wrap variables in (e.g., ".MyClass"). This will be extracted out of the root.
*/
condition?: string;
selector?: string;
/**
* CSS at-rule to wrap variables in (e.g., "@supports (display: grid)")
*/
atRule?: string;
}
/**
* Settings for palette colors, including optional conditions like media queries.
Expand Down Expand Up @@ -219,41 +223,65 @@ function colorValueToOklch(value: ColorValueOrString): string {
* ```
*/
export function processColors(colors: ColorConfig): Output {
const cssOutput: string[] = [];
const rootOutput: string[] = [];
const outsideOutput: string[] = [];
const resolveMap: ResolveMap = new Map();
cssOutput.push(`/* Palette */`);
rootOutput.push(`/* Palette */`);
const moduleKey = "palette";

function conditionalBuilder(
settings: WithCondition | undefined,
initialComment: string,
) {
const leadingComments: string[] = [];
const innerComments: string[] = [];
const vars: string[] = [];

if (settings?.condition) {
leadingComments.push(initialComment);
} else {
cssOutput.push(initialComment);
}
const hasSelector = Boolean(settings?.selector);
const hasAtRule = Boolean(settings?.atRule);

// If no settings provided, emit comment immediately into root output
if (!hasSelector && !hasAtRule) rootOutput.push(initialComment);

return {
addComment(c: string) {
if (settings?.condition) innerComments.push(c);
else cssOutput.push(c);
if (hasSelector || hasAtRule) innerComments.push(c);
else rootOutput.push(c);
},
pushVariable(v: string) {
if (settings?.condition) vars.push(v);
else cssOutput.push(v);
if (hasSelector || hasAtRule) vars.push(v);
else rootOutput.push(v);
},
finalize() {
if (settings?.condition && vars.length > 0) {
cssOutput.push(...leadingComments);
cssOutput.push(`${settings.condition} {`);
cssOutput.push(...innerComments.map((c) => ` ${c}`));
cssOutput.push(...vars.map((v) => ` ${v}`));
cssOutput.push(`}`);
if (!hasSelector && !hasAtRule) return;
if (vars.length === 0 && innerComments.length === 0) return;
if (!settings) return;
if (hasSelector && hasAtRule) {
outsideOutput.push(initialComment);
outsideOutput.push(`${settings.atRule} {`);
outsideOutput.push(` ${settings.selector} {`);
outsideOutput.push(...innerComments.map((c) => ` ${c}`));
outsideOutput.push(...vars.map((v) => ` ${v}`));
outsideOutput.push(` }`);
outsideOutput.push(`}`);
return;
}

if (hasSelector) {
outsideOutput.push(initialComment);
outsideOutput.push(`${settings.selector} {`);
outsideOutput.push(...innerComments.map((c) => ` ${c}`));
outsideOutput.push(...vars.map((v) => ` ${v}`));
outsideOutput.push(`}`);
return;
}

if (hasAtRule) {
rootOutput.push(initialComment);
rootOutput.push(`${settings.atRule} {`);
rootOutput.push(...innerComments.map((c) => ` ${c}`));
rootOutput.push(...vars.map((v) => ` ${v}`));
rootOutput.push(`}`);
return;
}
},
};
Expand Down Expand Up @@ -289,9 +317,12 @@ export function processColors(colors: ColorConfig): Output {
}

if (colors.gradients) {
cssOutput.push(`/* Gradients */`);
rootOutput.push(`/* Gradients */`);
const moduleKey = "gradients";
const palette = { css: cssOutput.join("\n"), resolveMap };
const palette = {
css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") },
resolveMap,
};

for (const [gradientName, gradient] of Object.entries(colors.gradients.value)) {
validateName(gradientName);
Expand Down Expand Up @@ -335,9 +366,12 @@ export function processColors(colors: ColorConfig): Output {
}

if (colors.theme) {
cssOutput.push(`/* Themes */`);
rootOutput.push(`/* Themes */`);
const moduleKey = "theme";
const palette = { css: cssOutput.join("\n"), resolveMap };
const palette = {
css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") },
resolveMap,
};

for (const [themeName, themeConfig] of Object.entries(colors.theme)) {
validateName(themeName);
Expand Down Expand Up @@ -387,7 +421,9 @@ export function processColors(colors: ColorConfig): Output {
}
}

const output = { css: cssOutput.join("\n"), resolveMap };
// console.log(output);
const output = {
css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") },
resolveMap,
};
return output;
}
Loading
Loading