diff --git a/packages/mcp/README.md b/packages/mcp/README.md index f80de191..da5f2c74 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -20,6 +20,26 @@ This MCP server helps you create custom themes for Ignite UI applications by gen | `react` | `igniteui-react` | Ignite UI for React applications | | `blazor` | `igniteui-blazor` | Ignite UI for Blazor applications | +### Per-Tool Platform and Output Support + +| Tool | Angular | Web Components | React | Blazor | CSS output | +| ----------------------------- | :-----: | :------------: | :---: | :----: | :--------: | +| `detect_platform` | ✅ | ✅ | ✅ | ✅ | — | +| `create_palette` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `create_custom_palette` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `create_typography` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `create_elevations` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `create_theme` | ✅ † | ✅ | ✅ | ✅ | ✅ | +| `create_component_theme` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `get_component_design_tokens` | ✅ | ✅ | ✅ | ✅ | — | +| `set_size` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `set_spacing` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `set_roundness` | ✅ | ✅ | ✅ | ✅ | ✅ | + +† `create_theme` with `output: "css"` and `platform: "angular"` returns CSS custom properties only — it does not include the component-scoped class styles generated by Angular's `core()` and `theme()` mixins. Use `output: "sass"` for full Angular theme output. + +**Default output format:** `"sass"` for Angular; `"css"` for all other platforms. + ### Supported Design Systems - **Material** - Google Material Design @@ -236,7 +256,7 @@ The MCP server provides tools for theme generation. ### `detect_platform` -Automatically detects whether your project uses Ignite UI for Angular or Web Components by analyzing `package.json`. +Automatically detects which Ignite UI platform your project uses (Angular, Web Components, React, or Blazor) by analyzing `package.json` and project config files. | Parameter | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------ | @@ -248,7 +268,11 @@ Automatically detects whether your project uses Ignite UI for Angular or Web Com > "What Ignite UI package is installed in this project?" -> "Check if I'm using Angular or Web Components for Ignite UI" +> "Check if I'm using Angular, Web Components, React, or Blazor for Ignite UI" + +> "Detect whether this is a React or Web Components project" + +> "Is this project using Ignite UI for Blazor?" --- @@ -256,19 +280,19 @@ Automatically detects whether your project uses Ignite UI for Angular or Web Com Generates a color palette with automatically calculated shade variations (50-900, A100-A700). -| Parameter | Type | Required | Description | -| ----------- | ----------------------------- | -------- | ----------------------------------------------- | -| `platform` | `angular` \| `webcomponents"` | No | Target platform | -| `primary` | string | Yes | Primary brand color (hex, rgb, hsl, or named) | -| `secondary` | string | Yes | Secondary/accent color | -| `surface` | string | Yes | Background/surface color | -| `gray` | string | No | Gray/neutral color (auto-calculated if omitted) | -| `info` | string | No | Info state color | -| `success` | string | No | Success state color | -| `warn` | string | No | Warning state color | -| `error` | string | No | Error state color | -| `variant` | `"light"` \| `"dark"` | No | Theme variant (default: `"light"`) | -| `name` | string | No | Custom variable name | +| Parameter | Type | Required | Description | +| ----------- | ----------------------------------------------------------- | -------- | ----------------------------------------------- | +| `platform` | `"angular"` \| `"webcomponents"` \| `"react"` \| `"blazor"` | No | Target platform | +| `primary` | string | Yes | Primary brand color (hex, rgb, hsl, or named) | +| `secondary` | string | Yes | Secondary/accent color | +| `surface` | string | Yes | Background/surface color | +| `gray` | string | No | Gray/neutral color (auto-calculated if omitted) | +| `info` | string | No | Info state color | +| `success` | string | No | Success state color | +| `warn` | string | No | Warning state color | +| `error` | string | No | Error state color | +| `variant` | `"light"` \| `"dark"` | No | Theme variant (default: `"light"`) | +| `name` | string | No | Custom variable name | **Example prompts:** @@ -286,20 +310,20 @@ Generates a color palette with automatically calculated shade variations (50-900 Creates a palette with fine-grained control over individual shade values. Use this when you have exact brand guidelines or when automatic shade generation produces suboptimal results. -| Parameter | Type | Required | Description | -| -------------- | --------------------------------------------------------- | -------- | -------------------------- | -| `platform` | `angular` \| `webcomponents` | No | Target platform | -| `variant` | `"light"` \| `"dark"` | No | Theme variant | -| `designSystem` | `"material"` \| `"bootstrap"` \| `"fluent"` \| `"indigo"` | No | Design system preset | -| `name` | string | No | Custom variable name | -| `primary` | ColorDefinition | Yes | Primary color definition | -| `secondary` | ColorDefinition | Yes | Secondary color definition | -| `surface` | ColorDefinition | Yes | Surface color definition | -| `gray` | ColorDefinition | No | Gray color definition | -| `info` | ColorDefinition | No | Info color definition | -| `success` | ColorDefinition | No | Success color definition | -| `warn` | ColorDefinition | No | Warning color definition | -| `error` | ColorDefinition | No | Error color definition | +| Parameter | Type | Required | Description | +| -------------- | ----------------------------------------------------------- | -------- | -------------------------- | +| `platform` | `"angular"` \| `"webcomponents"` \| `"react"` \| `"blazor"` | No | Target platform | +| `variant` | `"light"` \| `"dark"` | No | Theme variant | +| `designSystem` | `"material"` \| `"bootstrap"` \| `"fluent"` \| `"indigo"` | No | Design system preset | +| `name` | string | No | Custom variable name | +| `primary` | ColorDefinition | Yes | Primary color definition | +| `secondary` | ColorDefinition | Yes | Secondary color definition | +| `surface` | ColorDefinition | Yes | Surface color definition | +| `gray` | ColorDefinition | No | Gray color definition | +| `info` | ColorDefinition | No | Info color definition | +| `success` | ColorDefinition | No | Success color definition | +| `warn` | ColorDefinition | No | Warning color definition | +| `error` | ColorDefinition | No | Error color definition | **ColorDefinition** can be: @@ -322,12 +346,12 @@ Creates a palette with fine-grained control over individual shade values. Use th Sets up typography with your preferred font family. -| Parameter | Type | Required | Description | -| -------------- | --------------------------------------------------------- | -------- | ---------------------------- | -| `platform` | `"angular"` \| `"webcomponents"` | No | Target platform | -| `fontFamily` | string | Yes | Font family with fallbacks | -| `designSystem` | `"material"` \| `"bootstrap"` \| `"fluent"` \| `"indigo"` | No | Design system for type scale | -| `name` | string | No | Custom variable name | +| Parameter | Type | Required | Description | +| -------------- | ----------------------------------------------------------- | -------- | ---------------------------- | +| `platform` | `"angular"` \| `"webcomponents"` \| `"react"` \| `"blazor"` | No | Target platform | +| `fontFamily` | string | Yes | Font family with fallbacks | +| `designSystem` | `"material"` \| `"bootstrap"` \| `"fluent"` \| `"indigo"` | No | Design system for type scale | +| `name` | string | No | Custom variable name | **Example prompts:** @@ -345,11 +369,11 @@ Sets up typography with your preferred font family. Configures elevation shadows for visual depth. -| Parameter | Type | Required | Description | -| -------------- | -------------------------------- | -------- | -------------------- | -| `platform` | `"angular"` \| `"webcomponents"` | No | Target platform | -| `designSystem` | `"material"` \| `"indigo"` | No | Elevation preset | -| `name` | string | No | Custom variable name | +| Parameter | Type | Required | Description | +| -------------- | ----------------------------------------------------------- | -------- | -------------------- | +| `platform` | `"angular"` \| `"webcomponents"` \| `"react"` \| `"blazor"` | No | Target platform | +| `designSystem` | `"material"` \| `"indigo"` | No | Elevation preset | +| `name` | string | No | Custom variable name | **Example prompts:** @@ -367,19 +391,19 @@ Configures elevation shadows for visual depth. Generates a complete, production-ready theme with palette, typography, and elevations combined. -| Parameter | Type | Required | Description | -| ------------------- | --------------------------------------------------------- | -------- | ------------------------------------------------------- | -| `platform` | `"angular"` \| `"webcomponents"` | No | Target platform | -| `designSystem` | `"material"` \| `"bootstrap"` \| `"fluent"` \| `"indigo"` | No | Design system | -| `primaryColor` | string | Yes | Primary brand color | -| `secondaryColor` | string | Yes | Secondary/accent color | -| `surfaceColor` | string | Yes | Background/surface color | -| `variant` | `"light"` \| `"dark"` | No | Theme variant | -| `name` | string | No | Custom theme name | -| `fontFamily` | string | No | Font family | -| `includeTypography` | boolean | No | Include typography (default: `true`) | -| `includeElevations` | boolean | No | Include elevations (default: `true`) | -| `includeSpacing` | boolean | No | Include spacing - Web Components only (default: `true`) | +| Parameter | Type | Required | Description | +| ------------------- | ----------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `platform` | `"angular"` \| `"webcomponents"` \| `"react"` \| `"blazor"` | No | Target platform | +| `designSystem` | `"material"` \| `"bootstrap"` \| `"fluent"` \| `"indigo"` | No | Design system | +| `primaryColor` | string | Yes | Primary brand color | +| `secondaryColor` | string | Yes | Secondary/accent color | +| `surfaceColor` | string | Yes | Background/surface color | +| `variant` | `"light"` \| `"dark"` | No | Theme variant | +| `name` | string | No | Custom theme name | +| `fontFamily` | string | No | Font family | +| `includeTypography` | boolean | No | Include typography (default: `true`) | +| `includeElevations` | boolean | No | Include elevations (default: `true`) | +| `includeSpacing` | boolean | No | Include spacing CSS custom properties — applies to Web Components, React, and Blazor. No effect on Angular. (default: `true`) | **Example prompts:** @@ -560,6 +584,8 @@ The MCP server exposes read-only resources that provide reference data. | `theming://platforms` | List of supported platforms | | `theming://platforms/angular` | Angular platform configuration and usage examples | | `theming://platforms/webcomponents` | Web Components platform configuration and examples | +| `theming://platforms/react` | React platform configuration and usage examples | +| `theming://platforms/blazor` | Blazor platform configuration and usage examples | ### Preset Resources @@ -641,6 +667,7 @@ The AI will first use `detect_platform`, then use the detected platform for `cre - Requires `core()` mixin to be called first - Uses unified `theme()` mixin - Requires `ig-typography` CSS class on root element +- Sass is always available via Angular CLI — use `output: "sass"` ### Ignite UI for Web Components @@ -648,6 +675,28 @@ The AI will first use `detect_platform`, then use the detected platform for `cre - Uses individual mixins (`palette()`, `typography()`, `elevations()`) - Supports runtime theme switching via JavaScript API - Includes optional `spacing()` mixin +- Ships pre-compiled CSS themes; `igniteui-theming` must be installed separately for custom Sass theming +- Default output format: `"css"` (use `output: "sass"` only when a Sass pipeline is confirmed) + +### Ignite UI for React + +- Uses `@use "igniteui-theming"` directly (same Sass module as Web Components) +- Common project setups: Vite (configure `css.preprocessorOptions.scss.loadPaths` in `vite.config.ts`) or Next.js (`sassOptions.includePaths` in `next.config.js`) +- No required root class; themes apply via CSS custom properties on `:root` +- Ships pre-compiled CSS; `igniteui-theming` must be installed separately for custom Sass theming +- Default output format: `"css"` (use `output: "sass"` only when a Sass pipeline is confirmed) + +### Ignite UI for Blazor + +- Component library is a NuGet package (`IgniteUI.Blazor`), not npm +- For custom Sass theming, create a separate npm environment: + 1. `npm init -y` in your project's `wwwroot/` (or a dedicated styles directory) + 2. `npm install igniteui-theming sass` + 3. Create `wwwroot/css/theme.scss` using `@use "igniteui-theming" as *` + 4. Add a build script: `"build:theme": "sass wwwroot/css/theme.scss wwwroot/css/theme.css"` + 5. Reference the compiled CSS in `_Host.cshtml` or `index.html` +- Uses the same Sass mixins as Web Components (`palette()`, `typography()`, `elevations()`, `spacing()`) +- Default output format: `"css"` (the MCP compiles server-side; no local Sass toolchain needed unless you want to customise further) --- @@ -709,8 +758,15 @@ For dark themes, use dark surface colors (low luminance like `#121212`). Ensure you have the correct Ignite UI package installed: -- Angular: `npm install igniteui-angular` -- Web Components: `npm install igniteui-webcomponents` +- **Angular**: `npm install igniteui-angular` +- **Web Components**: `npm install igniteui-webcomponents` + For custom Sass theming: `npm install igniteui-theming sass` +- **React**: `npm install igniteui-react` + For custom Sass theming: `npm install igniteui-theming sass` +- **Blazor**: Install the `IgniteUI.Blazor` NuGet package. + For Sass theming, set up a separate npm environment in `wwwroot/`: `npm install igniteui-theming sass` + +For Web Components, React, and Blazor, if you don't have a Sass pipeline, use `output: "css"` to get ready-to-use CSS custom properties compiled server-side by the MCP. --- diff --git a/packages/mcp/src/__tests__/tools/handlers/handlers.test.ts b/packages/mcp/src/__tests__/tools/handlers/handlers.test.ts index ea8d22d9..f9a7a1a8 100644 --- a/packages/mcp/src/__tests__/tools/handlers/handlers.test.ts +++ b/packages/mcp/src/__tests__/tools/handlers/handlers.test.ts @@ -40,6 +40,7 @@ describe("handleCreatePalette", () => { it("includes generated Sass code", async () => { const result = await handleCreatePalette({ + platform: "angular", primary: "#2ab759", secondary: "#f7bd32", surface: "white", @@ -63,8 +64,9 @@ describe("handleCreatePalette", () => { expect(text).toContain("Ignite UI for Angular"); }); - it("shows platform hint when not specified", async () => { + it("shows platform hint when not specified (Sass output)", async () => { const result = await handleCreatePalette({ + output: "sass", primary: "#2ab759", secondary: "#f7bd32", surface: "white", @@ -88,8 +90,9 @@ describe("handleCreatePalette", () => { expect(text).toContain("surface"); }); - it("includes variable names in response", async () => { + it("includes variable names in response (Sass output)", async () => { const result = await handleCreatePalette({ + platform: "angular", primary: "#2ab759", secondary: "#f7bd32", surface: "white", @@ -102,6 +105,7 @@ describe("handleCreatePalette", () => { it("includes inline @use placement comment in generated Sass", async () => { const result = await handleCreatePalette({ + platform: "angular", primary: "#2ab759", secondary: "#f7bd32", surface: "white", @@ -113,6 +117,7 @@ describe("handleCreatePalette", () => { it("includes assembly note after Sass code block", async () => { const result = await handleCreatePalette({ + platform: "angular", primary: "#2ab759", secondary: "#f7bd32", surface: "white", @@ -125,6 +130,7 @@ describe("handleCreatePalette", () => { describe("handleCreateTheme", () => { const baseThemeParams = { + platform: "angular" as const, primaryColor: "#2ab759", secondaryColor: "#f7bd32", surfaceColor: "white", @@ -199,7 +205,9 @@ describe("handleCreateTheme", () => { }); const text = result.content[0].text; + // WC defaults to CSS output — platform name appears in CSS response expect(text).toContain("Web Components"); + expect(text).toContain("```css"); }); it("shows React-specific platform name for react platform", async () => { @@ -209,7 +217,9 @@ describe("handleCreateTheme", () => { }); const text = result.content[0].text; + // React defaults to CSS output — platform name appears in CSS response expect(text).toContain("Platform: Ignite UI for React"); + expect(text).toContain("```css"); }); it("shows Blazor-specific platform name for blazor platform", async () => { @@ -219,7 +229,9 @@ describe("handleCreateTheme", () => { }); const text = result.content[0].text; + // Blazor defaults to CSS output — platform name appears in CSS response expect(text).toContain("Platform: Ignite UI for Blazor"); + expect(text).toContain("```css"); }); it("includes inline @use placement comment in generated Sass", async () => { @@ -238,8 +250,9 @@ describe("handleCreateTheme", () => { }); describe("handleCreateTypography", () => { - it("returns MCP response format", () => { - const result = handleCreateTypography({ + it("returns MCP response format", async () => { + const result = await handleCreateTypography({ + platform: "angular", fontFamily: "Roboto", }); @@ -248,8 +261,9 @@ describe("handleCreateTypography", () => { expect(result.content[0]).toHaveProperty("type", "text"); }); - it("includes typography mixin in code", () => { - const result = handleCreateTypography({ + it("includes typography mixin in code", async () => { + const result = await handleCreateTypography({ + platform: "angular", fontFamily: "Roboto", }); @@ -259,8 +273,9 @@ describe("handleCreateTypography", () => { expect(text).toContain("Roboto"); }); - it("uses specified design system type scale", () => { - const result = handleCreateTypography({ + it("uses specified design system type scale", async () => { + const result = await handleCreateTypography({ + platform: "angular", fontFamily: "Inter", designSystem: "indigo", }); @@ -336,31 +351,32 @@ describe("layout handlers", () => { }); describe("handleCreateElevations", () => { - it("returns MCP response format", () => { - const result = handleCreateElevations({}); + it("returns MCP response format", async () => { + const result = await handleCreateElevations({ platform: "angular" }); expect(result).toHaveProperty("content"); expect(result.content).toBeInstanceOf(Array); expect(result.content[0]).toHaveProperty("type", "text"); }); - it("includes elevations mixin in code", () => { - const result = handleCreateElevations({}); + it("includes elevations mixin in code", async () => { + const result = await handleCreateElevations({ platform: "angular" }); const text = result.content[0].text; expect(text).toContain("```scss"); expect(text).toContain("@include elevations("); }); - it("uses material elevations by default", () => { - const result = handleCreateElevations({}); + it("uses material elevations by default", async () => { + const result = await handleCreateElevations({ platform: "angular" }); const text = result.content[0].text; expect(text).toContain("$material-elevations"); }); - it("uses indigo elevations when specified", () => { - const result = handleCreateElevations({ + it("uses indigo elevations when specified", async () => { + const result = await handleCreateElevations({ + platform: "angular", designSystem: "indigo", }); @@ -384,6 +400,7 @@ describe("handleCreateCustomPalette", () => { it("generates palette with shades mode", async () => { const result = await handleCreateCustomPalette({ + platform: "angular", primary: { mode: "shades", baseColor: "#2ab759" }, secondary: { mode: "shades", baseColor: "#f7bd32" }, surface: { mode: "shades", baseColor: "white" }, @@ -533,12 +550,13 @@ describe("handleCreateComponentTheme", () => { expect(result.content[0]).toHaveProperty("text"); }); - it("generates Sass code by default with platform-specific selector", async () => { + it("generates Sass code when output is sass (webcomponents)", async () => { const result = await handleCreateComponentTheme({ platform: "webcomponents", designSystem: "material", variant: "light", component: "avatar", + output: "sass", tokens: { background: "#ff5722", }, @@ -617,6 +635,7 @@ describe("handleCreateComponentTheme", () => { designSystem: "material", variant: "light", component: "flat-button", + output: "sass", tokens: { background: "#ff5722", }, @@ -634,6 +653,7 @@ describe("handleCreateComponentTheme", () => { designSystem: "material", variant: "light", component: "avatar", + output: "sass", tokens: { background: "#ff5722", }, @@ -731,6 +751,7 @@ describe("handleCreateComponentTheme", () => { platform: "webcomponents", designSystem: "bootstrap", variant: "light", + output: "sass", tokens: { background: "#ff5722", }, @@ -802,6 +823,7 @@ describe("handleCreateComponentTheme", () => { const result = await handleCreateComponentTheme({ platform: "webcomponents", component: "avatar", + output: "sass", tokens: { background: "#ff5722" }, }); @@ -813,6 +835,7 @@ describe("handleCreateComponentTheme", () => { const result = await handleCreateComponentTheme({ platform: "webcomponents", component: "avatar", + output: "sass", tokens: { background: "#ff5722" }, }); diff --git a/packages/mcp/src/generators/css.ts b/packages/mcp/src/generators/css.ts index 05626407..da93a585 100644 --- a/packages/mcp/src/generators/css.ts +++ b/packages/mcp/src/generators/css.ts @@ -306,3 +306,238 @@ ${selector} { throw new Error(`Failed to compile component theme CSS: ${message}`); } } + +// ============================================================================ +// Typography, Elevations, and Theme CSS generators +// ============================================================================ + +/** + * Options for generating typography CSS variables. + */ +export interface TypographyCssOptions { + /** Font family string with fallbacks */ + fontFamily?: string; + /** Design system preset (defaults to 'material') */ + designSystem?: string; + /** Internal testing parameter for Sass importers */ + _importers?: FileImporter[]; +} + +/** + * 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.). + */ +export async function generateTypographyCss( + options: TypographyCssOptions, +): Promise { + 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 as keyof typeof TYPOGRAPHY_PRESETS]; + const typeface = + options.fontFamily || (preset as any)?.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]; + const result = await sass.compileStringAsync(sassCode, { + importers, + style: "expanded", + }); + + return { + css: result.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}`); + } +} + +/** + * Options for generating elevations CSS variables. + */ +export interface ElevationsCssOptions { + /** Design system preset: 'material' or 'indigo' (defaults to 'material') */ + designSystem?: string; + /** Internal testing parameter for Sass importers */ + _importers?: FileImporter[]; +} + +/** + * Generate CSS custom properties for elevations. + * + * Compiles the elevations() mixin and returns the resulting CSS custom + * properties (--ig-elevation-0 through --ig-elevation-24). + */ +export async function generateElevationsCss( + options: ElevationsCssOptions, +): Promise { + const preset = options.designSystem ?? "material"; + const elevationsVar = `$${preset}-elevations`; + + const sassCode = ` +@use 'igniteui-theming/sass/elevations' as *; +@use 'igniteui-theming/sass/elevations/presets' as *; + +@include elevations(${elevationsVar}); +`; + + try { + const importers = options._importers ?? [themingImporter]; + const result = await sass.compileStringAsync(sassCode, { + importers, + style: "expanded", + }); + + return { + css: result.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}`); + } +} + +/** + * Options for generating a complete theme's CSS variables. + */ +export interface ThemeCssOptions { + platform?: (typeof PLATFORMS)[number]; + designSystem?: string; + variant?: ThemeVariant; + primaryColor: string; + secondaryColor: string; + surfaceColor: string; + fontFamily?: string; + includeTypography?: boolean; + includeElevations?: boolean; + includeSpacing?: boolean; + name?: string; + /** Internal testing parameter for Sass importers */ + _importers?: FileImporter[]; +} + +/** + * Result from generating a complete theme's CSS variables. + */ +export interface CssThemeResult { + css: string; + description: string; + /** + * True when platform is 'angular'. Angular CSS output contains only + * CSS custom properties — it does not include the component-scoped class + * styles generated by Angular's core() and theme() mixins. + */ + angularCaveat?: boolean; +} + +/** + * 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. + */ +export async function generateThemeCss( + options: ThemeCssOptions, +): Promise { + 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 as keyof typeof TYPOGRAPHY_PRESETS]; + const typeface = + options.fontFamily || (preset as any)?.typeface || "Roboto, sans-serif"; + const typeScaleVar = `$${designSystem}-type-scale`; + const elevationsVar = + designSystem === "indigo" ? "$indigo-elevations" : "$material-elevations"; + + // Build imports using explicit sub-module paths resolvable by themingImporter + const importLines: string[] = ["@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: string[] = []; + 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]; + const result = await sass.compileStringAsync(sassCode, { + importers, + style: "expanded", + }); + + return { + css: result.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}`); + } +} diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index d375797f..ae48ea40 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -156,6 +156,7 @@ function registerTools(server: McpServer): void { designSystem: createTypographySchema.shape.designSystem, customScale: createTypographySchema.shape.customScale, name: createTypographySchema.shape.name, + output: createTypographySchema.shape.output, }, }, withPreprocessing(createTypographySchema, handleCreateTypography), @@ -172,6 +173,7 @@ function registerTools(server: McpServer): void { licensed: createElevationsSchema.shape.licensed, designSystem: createElevationsSchema.shape.designSystem, name: createElevationsSchema.shape.name, + output: createElevationsSchema.shape.output, }, }, async (params) => { @@ -199,6 +201,7 @@ function registerTools(server: McpServer): void { includeTypography: createThemeSchema.shape.includeTypography, includeElevations: createThemeSchema.shape.includeElevations, includeSpacing: createThemeSchema.shape.includeSpacing, + output: createThemeSchema.shape.output, }, }, async (params) => { diff --git a/packages/mcp/src/tools/descriptions.ts b/packages/mcp/src/tools/descriptions.ts index 9e5818b4..bb34baef 100644 --- a/packages/mcp/src/tools/descriptions.ts +++ b/packages/mcp/src/tools/descriptions.ts @@ -74,6 +74,10 @@ export const TOOL_DESCRIPTIONS = { Use this tool FIRST before generating any theme code to ensure platform-optimized output. The detected platform determines the correct Sass module paths and syntax. + Output format ("sass" vs "css") is a separate concern — see the output parameter on each + generation tool. For non-Angular platforms, prefer "css" unless the project has a confirmed + Sass pipeline; use your own file-reading tools or ask the user to confirm before choosing + output: "sass". @@ -516,7 +520,7 @@ export const TOOL_DESCRIPTIONS = { 2. Creates color palette using palette() function 3. Sets up typography with specified font family (if includeTypography: true) 4. Configures elevations based on design system (if includeElevations: true) - 5. Configures spacing utilities for Web Components (if includeSpacing: true) + 5. Configures spacing utilities for Web Components, React, and Blazor (if includeSpacing: true) 6. Applies the theme using the theme() mixin 7. Returns luminance warnings if any colors may produce poor shades @@ -1124,7 +1128,13 @@ export const PARAM_DESCRIPTIONS = { variant: FRAGMENTS.VARIANT, designSystem: FRAGMENTS.DESIGN_SYSTEM, name: `Custom variable name (without $ prefix). If omitted, auto-generates based on tool and variant (e.g., "custom-light", "my-theme").`, - output: `Output format: "sass" generates Sass code using igniteui-theming library functions. "css" generates CSS custom properties (variables) directly - useful for vanilla CSS projects or when you don't want Sass compilation. Defaults to tool-specific output ("sass" for theme generators, "css" for layout setters).`, + output: `Output format for the generated code. + +"sass" — Returns Sass source using igniteui-theming functions and mixins. Requires a Sass pipeline in the consuming project. Prefer for Angular (Angular CLI handles Sass compilation automatically). + +"css" — The MCP server compiles the Sass internally and returns ready-to-use CSS custom properties. No local Sass toolchain needed. Prefer for Web Components, React, and Blazor unless the project has a confirmed Sass setup (e.g. .scss files and a sass build step are present). When in doubt, use "css" or ask the user. + +Layout tools (set_size, set_spacing, set_roundness) default to "css". Generation tools (create_palette, create_theme, etc.) default to "sass" for Angular and "css" for all other platforms.`, // --------------------------------------------------------------------------- // detect_platform parameters @@ -1166,7 +1176,7 @@ export const PARAM_DESCRIPTIONS = { includeElevations: "Include elevation shadows in the generated theme. Set to false if you want to configure elevations separately. Defaults to true.", includeSpacing: - "Include spacing CSS custom properties (Web Components platform only). Defaults to true. Has no effect on Angular platform.", + "Include spacing CSS custom properties. Applies to Web Components, React, and Blazor. Has no effect on Angular. Defaults to true.", // --------------------------------------------------------------------------- // Custom palette parameters (for create_custom_palette) diff --git a/packages/mcp/src/tools/handlers/component-theme.ts b/packages/mcp/src/tools/handlers/component-theme.ts index f3e4375b..80520f38 100644 --- a/packages/mcp/src/tools/handlers/component-theme.ts +++ b/packages/mcp/src/tools/handlers/component-theme.ts @@ -64,7 +64,7 @@ export async function handleCreateComponentTheme( tokens, selector, name, - output = "sass", + output = params.platform === "angular" ? "sass" : "css", designSystem = "material", variant = "light", } = params; diff --git a/packages/mcp/src/tools/handlers/custom-palette.ts b/packages/mcp/src/tools/handlers/custom-palette.ts index fc49181d..a3176c10 100644 --- a/packages/mcp/src/tools/handlers/custom-palette.ts +++ b/packages/mcp/src/tools/handlers/custom-palette.ts @@ -34,7 +34,8 @@ export async function handleCreateCustomPalette( ) { const variant = params.variant ?? "light"; const designSystem = params.designSystem ?? "material"; - const output = params.output ?? "sass"; + const output = + params.output ?? (params.platform === "angular" ? "sass" : "css"); const presetName = `${variant}-${designSystem}-palette` as PalettePresetName; const preset = PALETTE_PRESETS[presetName]; diff --git a/packages/mcp/src/tools/handlers/elevations.ts b/packages/mcp/src/tools/handlers/elevations.ts index d80799d9..0ca9b940 100644 --- a/packages/mcp/src/tools/handlers/elevations.ts +++ b/packages/mcp/src/tools/handlers/elevations.ts @@ -2,12 +2,60 @@ * Handler for create_elevations tool. */ +import { + formatCssOutput, + generateElevationsCss, +} from "../../generators/css.js"; import { generateElevations } from "../../generators/sass.js"; import { PLATFORM_METADATA } from "../../knowledge/platforms/index.js"; import { SASS_USE_ASSEMBLY_NOTE } from "../../utils/sass.js"; import type { CreateElevationsParams } from "../schemas.js"; -export function handleCreateElevations(params: CreateElevationsParams) { +export async function handleCreateElevations(params: CreateElevationsParams) { + const output = + params.output ?? (params.platform === "angular" ? "sass" : "css"); + + if (output === "css") { + return handleCssOutput(params); + } + + return handleSassOutput(params); +} + +async function handleCssOutput(params: CreateElevationsParams) { + try { + const result = await generateElevationsCss({ + designSystem: params.designSystem, + }); + + const formattedCss = formatCssOutput(result.css, result.description); + + const responseParts: string[] = [result.description]; + responseParts.push(""); + responseParts.push("Output format: CSS custom properties"); + responseParts.push(""); + responseParts.push("```css"); + responseParts.push(formattedCss.trimEnd()); + responseParts.push("```"); + + return { + content: [{ type: "text" as const, text: responseParts.join("\n") }], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text" as const, + text: `**Error generating CSS elevations**\n\n${message}`, + }, + ], + isError: true, + }; + } +} + +function handleSassOutput(params: CreateElevationsParams) { const result = generateElevations({ platform: params.platform, licensed: params.licensed, @@ -15,10 +63,8 @@ export function handleCreateElevations(params: CreateElevationsParams) { name: params.name, }); - // Build response text const responseParts: string[] = [result.description]; - // Add platform hint if not specified const platformNote = params.platform ? `Platform: ${PLATFORM_METADATA[params.platform]?.name ?? params.platform}` : "Platform: Not specified (generic output). Specify `platform` for optimized code."; @@ -34,11 +80,6 @@ export function handleCreateElevations(params: CreateElevationsParams) { responseParts.push(SASS_USE_ASSEMBLY_NOTE); return { - content: [ - { - type: "text" as const, - text: responseParts.join("\n"), - }, - ], + content: [{ type: "text" as const, text: responseParts.join("\n") }], }; } diff --git a/packages/mcp/src/tools/handlers/palette.ts b/packages/mcp/src/tools/handlers/palette.ts index 49f3ce3e..9301bd4e 100644 --- a/packages/mcp/src/tools/handlers/palette.ts +++ b/packages/mcp/src/tools/handlers/palette.ts @@ -15,7 +15,8 @@ import type { CreatePaletteParams } from "../schemas.js"; export async function handleCreatePalette(params: CreatePaletteParams) { const variant = params.variant ?? "light"; - const output = params.output ?? "sass"; + const output = + params.output ?? (params.platform === "angular" ? "sass" : "css"); // Validate surface and gray colors against the variant const validation = await validatePaletteColors({ diff --git a/packages/mcp/src/tools/handlers/theme.ts b/packages/mcp/src/tools/handlers/theme.ts index 47b7e8d2..733b5580 100644 --- a/packages/mcp/src/tools/handlers/theme.ts +++ b/packages/mcp/src/tools/handlers/theme.ts @@ -2,6 +2,7 @@ * Handler for create_theme tool. */ +import { formatCssOutput, generateThemeCss } from "../../generators/css.js"; import { generateTheme } from "../../generators/sass.js"; import { SASS_USE_ASSEMBLY_NOTE } from "../../utils/sass.js"; import { @@ -16,9 +17,10 @@ import type { CreateThemeParams } from "../schemas.js"; export async function handleCreateTheme(params: CreateThemeParams) { const variant = params.variant ?? "light"; + const output = + params.output ?? (params.platform === "angular" ? "sass" : "css"); // Validate surface color against the variant - // Note: For themes, we only validate surfaceColor (gray is not exposed in theme params) const validation = await validatePaletteColors({ variant, surface: params.surfaceColor, @@ -31,6 +33,96 @@ export async function handleCreateTheme(params: CreateThemeParams) { surface: params.surfaceColor, }); + if (output === "css") { + return handleCssOutput(params, validation, suitabilityAnalysis); + } + + return handleSassOutput(params, validation, suitabilityAnalysis); +} + +async function handleCssOutput( + params: CreateThemeParams, + validation: Awaited>, + suitabilityAnalysis: Awaited>, +) { + try { + const result = await generateThemeCss({ + platform: params.platform, + designSystem: params.designSystem, + variant: params.variant, + primaryColor: params.primaryColor, + secondaryColor: params.secondaryColor, + surfaceColor: params.surfaceColor, + fontFamily: params.fontFamily, + includeTypography: params.includeTypography, + includeElevations: params.includeElevations, + includeSpacing: params.includeSpacing, + name: params.name, + }); + + const formattedCss = formatCssOutput(result.css, result.description); + + const responseParts: string[] = [result.description]; + responseParts.push(""); + responseParts.push("Output format: CSS custom properties"); + + if (params.platform) { + const { PLATFORM_METADATA } = await import( + "../../knowledge/platforms/index.js" + ); + responseParts.push(""); + responseParts.push( + `Platform: ${PLATFORM_METADATA[params.platform]?.name ?? params.platform}`, + ); + } + + if (result.angularCaveat) { + responseParts.push(""); + responseParts.push( + "⚠️ Angular note: This CSS output contains only CSS custom properties (--ig-* variables). " + + "It does not include the component-scoped class styles generated by Angular's core() and theme() mixins. " + + 'For full Angular theme output use output: "sass".', + ); + } + + const validationText = formatValidationResult(validation); + if (validationText) { + responseParts.push(""); + responseParts.push(validationText); + } + + if (!suitabilityAnalysis.allSuitable) { + responseParts.push(""); + responseParts.push(formatPaletteSuitabilityWarnings(suitabilityAnalysis)); + } + + responseParts.push(""); + responseParts.push("```css"); + responseParts.push(formattedCss.trimEnd()); + responseParts.push("```"); + + return { + content: [{ type: "text" as const, text: responseParts.join("\n") }], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text" as const, + text: `**Error generating CSS theme**\n\n${message}`, + }, + ], + isError: true, + }; + } +} + +async function handleSassOutput( + params: CreateThemeParams, + validation: Awaited>, + suitabilityAnalysis: Awaited>, +) { // Generate the theme code const result = generateTheme({ platform: params.platform, diff --git a/packages/mcp/src/tools/handlers/typography.ts b/packages/mcp/src/tools/handlers/typography.ts index 7a75f23e..94687c9f 100644 --- a/packages/mcp/src/tools/handlers/typography.ts +++ b/packages/mcp/src/tools/handlers/typography.ts @@ -2,12 +2,61 @@ * Handler for create_typography tool. */ +import { + formatCssOutput, + generateTypographyCss, +} from "../../generators/css.js"; import { generateTypography } from "../../generators/sass.js"; import { PLATFORM_METADATA } from "../../knowledge/platforms/index.js"; import { SASS_USE_ASSEMBLY_NOTE } from "../../utils/sass.js"; import type { CreateTypographyParams } from "../schemas.js"; -export function handleCreateTypography(params: CreateTypographyParams) { +export async function handleCreateTypography(params: CreateTypographyParams) { + const output = + params.output ?? (params.platform === "angular" ? "sass" : "css"); + + if (output === "css") { + return handleCssOutput(params); + } + + return handleSassOutput(params); +} + +async function handleCssOutput(params: CreateTypographyParams) { + try { + const result = await generateTypographyCss({ + fontFamily: params.fontFamily, + designSystem: params.designSystem, + }); + + const formattedCss = formatCssOutput(result.css, result.description); + + const responseParts: string[] = [result.description]; + responseParts.push(""); + responseParts.push("Output format: CSS custom properties"); + responseParts.push(""); + responseParts.push("```css"); + responseParts.push(formattedCss.trimEnd()); + responseParts.push("```"); + + return { + content: [{ type: "text" as const, text: responseParts.join("\n") }], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text" as const, + text: `**Error generating CSS typography**\n\n${message}`, + }, + ], + isError: true, + }; + } +} + +function handleSassOutput(params: CreateTypographyParams) { const result = generateTypography({ platform: params.platform, licensed: params.licensed, @@ -17,10 +66,8 @@ export function handleCreateTypography(params: CreateTypographyParams) { name: params.name, }); - // Build response text const responseParts: string[] = [result.description]; - // Add platform hint if not specified const platformNote = params.platform ? `Platform: ${PLATFORM_METADATA[params.platform]?.name ?? params.platform}` : "Platform: Not specified (generic output). Specify `platform` for optimized code."; @@ -36,11 +83,6 @@ export function handleCreateTypography(params: CreateTypographyParams) { responseParts.push(SASS_USE_ASSEMBLY_NOTE); return { - content: [ - { - type: "text" as const, - text: responseParts.join("\n"), - }, - ], + content: [{ type: "text" as const, text: responseParts.join("\n") }], }; } diff --git a/packages/mcp/src/tools/schemas.ts b/packages/mcp/src/tools/schemas.ts index 03b73c73..c377a3b2 100644 --- a/packages/mcp/src/tools/schemas.ts +++ b/packages/mcp/src/tools/schemas.ts @@ -159,6 +159,7 @@ export const createTypographySchema = z.object({ .optional() .describe(PARAM_DESCRIPTIONS.customScale), name: z.string().optional().describe(PARAM_DESCRIPTIONS.name), + output: outputFormatSchema.describe(PARAM_DESCRIPTIONS.output), }); /** @@ -171,6 +172,7 @@ export const createElevationsSchema = z.object({ PARAM_DESCRIPTIONS.elevationPreset, ), name: z.string().optional().describe(PARAM_DESCRIPTIONS.name), + output: outputFormatSchema.describe(PARAM_DESCRIPTIONS.output), }); /** @@ -201,6 +203,7 @@ export const createThemeSchema = z.object({ .optional() .default(true) .describe(PARAM_DESCRIPTIONS.includeSpacing), + output: outputFormatSchema.describe(PARAM_DESCRIPTIONS.output), }); /**