igniteui-theming
Version:
A set of Sass variables, mixins, and functions for generating palettes, typography, and elevations used by Ignite UI components.
293 lines (282 loc) • 11 kB
JavaScript
import { themingImporter } from "../utils/theming-resolve.js";
import * as sass from "sass-embedded";
//#region src/generators/css.ts
/**
* Generate CSS custom properties for a standard palette.
*
* This function compiles Sass code that uses the palette() function and
* @include palette() mixin, then returns the compiled CSS output.
*
* @example
* const result = await generatePaletteCss({
* primary: '#1976d2',
* secondary: '#ff9800',
* surface: '#fafafa',
* variant: 'light'
* });
* // result.css contains :root { --ig-primary-50: ...; --ig-primary-100: ...; ... }
*/
async function generatePaletteCss(options) {
const variant = options.variant ?? "light";
const paletteArgs = [
`$primary: ${options.primary}`,
`$secondary: ${options.secondary}`,
`$surface: ${options.surface}`
];
if (options.gray) paletteArgs.push(`$gray: ${options.gray}`);
if (options.info) paletteArgs.push(`$info: ${options.info}`);
if (options.success) paletteArgs.push(`$success: ${options.success}`);
if (options.warn) paletteArgs.push(`$warn: ${options.warn}`);
if (options.error) paletteArgs.push(`$error: ${options.error}`);
const sassCode = `
@use 'igniteui-theming/sass/color' as *;
$palette: palette(
${paletteArgs.join(",\n ")}
);
@include palette($palette);
`;
try {
const importers = options._importers ?? [themingImporter];
return {
css: (await sass.compileStringAsync(sassCode, {
importers,
style: "expanded"
})).css,
description: `Generated CSS custom properties for a ${variant} palette with primary color ${options.primary}`
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile palette CSS: ${message}`);
}
}
/**
* Generate CSS custom properties for a custom palette.
*
* This function generates Sass code for the custom palette structure
* (using either shades() function or explicit values), compiles it,
* and returns the CSS output.
*/
async function generateCustomPaletteCss(options) {
const variant = options.variant ?? "light";
const { generateCustomPaletteCode } = await import("../utils/sass.js");
const sassCode = `
@use 'igniteui-theming/sass/color' as *;
${generateCustomPaletteCode({
variant,
variableName: "custom",
surfaceColor: options.surfaceColor,
colors: options.colors
}).join("\n")}
@include palette($custom-palette);
`;
try {
const importers = options._importers ?? [themingImporter];
return {
css: (await sass.compileStringAsync(sassCode, {
importers,
style: "expanded"
})).css,
description: `Generated CSS custom properties for a custom ${variant} palette`
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile custom palette CSS: ${message}`);
}
}
/**
* Format CSS output for display.
* Adds a header comment and ensures consistent formatting.
*/
function formatCssOutput(css, description) {
return `/* Generated by Ignite UI Theming MCP Server */
/* ${description} */
` + css;
}
/**
* Generate CSS custom properties for a component theme.
*
* This function compiles Sass code that uses the component theme function
* and @include tokens() mixin, then returns the compiled CSS output.
*
* @example
* const result = await generateComponentThemeCss({
* platform: 'webcomponents',
* designSystem: 'bootstrap',
* variant: 'light',
* component: 'button',
* tokens: { background: '#1976d2', 'text-color': 'white' },
* selector: 'igc-button'
* });
* // result.css contains: igc-button { --ig-button-background: var(--ig-button-background, #1976d2); ... }
*/
async function generateComponentThemeCss(options) {
const { COMPONENT_METADATA, getComponentTheme, getThemingSelector, SCHEMA_PRESETS } = await import("../knowledge/index.js");
const { toVariableName } = await import("../utils/sass.js");
const theme = getComponentTheme(options.component);
if (!theme) throw new Error(`Unknown component: ${options.component}`);
const designSystem = options.designSystem ?? "material";
const variant = options.variant ?? "light";
const themeFn = theme.themeFunctionName;
const themeComponentName = COMPONENT_METADATA[options.component]?.childOf ?? options.component;
const themeName = options.name ? `$${toVariableName(options.name)}` : `$custom-${themeComponentName}-theme`;
const tokenArgs = [`$schema: ${SCHEMA_PRESETS[variant][designSystem]}`];
for (const [tokenName, value] of Object.entries(options.tokens)) {
const stringValue = typeof value === "number" ? String(value) : value;
tokenArgs.push(`$${tokenName}: ${stringValue}`);
}
const defaultSelectors = getThemingSelector(options.component, options.platform);
const selector = options.selector || (defaultSelectors.length > 0 ? defaultSelectors[0] : options.component);
const sassCode = `
@use 'igniteui-theming/sass/themes' as *;
@use 'igniteui-theming/sass/themes/schemas' as *;
// Custom ${themeComponentName} theme
${themeName}: ${themeFn}(
${tokenArgs.join(",\n ")}
);
// Apply the theme to ${selector}
${selector} {
@include tokens(${themeName});
}
`;
try {
const importers = options._importers ?? [themingImporter];
return {
css: (await sass.compileStringAsync(sassCode, {
importers,
style: "expanded"
})).css,
description: `Generated CSS custom properties for ${themeComponentName} component with ${Object.keys(options.tokens).length} token(s) using ${designSystem} design system (${variant} variant)`
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile component theme CSS: ${message}`);
}
}
/**
* Generate CSS custom properties for a typography setup.
*
* Compiles the typography() mixin and returns the resulting CSS custom
* properties (--ig-h1-font-size, --ig-body-1-font-weight, etc.).
*/
async function generateTypographyCss(options) {
const { TYPOGRAPHY_PRESETS } = await import("../knowledge/typography.js");
const { quoteFontFamily } = await import("../utils/sass.js");
const designSystem = options.designSystem ?? "material";
const preset = TYPOGRAPHY_PRESETS[designSystem];
const typeface = options.fontFamily || preset?.typeface || "Roboto, sans-serif";
const typeScaleVar = `$${designSystem}-type-scale`;
const sassCode = `
@use 'igniteui-theming/sass/typography' as *;
@use 'igniteui-theming/sass/typography/presets' as *;
@include typography(
$font-family: ${quoteFontFamily(typeface)},
$type-scale: ${typeScaleVar}
);
`;
try {
const importers = options._importers ?? [themingImporter];
return {
css: (await sass.compileStringAsync(sassCode, {
importers,
style: "expanded"
})).css,
description: `Generated CSS custom properties for typography using ${designSystem} type scale with font family ${typeface}`
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile typography CSS: ${message}`);
}
}
/**
* Generate CSS custom properties for elevations.
*
* Compiles the elevations() mixin and returns the resulting CSS custom
* properties (--ig-elevation-0 through --ig-elevation-24).
*/
async function generateElevationsCss(options) {
const preset = options.designSystem ?? "material";
const sassCode = `
@use 'igniteui-theming/sass/elevations' as *;
@use 'igniteui-theming/sass/elevations/presets' as *;
@include elevations(${`$${preset}-elevations`});
`;
try {
const importers = options._importers ?? [themingImporter];
return {
css: (await sass.compileStringAsync(sassCode, {
importers,
style: "expanded"
})).css,
description: `Generated CSS custom properties for elevations using ${preset} preset (25 elevation levels)`
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile elevations CSS: ${message}`);
}
}
/**
* Generate CSS custom properties for a complete theme.
*
* For Web Components, React, and Blazor this compiles the full
* palette + typography + elevations Sass and returns all resulting
* CSS custom properties in one block.
*
* For Angular the same approach is used, but the response carries
* `angularCaveat: true` to signal that component-scoped class styles
* (generated by core() + theme()) are absent from the CSS output.
*/
async function generateThemeCss(options) {
const { TYPOGRAPHY_PRESETS } = await import("../knowledge/typography.js");
const { quoteFontFamily, toVariableName } = await import("../utils/sass.js");
const designSystem = options.designSystem ?? "material";
const variant = options.variant ?? "light";
const isAngular = options.platform === "angular";
const paletteName = options.name ? `$${toVariableName(options.name)}-palette` : "$palette";
const includeTypography = options.includeTypography !== false;
const includeElevations = options.includeElevations !== false;
const includeSpacing = options.includeSpacing !== false;
const preset = TYPOGRAPHY_PRESETS[designSystem];
const typeface = options.fontFamily || preset?.typeface || "Roboto, sans-serif";
const typeScaleVar = `$${designSystem}-type-scale`;
const elevationsVar = designSystem === "indigo" ? "$indigo-elevations" : "$material-elevations";
const importLines = ["@use 'igniteui-theming/sass/color' as *;"];
if (includeTypography) {
importLines.push("@use 'igniteui-theming/sass/typography' as *;");
importLines.push("@use 'igniteui-theming/sass/typography/presets' as *;");
}
if (includeElevations) {
importLines.push("@use 'igniteui-theming/sass/elevations' as *;");
importLines.push("@use 'igniteui-theming/sass/elevations/presets' as *;");
}
if (includeSpacing) importLines.push("@use 'igniteui-theming/sass/themes' as *;");
const mixinLines = [];
mixinLines.push(`@include palette(${paletteName});`);
if (includeElevations) mixinLines.push(`@include elevations(${elevationsVar});`);
if (includeTypography) mixinLines.push(`@include typography(\n $font-family: ${quoteFontFamily(typeface)},\n $type-scale: ${typeScaleVar}\n);`);
if (includeSpacing) mixinLines.push("@include spacing();");
const sassCode = `
${importLines.join("\n")}
${paletteName}: palette(
$primary: ${options.primaryColor},
$secondary: ${options.secondaryColor},
$surface: ${options.surfaceColor}
);
${mixinLines.join("\n")}
`;
try {
const importers = options._importers ?? [themingImporter];
return {
css: (await sass.compileStringAsync(sassCode, {
importers,
style: "expanded"
})).css,
description: `Generated CSS custom properties for a complete ${variant} ${designSystem} theme`,
angularCaveat: isAngular
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to compile theme CSS: ${message}`);
}
}
//#endregion
export { formatCssOutput, generateComponentThemeCss, generateCustomPaletteCss, generateElevationsCss, generatePaletteCss, generateThemeCss, generateTypographyCss };