UNPKG

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
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 };