Skip to content

Commit 1e7e2a6

Browse files
authored
feat(ui5-icon): display custom SVG, defined as JSX template (#11966)
Display custom SVG, defined as JSX template. Requested by internal channels by the community.
1 parent e2411a6 commit 1e7e2a6

File tree

6 files changed

+110
-39
lines changed

6 files changed

+110
-39
lines changed

docs/2-advanced/03-using-icons.md

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,61 +110,58 @@ After the SVG icons collection is registered, you can use the custom icons every
110110
111111
## Custom SVG icons
112112
113-
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 `<ui5-icon>`.
113+
### with JSX Templates
114114
115-
First, create a template for the icon you need:
115+
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.
116116
117-
`BakeryDining.hbs`
118-
```html
119-
<g>
120-
<rect fill="none" height="24" width="24" y="0" />
121-
</g>
122-
<g>
123-
<g>
124-
<path
125-
d="M7.6,8.67l-2.01,0.8c-0.22,0.09-0.34,0.31-0.31,0.54l2.4,5.98h1.23l-0.62-6.9C8.25,8.75,7.91,8.54,7.6,8.67 z"
126-
opacity=".3" />
127-
<path d="M3.07,16.1c-0.27,0.53,0.29,1.09,0.82,0.83l1.68-0.84l-1.08-2.71L3.07,16.1z" opacity=".3" />
128-
<path
129-
d="M13.36,6.99h-2.71c-0.27,0-0.53,0.23-0.5,0.54l0.77,8.45h2.17l0.77-8.45C13.88,7.22,13.63,6.99,13.36,6.99z"
130-
opacity=".3" />
131-
<path
132-
d="M18.41,9.47l-2.01-0.8c-0.31-0.12-0.65,0.09-0.68,0.42l-0.62,6.9h1.23l2.4-5.98 C18.75,9.78,18.63,9.56,18.41,9.47z"
133-
opacity=".3" />
134-
<path d="M19.52,13.39l-1.08,2.7l1.68,0.84c0.52,0.26,1.09-0.3,0.82-0.83L19.52,13.39z" opacity=".3" />
135-
<path
136-
d="M20.5,10.94c0.13-0.32,0.1-0.23,0.15-0.39c0.3-1.21-0.34-2.47-1.5-2.93l-2.01-0.8c-0.46-0.18-0.95-0.21-1.41-0.12 c-0.11-0.33-0.29-0.63-0.52-0.89C14.73,5.29,14.06,5,13.36,5h-2.71C9.94,5,9.27,5.29,8.8,5.81C8.56,6.07,8.38,6.37,8.27,6.69 C7.81,6.6,7.32,6.63,6.86,6.81l-2.01,0.8c-1.16,0.46-1.8,1.72-1.5,2.93l0.15,0.38C1.1,15.55,1,15.55,1,16.38 c0,0.91,0.46,1.74,1.24,2.22c1.42,0.88,2.49,0.14,4-0.61h11.53c1.52,0.76,1.86,1.01,2.63,1.01c1,0,2.61-0.77,2.61-2.61 C23,15.54,22.88,15.51,20.5,10.94z M3.88,16.93c-0.53,0.26-1.09-0.3-0.82-0.83l1.41-2.72l1.08,2.71L3.88,16.93z M7.68,15.99 l-2.4-5.98C5.25,9.78,5.37,9.56,5.59,9.47l2.01-0.8c0.31-0.12,0.65,0.08,0.68,0.42l0.62,6.9H7.68z M13.09,15.99h-2.17l-0.77-8.45 c-0.03-0.31,0.23-0.54,0.5-0.54h2.71c0.27,0,0.53,0.23,0.5,0.54L13.09,15.99z M16.32,15.99h-1.23l0.62-6.9 c0.03-0.33,0.37-0.54,0.68-0.42l2.01,0.8c0.22,0.09,0.34,0.31,0.31,0.54L16.32,15.99z M20.12,16.93l-1.68-0.84l1.08-2.7l1.41,2.71 C21.21,16.63,20.64,17.19,20.12,16.93z" />
137-
</g>
138-
</g>
117+
118+
#### 1. Create JSX template
119+
120+
First, create a JSX template for the icon you need:
121+
122+
```tsx
123+
// MyPensilSVGTemplate.tsx
124+
export default function MyPensilSVGTemplate() {
125+
return (
126+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
127+
<g clip-path="url(#clip0_2221_23716)"><path d="M11.3333 1.99998C11.503 1.79933 11.7131 1.63601 11.9499 1.52043C12.1868 1.40485 12.4453 1.33953 12.709 1.32865C12.9727 1.31777 13.2358 1.36156 13.4815 1.45723C13.7272 1.55291 13.9502 1.69836 14.1361 1.88432C14.3221 2.07029 14.467 2.29268 14.5616 2.53734C14.6562 2.78199 14.6985 3.04353 14.6857 3.3053C14.6728 3.56706 14.6052 3.8233 14.4872 4.05769C14.3691 4.29207 14.2032 4.49947 13.9999 4.66664L4.99992 13.6666L1.33325 14.6666L2.33325 11L11.3333 1.99998Z" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3.33331L12.6667 5.99998" stroke-linecap="round" stroke-linejoin="round"/></g>
128+
<defs>
129+
<clipPath id="clip0_2221_23716"><rect width="16" height="16"/></clipPath>
130+
</defs>
131+
</svg>
132+
)
133+
};
139134
```
140135
141-
The `.hbs` file must start exactly with the content `"<g>"` or `"<g "` for correct compilation. The HBS compiler will generate a template that you can then import and register with the icon regsitry.
136+
#### 2. Register the Custom Icon
137+
138+
You can use the `registerIcon` to register the custom icon as follows:
142139
143-
`bundle.esm.js`
144140
```js
145141
import { registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js";
146-
import iconBakeryDiningTemplate from "./dist/generated/templates/BakeryDiningTemplate.lit.js";
142+
import myPensilSVGTemplate from "./MyPensilSVGTemplate.js";
147143

148144
// create the icon data for registration
149-
const iconBakeryDining = {
150-
customTemplate: iconBakeryDiningTemplate,
151-
viewBox: "0 0 24 24",
145+
const iconPensil = {
146+
customTemplate: myPensilSVGTemplate,
152147
collection: "custom",
148+
viewBox: "0 0 24 24", // optional
153149
}
154150

155151
// register the icon
156-
registerIcon("bakery-dining", iconBakeryDining);
152+
registerIcon("pensil", iconPensil);
157153
```
158154
159-
The icon data object should fill the `customTemplate` property with a template that will be included inside the SVG of the `<ui5-icon>`. 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`.
155+
#### 3. Use the Custom Icon
160156
161157
Finally, the icon can be used anywhere.
162158
```html
163-
<ui5-icon name="custom/backery-dining"></ui5-icon>
164-
<ui5-avatar icon="custom/backery-dining" size="XS"></ui5-avatar>
159+
<ui5-icon name="custom/pensil"></ui5-icon>
160+
<ui5-button icon="custom/pensil"></ui5-button>
161+
<ui5-avatar icon="custom/pensil" size="XS"></ui5-avatar>
165162
```
166163
167-
Tip: for multi-colored icons, you can specify multiple SVG elements and put a fill/color attribute with a specific value on each element.
164+
**Tip:** for multi-colored icons, you can specify multiple SVG elements and put a fill/color attribute with a specific value on each element.
168165
```html
169166
<g fill="none" stroke="currentColor" stroke-width="2">
170167
<path stroke-linecap="round" stroke-linejoin="round" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" fill="aqua"/>

packages/main/src/IconTemplate.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type Icon from "./Icon.js";
22

3+
type LegacySVGTemplate = {
4+
strings: string[],
5+
values?: Array<string | LegacySVGTemplate>
6+
}
7+
38
export default function IconTemplate(this: Icon) {
49
return (
510
<svg
@@ -22,9 +27,7 @@ export default function IconTemplate(this: Icon) {
2227
}
2328

2429
<g role="presentation">
25-
{this.customSvg &&
26-
<g dangerouslySetInnerHTML={{ __html: (this.customSvg as { strings?: string[] }).strings?.join("") ?? "" }}></g>
27-
}
30+
{this.customSvg && svgTemplate.call(this, this.customSvg)}
2831

2932
{this.pathData.map(path => (
3033
<path d={path}></path>
@@ -33,3 +36,29 @@ export default function IconTemplate(this: Icon) {
3336
</svg>
3437
);
3538
}
39+
40+
function svgTemplate(this: Icon, template: object | LegacySVGTemplate) {
41+
if ((template as LegacySVGTemplate).strings) {
42+
return <g dangerouslySetInnerHTML={{ __html: renderLegacySVGTemplate(this.customSvg as LegacySVGTemplate) ?? "" }}></g>;
43+
}
44+
return template;
45+
}
46+
47+
// Renders legacy (lit) SVG template
48+
function renderLegacySVGTemplate(customTemplate: LegacySVGTemplate): string {
49+
const { strings, values } = customTemplate;
50+
51+
return strings.map((str: string, i: number) => {
52+
const value = values && values[i];
53+
54+
if (typeof value === "string") {
55+
return str + value;
56+
}
57+
58+
if (typeof value === "object" && value?.strings) {
59+
return str + renderLegacySVGTemplate(value);
60+
}
61+
62+
return str;
63+
}).join("");
64+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default function IconPensilJSXTemplate() {
2+
return (
3+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
4+
<g clip-path="url(#clip0_2221_23716)"><path d="M11.3333 1.99998C11.503 1.79933 11.7131 1.63601 11.9499 1.52043C12.1868 1.40485 12.4453 1.33953 12.709 1.32865C12.9727 1.31777 13.2358 1.36156 13.4815 1.45723C13.7272 1.55291 13.9502 1.69836 14.1361 1.88432C14.3221 2.07029 14.467 2.29268 14.5616 2.53734C14.6562 2.78199 14.6985 3.04353 14.6857 3.3053C14.6728 3.56706 14.6052 3.8233 14.4872 4.05769C14.3691 4.29207 14.2032 4.49947 13.9999 4.66664L4.99992 13.6666L1.33325 14.6666L2.33325 11L11.3333 1.99998Z" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3.33331L12.6667 5.99998" stroke-linecap="round" stroke-linejoin="round"/></g>
5+
<defs>
6+
<clipPath id="clip0_2221_23716"><rect width="16" height="16"/></clipPath>
7+
</defs>
8+
</svg>
9+
);
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { html, svg } from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
2+
3+
function block0(this: any) {
4+
return html`<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">${blockSVG1.call(this)}</svg>`;
5+
}
6+
7+
function blockSVG1(this: any) {
8+
return svg`<g clip-path="url(#clip0_2221_23716)"><path d="M11.3333 1.99998C11.503 1.79933 11.7131 1.63601 11.9499 1.52043C12.1868 1.40485 12.4453 1.33953 12.709 1.32865C12.9727 1.31777 13.2358 1.36156 13.4815 1.45723C13.7272 1.55291 13.9502 1.69836 14.1361 1.88432C14.3221 2.07029 14.467 2.29268 14.5616 2.53734C14.6562 2.78199 14.6985 3.04353 14.6857 3.3053C14.6728 3.56706 14.6052 3.8233 14.4872 4.05769C14.3691 4.29207 14.2032 4.49947 13.9999 4.66664L4.99992 13.6666L1.33325 14.6666L2.33325 11L11.3333 1.99998Z" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3.33331L12.6667 5.99998" stroke-linecap="round" stroke-linejoin="round"/></g><defs><clipPath id="clip0_2221_23716"><rect width="16" height="16"/></clipPath></defs>`;
9+
}
10+
11+
export default function IconPensilLitTemplate(this: any) {
12+
return block0.call(this);
13+
}

packages/main/src/bundle.esm.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// eslint-disable-next-line
44
import testAssetsCommon from "./bundle.common.bootstrap.js"; // code that needs to be executed before other modules
55

6-
import { registerIconLoader } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js";
6+
import { registerIconLoader, registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js";
77

88
// SAP Icons
99
import accept, { getPathData } from "@ui5/webcomponents-icons/dist/accept.js";
@@ -128,6 +128,10 @@ import ListItemCustom from "./ListItemCustom.js";
128128
import ListItemGroupHeader from "./ListItemGroupHeader.js";
129129
import ListItemGroup from "./ListItemGroup.js";
130130

131+
// custom SVG template (Lit or JSX), registered as an icon
132+
import IconPensilJSXTemplate from "./bundle-assets/IconPensilJSXTemplate.js";
133+
import IconPensilLitTemplate from "./bundle-assets/IconPensilLitTemplate.js";
134+
131135
const icons = [accept, acceptv4, acceptv5, actor, actorv2, actorv3, icon3d, icon3dv1, icon3dv2];
132136

133137
const testAssets = {
@@ -215,6 +219,22 @@ registerIconLoader("my-icons", () => {
215219
}]);
216220
});
217221

222+
registerIcon("pencil", {
223+
customTemplate: IconPensilJSXTemplate,
224+
viewBox: "0 0 16 16",
225+
packageName: "custom-svg-icon",
226+
collection: "custom-svg-icons",
227+
pathData: "pencil",
228+
});
229+
230+
registerIcon("pencil2", {
231+
customTemplate: IconPensilLitTemplate,
232+
viewBox: "0 0 16 16",
233+
packageName: "custom-svg-icon",
234+
collection: "custom-svg-icons",
235+
pathData: "pencil2",
236+
});
237+
218238
// @ts-ignore
219239
window["sap-ui-webcomponents-bundle"] = testAssets;
220240

packages/main/test/pages/Icon_custom.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@
1212
<ui5-icon name="home"></ui5-icon>home<br>
1313
<ui5-icon name="tnt/actor"></ui5-icon>tnt/actor<br>
1414
<ui5-icon name="my-icons/mark"></ui5-icon>my-icons/mark<br>
15+
<ui5-icon name="custom-svg-icons/pencil"></ui5-icon>pensil<br>
16+
<ui5-icon name="custom-svg-icons/pencil2"></ui5-icon>pensil2<br>
1517
</body>
1618
</html>

0 commit comments

Comments
 (0)