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