igniteui-theming
Version:
A set of Sass variables, mixins, and functions for generating palettes, typography, and elevations used by Ignite UI components.
270 lines (265 loc) • 10.7 kB
JavaScript
import { COMPONENT_METADATA, getThemingSelector } from "../knowledge/component-metadata.js";
import { getComponentTheme } from "../knowledge/component-themes.js";
import { SCHEMAS } from "../knowledge/platforms/common.js";
import { generateWebComponentsThemeSass } from "../knowledge/platforms/webcomponents.js";
import { TYPOGRAPHY_PRESETS } from "../knowledge/typography.js";
import { generateHeader, generatePresetImports, generateUseStatement, quoteFontFamily, toVariableName } from "../utils/sass.js";
import { generateAngularThemeSass } from "../knowledge/platforms/angular.js";
//#region src/generators/sass.ts
/**
* Sass code generator for Ignite UI Theming.
* Generates valid Sass code that uses the igniteui-theming library.
*
* Supported platforms:
* - Angular: Uses `igniteui-angular/theming` with `core()` and `theme()` mixins
* - Web Components: Uses `igniteui-theming` directly with individual mixins
* - Blazor: Uses `igniteui-theming` directly with individual mixins
* - React: Uses `igniteui-theming` directly with individual mixins
*/
/**
* Generate a palette definition.
*/
function generatePalette(input) {
const variant = input.variant ?? "light";
const varName = `$${input.name ? toVariableName(input.name) : `custom-${variant}`}-palette`;
const paletteArgs = [
`$primary: ${input.primary}`,
`$secondary: ${input.secondary}`,
`$surface: ${input.surface}`
];
if (input.gray) paletteArgs.push(`$gray: ${input.gray}`);
if (input.info) paletteArgs.push(`$info: ${input.info}`);
if (input.success) paletteArgs.push(`$success: ${input.success}`);
if (input.warn) paletteArgs.push(`$warn: ${input.warn}`);
if (input.error) paletteArgs.push(`$error: ${input.error}`);
return {
code: `${generateHeader(`${variant} palette with primary color ${input.primary}`)}
${generateUseStatement(input.platform, input.licensed)}
// Custom ${variant} palette
${varName}: palette(
${paletteArgs.join(",\n ")}
);
// Apply the palette (generates CSS custom properties)
@include palette(${varName});
`,
description: `Generated a ${variant} color palette with primary color ${input.primary}`,
variables: [varName]
};
}
/**
* Generate typography setup.
*/
function generateTypography(input) {
const designSystem = input.designSystem ?? "material";
const typeScaleVar = `$${designSystem}-type-scale`;
const preset = TYPOGRAPHY_PRESETS[designSystem];
const typeface = input.fontFamily || preset.typeface;
const presetImports = generatePresetImports({
platform: input.platform,
includeTypography: true
});
const imports = [generateUseStatement(input.platform, input.licensed), ...presetImports].join("\n");
return {
code: `${generateHeader(`Typography setup using ${designSystem} type scale`)}
${imports}
// Typography setup with ${designSystem} type scale
@include typography(
$font-family: ${quoteFontFamily(typeface)},
$type-scale: ${typeScaleVar}
);
`,
description: `Generated typography setup using ${designSystem} design system with font family ${typeface}`,
variables: [typeScaleVar]
};
}
/**
* Generate elevations setup.
*/
function generateElevations(input) {
const preset = input.designSystem ?? "material";
const elevationsVar = `$${preset}-elevations`;
const presetImports = generatePresetImports({
platform: input.platform,
includeElevations: true
});
const imports = [generateUseStatement(input.platform, input.licensed), ...presetImports].join("\n");
return {
code: `${generateHeader(`Elevations setup using ${preset} preset`)}
${imports}
// Elevations setup with ${preset} shadows
@include elevations(${elevationsVar});
`,
description: `Generated elevations setup using ${preset} preset (25 elevation levels)`,
variables: [elevationsVar]
};
}
/**
* Generate a complete theme (palette + typography + elevations).
*
* Supports two platforms:
* - `angular`: Uses `igniteui-angular/theming` with `core()` and `theme()` mixins
* - `webcomponents`: Uses `igniteui-theming` directly with individual mixins
*
* If no platform is specified, defaults to generating platform-agnostic code
* using igniteui-theming directly (similar to webcomponents but simpler).
*/
function generateTheme(input) {
const platform = input.platform;
const designSystem = input.designSystem ?? "material";
const variant = input.variant ?? "light";
switch (platform) {
case "angular": return generateAngularTheme(input, designSystem, variant);
case "webcomponents":
case "react":
case "blazor": return generateWebComponentsTheme(input, designSystem, variant);
default: return generateGenericTheme(input, designSystem, variant);
}
}
/**
* Generate Angular-specific theme using core() and theme() mixins.
*/
function generateAngularTheme(input, designSystem, variant) {
const code = generateAngularThemeSass({
designSystem,
variant,
primaryColor: input.primaryColor,
secondaryColor: input.secondaryColor,
surfaceColor: input.surfaceColor,
customPaletteName: input.name ? `$${toVariableName(input.name)}-palette` : void 0,
fontFamily: input.fontFamily,
includeTypography: input.includeTypography !== false
});
const variables = [];
if (input.name) variables.push(`$${toVariableName(input.name)}-palette`);
variables.push(`$${variant}-${designSystem}-schema`);
if (input.includeTypography !== false) {
if (!input.fontFamily) variables.push(`$${designSystem}-typeface`);
variables.push(`$${designSystem}-type-scale`);
}
return {
code,
description: `Generated Angular ${variant} theme based on ${designSystem} design system`,
variables
};
}
/**
* Generate Web Components-specific theme using individual mixins.
*/
function generateWebComponentsTheme(input, designSystem, variant) {
const code = generateWebComponentsThemeSass({
designSystem,
variant,
primaryColor: input.primaryColor,
secondaryColor: input.secondaryColor,
surfaceColor: input.surfaceColor,
customPaletteName: input.name ? `$${toVariableName(input.name)}-palette` : void 0,
fontFamily: input.fontFamily,
includeTypography: input.includeTypography !== false,
includeElevations: input.includeElevations !== false,
includeSpacing: input.includeSpacing !== false
});
const variables = [];
if (input.name) variables.push(`$${toVariableName(input.name)}-palette`);
else variables.push("$palette");
if (input.includeTypography !== false) {
if (!input.fontFamily) variables.push("$typeface");
variables.push("$type-scale");
}
if (input.includeElevations !== false) variables.push(designSystem === "indigo" ? "$indigo-elevations" : "$material-elevations");
return {
code,
description: `Generated Web Components ${variant} theme based on ${designSystem} design system`,
variables
};
}
/**
* Generate platform-agnostic theme (legacy behavior).
* Uses igniteui-theming directly without platform-specific optimizations.
*/
function generateGenericTheme(input, designSystem, variant) {
const themeName = input.name ? toVariableName(input.name) : `${variant}-${designSystem}`;
const paletteVar = `$${themeName}-palette`;
const includeTypography = input.includeTypography !== false;
const includeElevations = input.includeElevations !== false;
const variables = [paletteVar];
const paletteArgs = [`$primary: ${input.primaryColor}`];
if (input.secondaryColor) paletteArgs.push(`$secondary: ${input.secondaryColor}`);
if (input.surfaceColor) paletteArgs.push(`$surface: ${input.surfaceColor}`);
else paletteArgs.push(`$surface: ${variant === "dark" ? "#222222" : "white"}`);
const presetImports = generatePresetImports({
platform: input.platform,
includeTypography,
includeElevations
});
const sections = [
generateHeader(`Complete ${variant} theme based on ${designSystem} design system`),
"// NOTE: Specify platform (\"angular\" or \"webcomponents\") for optimized output",
generateUseStatement(input.platform, input.licensed),
...presetImports,
"",
`// ${themeName} palette`,
`${paletteVar}: palette(`,
` ${paletteArgs.join(",\n ")}`,
");",
"",
"// Apply the palette",
`@include palette(${paletteVar});`
];
if (includeTypography) {
const preset = TYPOGRAPHY_PRESETS[designSystem];
const typeface = input.fontFamily || preset.typeface;
const typeScaleVar = `$${designSystem}-type-scale`;
variables.push(typeScaleVar);
sections.push("", "// Typography setup", "@include typography(", ` $font-family: ${quoteFontFamily(typeface)},`, ` $type-scale: ${typeScaleVar}`, ");");
}
if (includeElevations) {
const elevationsVar = `$${designSystem === "indigo" ? "indigo" : "material"}-elevations`;
variables.push(elevationsVar);
sections.push("", "// Elevations setup", `@include elevations(${elevationsVar});`);
}
return {
code: `${sections.join("\n")}\n`,
description: `Generated complete ${variant} theme based on ${designSystem} design system (platform-agnostic)`,
variables
};
}
/**
* Generate Sass code for a component theme.
*/
function generateComponentTheme(input) {
const theme = getComponentTheme(input.component);
if (!theme) throw new Error(`Unknown component: ${input.component}`);
const designSystem = input.designSystem ?? "material";
const variant = input.variant ?? "light";
const themeFn = theme.themeFunctionName;
const themeComponentName = COMPONENT_METADATA[input.component]?.childOf ?? input.component;
const themeName = input.name ? `$${toVariableName(input.name)}` : `$custom-${themeComponentName}-theme`;
const tokenArgs = [`$schema: ${SCHEMAS[variant][designSystem]}`];
for (const [tokenName, value] of Object.entries(input.tokens)) {
const stringValue = typeof value === "number" ? String(value) : value;
tokenArgs.push(`$${tokenName}: ${stringValue}`);
}
const defaultSelectors = getThemingSelector(input.component, input.platform);
const selector = input.selector || (defaultSelectors.length > 0 ? defaultSelectors[0] : input.component);
const sections = [
generateHeader(`Custom ${themeComponentName} theme`),
generateUseStatement(input.platform, input.licensed),
"",
`// Custom ${themeComponentName} theme`,
`${themeName}: ${themeFn}(`
];
if (tokenArgs.length > 0) sections.push(` ${tokenArgs.join(",\n ")}`);
sections.push(");");
sections.push("");
sections.push(`// Apply the theme to ${selector}`);
sections.push(`${selector} {`);
sections.push(` @include tokens(${themeName});`);
sections.push("}");
return {
code: `${sections.join("\n")}\n`,
description: `Generated custom ${themeComponentName} theme with ${Object.keys(input.tokens).length} token(s) using ${designSystem} design system (${variant} variant)`,
variables: [themeName]
};
}
//#endregion
export { generateComponentTheme, generateElevations, generatePalette, generateTheme, generateTypography };