diff --git a/renderers/angular/src/lib/catalog/icon.ts b/renderers/angular/src/lib/catalog/icon.ts index 770420d31..3d92cd031 100644 --- a/renderers/angular/src/lib/catalog/icon.ts +++ b/renderers/angular/src/lib/catalog/icon.ts @@ -15,6 +15,7 @@ */ import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; +import { toMaterialSymbolLigature } from '@a2ui/web_core/styles/icons'; import { DynamicComponent } from '../rendering/dynamic-component'; import * as Primitives from '@a2ui/web_core/types/primitives'; @@ -45,5 +46,8 @@ import * as Primitives from '@a2ui/web_core/types/primitives'; }) export class Icon extends DynamicComponent { readonly name = input.required(); - protected readonly resolvedName = computed(() => this.resolvePrimitive(this.name())); + protected readonly resolvedName = computed(() => { + const resolvedName = this.resolvePrimitive(this.name()); + return resolvedName ? toMaterialSymbolLigature(resolvedName) : null; + }); } diff --git a/renderers/lit/src/0.8/ui/icon.ts b/renderers/lit/src/0.8/ui/icon.ts index aec35da79..fde3c4e4f 100644 --- a/renderers/lit/src/0.8/ui/icon.ts +++ b/renderers/lit/src/0.8/ui/icon.ts @@ -18,6 +18,7 @@ import { html, css, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { Root } from "./root.js"; import { A2uiMessageProcessor } from "@a2ui/web_core/data/model-processor"; +import { toMaterialSymbolLigature } from "@a2ui/web_core/styles/icons"; import * as Primitives from "@a2ui/web_core/types/primitives"; import { classMap } from "lit/directives/class-map.js"; import { styleMap } from "lit/directives/style-map.js"; @@ -68,7 +69,7 @@ export class Icon extends Root { } const render = (url: string) => { - url = url.replace(/([A-Z])/gm, "_$1").toLocaleLowerCase(); + url = toMaterialSymbolLigature(url); return html`${url}`; }; diff --git a/renderers/react/src/components/content/Icon.tsx b/renderers/react/src/components/content/Icon.tsx index be8a607ee..b8520efc8 100644 --- a/renderers/react/src/components/content/Icon.tsx +++ b/renderers/react/src/components/content/Icon.tsx @@ -16,19 +16,11 @@ import {memo} from 'react'; import type * as Types from '@a2ui/web_core/types/types'; +import {toMaterialSymbolLigature} from '@a2ui/web_core/styles/icons'; import type {A2UIComponentProps} from '../../types'; import {useA2UIComponent} from '../../hooks/useA2UIComponent'; import {classMapToString, stylesToObject} from '../../lib/utils'; -/** - * Convert camelCase to snake_case for Material Symbols font. - * e.g., "shoppingCart" -> "shopping_cart" - * This matches the Lit renderer's approach. - */ -function toSnakeCase(str: string): string { - return str.replace(/([A-Z])/g, '_$1').toLowerCase(); -} - /** * Icon component - renders an icon using Material Symbols Outlined font. * @@ -51,7 +43,7 @@ export const Icon = memo(function Icon({node, surfaceId}: A2UIComponentProps { + it('leaves simple icon names unchanged', () => { + assert.strictEqual(toMaterialSymbolLigature('home'), 'home'); + }); + + it('converts camelCase names to snake_case ligatures', () => { + assert.strictEqual(toMaterialSymbolLigature('shoppingCart'), 'shopping_cart'); + assert.strictEqual(toMaterialSymbolLigature('accountCircle'), 'account_circle'); + }); + + it('keeps existing separators and lowercases the result', () => { + assert.strictEqual( + toMaterialSymbolLigature('unknownIconName12345'), + 'unknown_icon_name12345' + ); + assert.strictEqual(toMaterialSymbolLigature('already_snake_case'), 'already_snake_case'); + }); +}); diff --git a/renderers/web_core/src/v0_8/styles/icons.ts b/renderers/web_core/src/v0_8/styles/icons.ts index e62d7f509..4cdff7c32 100644 --- a/renderers/web_core/src/v0_8/styles/icons.ts +++ b/renderers/web_core/src/v0_8/styles/icons.ts @@ -14,6 +14,14 @@ * limitations under the License. */ +/** + * Convert an A2UI camelCase icon name into the snake_case ligature used by + * Material Symbols. + */ +export function toMaterialSymbolLigature(iconName: string): string { + return iconName.replace(/([A-Z])/g, '_$1').toLowerCase(); +} + /** * CSS classes for Google Symbols. *