From 0093370f070a9d2c3455dea3ca060ddb6d63bec5 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Tue, 22 Jul 2025 09:00:49 +0300 Subject: [PATCH 1/3] feat(ui5-icon): display custom SVG, defined as JSX template --- docs/2-advanced/03-using-icons.md | 70 ++++++++++++++++--- packages/main/src/IconTemplate.tsx | 35 +++++++++- .../bundle-assets/IconPensilJSXTemplate.tsx | 10 +++ .../bundle-assets/IconPensilLitTemplate.ts | 15 ++++ packages/main/src/bundle.esm.ts | 22 +++++- packages/main/test/pages/Icon_custom.html | 2 + 6 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx create mode 100644 packages/main/src/bundle-assets/IconPensilLitTemplate.ts diff --git a/docs/2-advanced/03-using-icons.md b/docs/2-advanced/03-using-icons.md index 4611dbc878b1..69f24e0b5559 100644 --- a/docs/2-advanced/03-using-icons.md +++ b/docs/2-advanced/03-using-icons.md @@ -110,6 +110,68 @@ After the SVG icons collection is registered, you can use the custom icons every ## Custom SVG icons +### with JSX Templates + +In case you need to use a fully custom SVG, that can be used `ui5-icon`, `ui5-button` or any component that offers API to display an icon via icon name, you can provide a custom JSX template, rendering the custom SVG and register it under a custom name. + + +#### 1. Create JSX template + +First, create a JSX template for the icon you need: + +```tsx +// MyPensilSVGTemplate.tsx +export default function MyPensilSVGTemplate() { + return ( + + + + + + + ) +}; +``` + +#### 2. Register the Custom Icon + +You can use the `registerIcon` to register the custom icon as follows: + +```js +import { registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js"; +import myPensilSVGTemplate from "./MyPensilSVGTemplate.js"; + +// create the icon data for registration +const iconPensil = { + customTemplate: myPensilSVGTemplate, + collection: "custom", + viewBox: "0 0 24 24", // optional +} + +// register the icon +registerIcon("pensil", iconPensil); +``` + +#### 3. Use the Custom Icon + +Finally, the icon can be used anywhere. +```html + + + +``` + +**Tip:** for multi-colored icons, you can specify multiple SVG elements and put a fill/color attribute with a specific value on each element. +```html + + + + +``` + + + +### (deprecated) with HBS Templates In case you need to use a fully custom SVG with multiple SVG elements like `circle` and `rect` instead of only a custom `path`, you can provide a custom renderer and register it for usage in ``. First, create a template for the icon you need: @@ -163,11 +225,3 @@ Finally, the icon can be used anywhere. ``` - -Tip: for multi-colored icons, you can specify multiple SVG elements and put a fill/color attribute with a specific value on each element. -```html - - - - -``` diff --git a/packages/main/src/IconTemplate.tsx b/packages/main/src/IconTemplate.tsx index 67e18b8cc9a9..97fc063f68b3 100644 --- a/packages/main/src/IconTemplate.tsx +++ b/packages/main/src/IconTemplate.tsx @@ -1,5 +1,10 @@ import type Icon from "./Icon.js"; +type LegacySVGTemplate = { + strings: string[], + values?: Array +} + export default function IconTemplate(this: Icon) { return ( - {this.customSvg && - - } + {this.customSvg && svgTemplate.call(this, this.customSvg)} {this.pathData.map(path => ( @@ -33,3 +36,29 @@ export default function IconTemplate(this: Icon) { ); } + +function svgTemplate(this: Icon, template: object | LegacySVGTemplate) { + if ((template as LegacySVGTemplate).strings) { + return ; + } + return template; +} + +// Renders legacy (lit) SVG template +function renderLegacySVGTemplate(customTemplate: LegacySVGTemplate): string { + const { strings, values } = customTemplate; + + return strings.map((str: string, i: number) => { + const value = values && values[i]; + + if (typeof value === "string") { + return str + value; + } + + if (typeof value === "object" && value?.strings) { + return str + renderLegacySVGTemplate(value); + } + + return str; + }).join(""); +} diff --git a/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx b/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx new file mode 100644 index 000000000000..b00f81986d5c --- /dev/null +++ b/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx @@ -0,0 +1,10 @@ +export default function IconPensilJSXTemplate() { + return ( + + + + + + + ) +}; \ No newline at end of file diff --git a/packages/main/src/bundle-assets/IconPensilLitTemplate.ts b/packages/main/src/bundle-assets/IconPensilLitTemplate.ts new file mode 100644 index 000000000000..656fd149e6d1 --- /dev/null +++ b/packages/main/src/bundle-assets/IconPensilLitTemplate.ts @@ -0,0 +1,15 @@ +import type UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import { html, svg } from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { getCustomElementsScopingSuffix } from "@ui5/webcomponents-base/dist/CustomElementsScopeUtils.js"; + +function block0 (this: any, context: UI5Element, tags: string[], suffix: string | undefined) { + return html`${blockSVG1.call(this, context, tags, suffix)}`; +} + +function blockSVG1 (this: any, context: UI5Element, tags: string[], suffix: string | undefined) { + return svg``; +}; + +export default function IconPensilLitTemplate(this: any) { + return block0.call(this, this, (this.constructor as typeof UI5Element).tagsToScope, getCustomElementsScopingSuffix()); +} \ No newline at end of file diff --git a/packages/main/src/bundle.esm.ts b/packages/main/src/bundle.esm.ts index 6ff524ca75e5..abb8d9eea915 100644 --- a/packages/main/src/bundle.esm.ts +++ b/packages/main/src/bundle.esm.ts @@ -3,7 +3,7 @@ // eslint-disable-next-line import testAssetsCommon from "./bundle.common.bootstrap.js"; // code that needs to be executed before other modules -import { registerIconLoader } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js"; +import { registerIconLoader, registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js"; // SAP Icons import accept, { getPathData } from "@ui5/webcomponents-icons/dist/accept.js"; @@ -215,6 +215,26 @@ registerIconLoader("my-icons", () => { }]); }); +// custom SVG template (Lit or JSX), registered as an icon +import IconPensilJSXTemplate from "./bundle-assets/IconPensilJSXTemplate.js"; +import IconPensilLitTemplate from "./bundle-assets/IconPensilLitTemplate.js"; +; +registerIcon('pencil', { + customTemplate: IconPensilJSXTemplate, + viewBox : "0 0 16 16", + packageName : "custom-svg-icon", + collection : "custom-svg-icons", + pathData : "pencil", +}); + +registerIcon('pencil2', { + customTemplate: IconPensilLitTemplate, + viewBox : "0 0 16 16", + packageName : "custom-svg-icon", + collection : "custom-svg-icons", + pathData : "pencil2", +}); + // @ts-ignore window["sap-ui-webcomponents-bundle"] = testAssets; diff --git a/packages/main/test/pages/Icon_custom.html b/packages/main/test/pages/Icon_custom.html index 1ff10aee6e03..031d9e6c8a83 100644 --- a/packages/main/test/pages/Icon_custom.html +++ b/packages/main/test/pages/Icon_custom.html @@ -12,5 +12,7 @@ home
tnt/actor
my-icons/mark
+ pensil
+ pensil2
From 158ad4d0df2e34fe3af3596895a1dbb340bf54a1 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Tue, 22 Jul 2025 10:37:30 +0300 Subject: [PATCH 2/3] chore: fix lint errors --- packages/main/src/IconTemplate.tsx | 4 +-- .../bundle-assets/IconPensilJSXTemplate.tsx | 4 +-- .../bundle-assets/IconPensilLitTemplate.ts | 18 ++++++------ packages/main/src/bundle.esm.ts | 28 +++++++++---------- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/main/src/IconTemplate.tsx b/packages/main/src/IconTemplate.tsx index 97fc063f68b3..b4d101487b96 100644 --- a/packages/main/src/IconTemplate.tsx +++ b/packages/main/src/IconTemplate.tsx @@ -50,11 +50,11 @@ function renderLegacySVGTemplate(customTemplate: LegacySVGTemplate): string { return strings.map((str: string, i: number) => { const value = values && values[i]; - + if (typeof value === "string") { return str + value; } - + if (typeof value === "object" && value?.strings) { return str + renderLegacySVGTemplate(value); } diff --git a/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx b/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx index b00f81986d5c..390243e10c43 100644 --- a/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx +++ b/packages/main/src/bundle-assets/IconPensilJSXTemplate.tsx @@ -6,5 +6,5 @@ export default function IconPensilJSXTemplate() { - ) -}; \ No newline at end of file + ); +} diff --git a/packages/main/src/bundle-assets/IconPensilLitTemplate.ts b/packages/main/src/bundle-assets/IconPensilLitTemplate.ts index 656fd149e6d1..eaa3c765cf0d 100644 --- a/packages/main/src/bundle-assets/IconPensilLitTemplate.ts +++ b/packages/main/src/bundle-assets/IconPensilLitTemplate.ts @@ -1,15 +1,13 @@ -import type UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; import { html, svg } from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; -import { getCustomElementsScopingSuffix } from "@ui5/webcomponents-base/dist/CustomElementsScopeUtils.js"; - -function block0 (this: any, context: UI5Element, tags: string[], suffix: string | undefined) { - return html`${blockSVG1.call(this, context, tags, suffix)}`; + +function block0(this: any) { + return html`${blockSVG1.call(this)}`; } -function blockSVG1 (this: any, context: UI5Element, tags: string[], suffix: string | undefined) { +function blockSVG1(this: any) { return svg``; -}; +} -export default function IconPensilLitTemplate(this: any) { - return block0.call(this, this, (this.constructor as typeof UI5Element).tagsToScope, getCustomElementsScopingSuffix()); -} \ No newline at end of file +export default function IconPensilLitTemplate(this: any) { + return block0.call(this); +} diff --git a/packages/main/src/bundle.esm.ts b/packages/main/src/bundle.esm.ts index abb8d9eea915..65880608717e 100644 --- a/packages/main/src/bundle.esm.ts +++ b/packages/main/src/bundle.esm.ts @@ -128,6 +128,10 @@ import ListItemCustom from "./ListItemCustom.js"; import ListItemGroupHeader from "./ListItemGroupHeader.js"; import ListItemGroup from "./ListItemGroup.js"; +// custom SVG template (Lit or JSX), registered as an icon +import IconPensilJSXTemplate from "./bundle-assets/IconPensilJSXTemplate.js"; +import IconPensilLitTemplate from "./bundle-assets/IconPensilLitTemplate.js"; + const icons = [accept, acceptv4, acceptv5, actor, actorv2, actorv3, icon3d, icon3dv1, icon3dv2]; const testAssets = { @@ -215,24 +219,20 @@ registerIconLoader("my-icons", () => { }]); }); -// custom SVG template (Lit or JSX), registered as an icon -import IconPensilJSXTemplate from "./bundle-assets/IconPensilJSXTemplate.js"; -import IconPensilLitTemplate from "./bundle-assets/IconPensilLitTemplate.js"; -; -registerIcon('pencil', { +registerIcon("pencil", { customTemplate: IconPensilJSXTemplate, - viewBox : "0 0 16 16", - packageName : "custom-svg-icon", - collection : "custom-svg-icons", - pathData : "pencil", + viewBox: "0 0 16 16", + packageName: "custom-svg-icon", + collection: "custom-svg-icons", + pathData: "pencil", }); -registerIcon('pencil2', { +registerIcon("pencil2", { customTemplate: IconPensilLitTemplate, - viewBox : "0 0 16 16", - packageName : "custom-svg-icon", - collection : "custom-svg-icons", - pathData : "pencil2", + viewBox: "0 0 16 16", + packageName: "custom-svg-icon", + collection: "custom-svg-icons", + pathData: "pencil2", }); // @ts-ignore From bee7209e43e3e7e4c913567c69b00d2d1e8fae87 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Tue, 22 Jul 2025 11:26:59 +0300 Subject: [PATCH 3/3] docs: remove hbs section --- docs/2-advanced/03-using-icons.md | 57 ------------------------------- 1 file changed, 57 deletions(-) diff --git a/docs/2-advanced/03-using-icons.md b/docs/2-advanced/03-using-icons.md index 69f24e0b5559..94ea9ae90557 100644 --- a/docs/2-advanced/03-using-icons.md +++ b/docs/2-advanced/03-using-icons.md @@ -168,60 +168,3 @@ Finally, the icon can be used anywhere. ``` - - - -### (deprecated) with HBS Templates -In case you need to use a fully custom SVG with multiple SVG elements like `circle` and `rect` instead of only a custom `path`, you can provide a custom renderer and register it for usage in ``. - -First, create a template for the icon you need: - -`BakeryDining.hbs` -```html - - - - - - - - - - - - - -``` - -The `.hbs` file must start exactly with the content `""` or `"`. In that case, a `path` won't be rendered. You can also specify a custom `viewBox` size, as the default one is `0 0 512 512`. - -Finally, the icon can be used anywhere. -```html - - -```