ui5-webcomponents
ui5-webcomponents copied to clipboard
Support for custom svg libraries without using handlebars
Feature Request Description
We are using a react wrapper around the ui5-webcomponents in our application and have tried implementing custom icons from an internal svg library. Since we do not use handlebars, the instructions created in this PR do not work for us.
One workaround we have found is to use the svg template literal from the LitRenderer directly and copy the contents of the svg like so:
import { svg } from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import { registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js";
const my_icon = svg`<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 75 75" xml:space="preserve">
//other svg content
</svg>`
registerIcon("my_icon", {
customTemplate: () => my_icon,
collection: "custom",
});
Proposed Solution
We would like to have a universal solution that works for many icons and optimally supports dynamic loading. Preferably one should either pass the svg icon to the component directly, or register the svg without having to run a build script beforehand.
Proposed Alternatives
If there is a better solution or something similiar has already been implemented, an updated documentation would be awesome.
Priority
- [x] High
In the PR you linked, we discussed that forcing HBS usage might be too restrictive. The reason we kept it for custom icons is because lit templates are the current output of HBS files, but this is not guaranteed and might change in the future.
This means the <ui5-icon> renderer is using lit and has to render embedded content which is also lit. In case we switch the HBS for ui5-webcomponents to some other technology, it would be more difficult to provide a bridge to other rendering technologies.
You currently have two options:
-
use a lit template like you have shown above, but this might stop working if the webcomponents start rendering with some other underlying technology. Please check the docs, you should not pass
<svg>as it will be rendered by<ui5-icon>, you have to pass a<g>with the content of the original SVG and the viewbox is a parameter toregisterIcon. -
Add the HBS processor to you project.
I suggest you use option 2 as more future-proof, and it should not add so much overhead.
Instructions to add HBS support to your project:
npm install @ui5/webcomponents-tools -D- create a file
package-scripts.jsand reexport the default build scripts
const getScripts = require("@ui5/webcomponents-tools/components-package/nps.js");
const scripts = getScripts({});
module.exports = {
scripts
};
- create your icon in
src/MyIcon.jsand put your content (change SVG -> G)
<g>
<path d="xxx"></path>
</g>
- add a build command before all other commands
npm run nps build.templates - if you change the icon content a lot, you could also add a watch, but this gets more complicated as it needs to run in parallel to your dev server
npm run nps watch.templates - the generated icon can be imported from
dist/generated/templates/MyIconTemplate.lit.jsPassing an SVG directly as child to<ui5-icon>was also considered, but this would not work everywhere an icon is created by name, like the avatar that has anicon=property and an SVG cannot be passed there.
Regarding dynamic loading - the module that imports and registers the icon can be addressed with dynamic import(), so that is possible.
Let me know if that works for you.
I have created a demo repo here.
If we have to use handlebars we would like to extract the logic to a new package. The aim is to keep the same workflow as with the current ui5-icons e.g.
import navBack from "@ui5/webcomponents-icons/dist/nav-back.js";
=>
import customIcon from "custom-icons/dist/customIcon.js";
Is there a straightforward way or some documentation that outlines the steps of how to achieve this?
Do you mean how to publish to npm? The process is described here: https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages
In a nutshell, you have to make sure your package.json contains the correct information, especially the package name as it will be published under this name.
It is a good idea to publish only the dist folder. I suggest you use the files field in package.json to explicitly list the dist folder.
https://docs.npmjs.com/cli/v8/configuring-npm/package-json#files
Final step is to create an npm account and run npm publish as described in the first link. Then you will be able to import your icon as you showed.
No, that was not what I meant.
This is the output of the lit template in my example repo (MyIconTemplate.lit.js):
/* eslint no-unused-vars: 0 */
import { html, svg, repeat, classMap, styleMap, ifDefined, unsafeHTML, scopeTag } from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
let block0 = () => {
return svg`<g><path d="M150 0 L75 200 L225 200 Z" /></g>; `
}
export default block0;
These are the outputs of the ui5-icons package (navigation-right-arrow.d.ts:
declare const pathData: string;
declare const ltr: boolean;
declare const accData: null
declare const _default: "SAP-icons-v5/navigation-right-arrow";
export default _default;
export { pathData, ltr, accData };
and navigation-right-arrow.js:
import { registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js";
const name = "navigation-right-arrow";
const pathData = "M172 155q-9-11-9-22 0-13 9-22t22-9q12 0 21 9l124 124q10 9 10 22 0 12-10 22L215 403q-9 9-21 9-13 0-22-9-9-11-9-22 0-13 9-22l102-102z";
const ltr = false;
const accData = null;
const collection = "SAP-icons-v5";
const packageName = "@ui5/webcomponents-icons";
registerIcon(name, { pathData, ltr, collection, packageName });
export default "SAP-icons-v5/navigation-right-arrow";
export { pathData, ltr, accData };
This is all seemingly generated from this svg (navigation-right-arrow.svg):
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M296 268q11-12 0-23L139 86q-10-10-10-23t10-22q9-10 22-10t23 10l191 193q9 9 9 22.5t-9 22.5L183 471q-10 10-23 10t-23-10q-9-9-9-22.5t9-22.5z"/>
</svg>
I would like to replicate the same behaviour in my demo repo so we can replicate the same workflow as with the existing ui5-icons. How do I do that?
I see, thanks for clarifying.
Let me explain the internal workflow. The source format for the icons is the .woff2 font from openui5. You can see the openui5 icons here:
https://sapui5.hana.ondemand.com/sdk/test-resources/sap/m/demokit/iconExplorer/webapp/index.html#/overview/SAP-icons
Each icon is a character from a font file which you can inspect in the elements panel and the font file from the network panel.
We have an internal tool which converts the font file into a json file with the path data for each icon. This is then submitted in our repository for each icon collection we support. Example here: https://github.com/SAP/ui5-webcomponents/blob/main/packages/icons/src/v5/SAP-icons.json
The build for each icon package then generates JS files for single imports, SVG files for stand alone usage and the JSON file itself can be used as a bundle. All from the above mentioned JSON file.
As you correctly pointed out, the import of the icon in our package (which is a generated file) has the side effect of calling registerIcon which makes the icon known to the <ui5-icon> component. In your case, the custom SVG needs to be a template, not just path data. There are no tools which generate the file that registers the icon, so #5865 describes that you need to call registerIcon yourself.
https://github.com/SAP/ui5-webcomponents/blob/bf2fc4b43bea86d330eb66fbe60dc226e9e9f550/docs/1-getting-started/04-using-icons.md#custom-svg-icons
import { registerIcon } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js";
import iconBakeryDiningTemplate from "./dist/generated/templates/BakeryDiningTemplate.lit.js";
// create the icon data for registration
const iconBakeryDining = {
customTemplate: iconBakeryDiningTemplate,
viewBox: "0 0 24 24",
collection: "custom",
}
// register the icon
registerIcon("bakery-dining", iconBakeryDining);
In the object you pass as second parameter to registerIcon, you can pass {ltr, accData, packageName} in addition to customTemplate, viewBox and collection from the example above.
Final step that is missing is to export the icon name. as a default export, so it can be used in your JSX templates.
There are simply no tools to generate an icon registration module for custom SVG templates, it is just a function call to register icon in the end and much easier to do manually.
Let me know in case more information is needed.