UNPKG

igniteui-theming

Version:

A set of Sass variables, mixins, and functions for generating palettes, typography, and elevations used by Ignite UI components.

295 lines (294 loc) 10.7 kB
import { ALL_COLOR_SHADES, SHADE_LEVELS } from "./types.js"; import "../knowledge/index.js"; //#region src/utils/sass.ts /** * Sass code generation utilities. * * This module provides low-level helpers for generating Sass code snippets. * These helpers are designed to be reusable across different code generators * (standalone tools and platform-specific theme generators). */ /** * Properly quote a font-family value for Sass. * Font stacks (containing commas) need to be wrapped in quotes * to preserve them as a single string value. * * Handles various input formats: * - `Roboto` -> `'Roboto'` * - `'Roboto'` -> `'Roboto'` (unchanged) * - `'Titillium Web', sans-serif` -> `"'Titillium Web', sans-serif"` * - `"Titillium Web", sans-serif` -> `'"Titillium Web", sans-serif'` */ function quoteFontFamily(typeface) { if (typeface.includes(",")) { const hasDoubleQuotes = typeface.includes("\""); const hasSingleQuotes = typeface.includes("'"); if (hasDoubleQuotes && !hasSingleQuotes) return `'${typeface}'`; return `"${typeface}"`; } if (typeface.startsWith("'") || typeface.startsWith("\"")) return typeface; return `'${typeface}'`; } /** * Convert a name to a valid Sass variable name. * * Transforms input string to lowercase, replaces invalid characters with hyphens, * collapses multiple hyphens, and removes leading/trailing hyphens. * * @param name - The name to convert * @returns A valid Sass variable name (without $ prefix) * @throws Error if the input results in an empty variable name * * @example * toVariableName('My Theme') // 'my-theme' * toVariableName('DARK_THEME_v2') // 'dark-theme-v2' */ function toVariableName(name) { if (!name?.trim()) throw new Error("Variable name cannot be empty"); const result = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); if (!result) throw new Error(`Cannot create valid Sass variable name from: "${name}"`); return result; } /** * Generate a comment header for generated Sass code. * * @param description - Description of what the code does * @returns Formatted Sass comment header */ function generateHeader(description) { return `// Generated by Ignite UI Theming MCP Server\n// ${description}`; } /** Inline comment prepended above @use lines in generated Sass. */ var SASS_USE_INLINE_COMMENT = "// NOTE: @use rules must be at the top of the file. Deduplicate when combining multiple outputs."; /** Markdown assembly note appended after Sass code blocks in handler responses. */ var SASS_USE_ASSEMBLY_NOTE = "\n> **File placement:** `@use` rules must appear at the very top of the `.scss` file, before any other statements. When combining outputs from multiple tools, keep only one `@use` per module path."; /** * Generate the Sass @use statement for the theming library. * Uses platform-specific import paths when platform is specified. * * @param platform - Target platform (angular or webcomponents) * @param licensed - Whether to use the licensed @infragistics package (Angular only, defaults to false) * @returns The inline placement comment + @use statement for the platform */ function generateUseStatement(platform, licensed) { if (platform === "angular") return `${SASS_USE_INLINE_COMMENT}\n@use "${licensed ? "@infragistics/igniteui-angular" : "igniteui-angular"}/theming" as *;`; return `${SASS_USE_INLINE_COMMENT}\n@use 'igniteui-theming' as *;`; } /** * Typography presets index path. * Imports through the index to get design-system-prefixed variables * (e.g., $material-type-scale, $indigo-type-scale). */ var TYPOGRAPHY_PRESETS_PATH = "igniteui-theming/sass/typography/presets"; /** * Elevations presets index path. * Imports through the index to get design-system-prefixed variables * (e.g., $material-elevations, $indigo-elevations). */ var ELEVATIONS_PRESETS_PATH = "igniteui-theming/sass/elevations/presets"; /** * Generate additional @use import statements for typography and/or elevation presets. * * For non-Angular platforms, preset variables like `$material-type-scale` and * `$material-elevations` are defined in separate sub-modules that must be * explicitly imported. Angular's `igniteui-angular/theming` re-exports all * presets, so no additional imports are needed. * * These imports use the presets index files which re-forward individual * design system modules with prefixed names (e.g., `$type-scale` becomes * `$material-type-scale` via `@forward './material' as material-*`). * * @param options - Configuration for which preset imports to include * @returns Array of @use statements (empty for Angular) */ function generatePresetImports(options) { if (options.platform === "angular") return []; const lines = []; if (options.includeTypography) lines.push(`@use '${TYPOGRAPHY_PRESETS_PATH}' as *;`); if (options.includeElevations) lines.push(`@use '${ELEVATIONS_PRESETS_PATH}' as *;`); return lines; } /** * Generate the palette() function call code. * Returns an object with code lines that can be joined or embedded in other code. * * @example * // Basic usage - returns lines for palette definition * const result = generatePaletteCode({ primary: '#2ab759' }); * // result.paletteDefinition: ['$custom-light-palette: palette(', ' $primary: #2ab759,', ' $surface: white', ');'] * * @example * // With variable references (for use in theme generators) * const result = generatePaletteCode({ * primary: '#2ab759', * useVariableReferences: true, * variableName: 'my-palette' * }); * // Uses $primary-color, $secondary-color references */ function generatePaletteCode(options) { const variant = options.variant ?? "light"; const varName = options.variableName ? `$${options.variableName}` : `$custom-${variant}-palette`; const colorVariables = []; const paletteArgs = []; if (options.useVariableReferences) { colorVariables.push(`$primary-color: ${options.primary};`); paletteArgs.push("$primary: $primary-color"); colorVariables.push(`$secondary-color: ${options.secondary};`); paletteArgs.push("$secondary: $secondary-color"); colorVariables.push(`$surface-color: ${options.surface};`); paletteArgs.push("$surface: $surface-color"); if (options.gray) { colorVariables.push(`$gray-color: ${options.gray};`); paletteArgs.push("$gray: $gray-color"); } } else { paletteArgs.push(`$primary: ${options.primary}`); paletteArgs.push(`$secondary: ${options.secondary}`); paletteArgs.push(`$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}`); return { colorVariables, paletteDefinition: [ `${varName}: palette(`, ...paletteArgs.map((arg, i) => ` ${arg}${i < paletteArgs.length - 1 ? "," : ""}`), ");" ], variableName: varName }; } /** * Generate the typography() mixin call code. * Returns an array of lines that can be joined or embedded in other code. */ function generateTypographyCode(options) { const indent = options.indent ?? ""; return [ `${indent}@include typography(`, `${indent} $font-family: ${quoteFontFamily(options.fontFamily)},`, `${indent} $type-scale: ${options.typeScaleVar}`, `${indent});` ]; } /** * Generate the elevations() mixin call code. * Returns an array of lines that can be joined or embedded in other code. */ function generateElevationsCode(options) { return [`${options.indent ?? ""}@include elevations(${options.elevationsVar});`]; } /** * Generates Sass code for a custom handmade palette. * * This function creates a Sass map palette structure that can use either: * - The `shades()` function for auto-generating shades from a base color * - Explicit shade maps with manual control over each shade value * * @example * // Generate palette with mixed modes * const lines = generateCustomPaletteCode({ * variant: 'light', * variableName: 'my-brand', * surfaceColor: '#fafafa', * colors: { * primary: { mode: 'explicit', shades: { '50': '#e6eff8', ... } }, * secondary: { mode: 'shades', baseColor: '#f7bd32' }, * // ... * } * }); */ function generateCustomPaletteCode(options) { const variant = options.variant ?? "light"; const varName = options.variableName ?? `custom-${variant}`; const lines = []; lines.push(`$${varName}-palette: (`); const colorGroups = [ { name: "primary", def: options.colors.primary, shades: ALL_COLOR_SHADES, isGray: false }, { name: "secondary", def: options.colors.secondary, shades: ALL_COLOR_SHADES, isGray: false }, { name: "gray", def: options.colors.gray, shades: SHADE_LEVELS, isGray: true }, { name: "surface", def: options.colors.surface, shades: ALL_COLOR_SHADES, isGray: false }, { name: "info", def: options.colors.info, shades: ALL_COLOR_SHADES, isGray: false }, { name: "success", def: options.colors.success, shades: ALL_COLOR_SHADES, isGray: false }, { name: "warn", def: options.colors.warn, shades: ALL_COLOR_SHADES, isGray: false }, { name: "error", def: options.colors.error, shades: ALL_COLOR_SHADES, isGray: false } ]; for (let i = 0; i < colorGroups.length; i++) { const { name, def, shades, isGray } = colorGroups[i]; if (def.mode === "shades") { const shadesListStr = shades.map((s) => `'${s}'`).join(", "); lines.push(` '${name}': shades(`); lines.push(` '${name}',`); lines.push(` ${def.baseColor},`); if (isGray && options.surfaceColor) { lines.push(` (${shadesListStr}),`); lines.push(` ${options.surfaceColor}`); } else lines.push(` (${shadesListStr})`); lines.push(` ),`); } else { lines.push(` '${name}': (`); for (let j = 0; j < shades.length; j++) { const shade = shades[j]; const color = def.shades[shade]; const contrast = def.contrastOverrides?.[shade]; const isLastShade = j === shades.length - 1; lines.push(` '${shade}': ${color},`); const contrastValue = contrast ?? `adaptive-contrast(${color})`; lines.push(` '${shade}-contrast': ${contrastValue},`); lines.push(` '${shade}-raw': ${color}${isLastShade ? "" : ","}`); } lines.push(` ),`); } } lines.push(` '_meta': (`); lines.push(` 'variant': ${variant}`); lines.push(" )"); lines.push(");"); return lines; } //#endregion export { SASS_USE_ASSEMBLY_NOTE, generateCustomPaletteCode, generateElevationsCode, generateHeader, generatePaletteCode, generatePresetImports, generateTypographyCode, generateUseStatement, quoteFontFamily, toVariableName };