UNPKG

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