ui5-webcomponents icon indicating copy to clipboard operation
ui5-webcomponents copied to clipboard

Support for custom svg libraries without using handlebars

Open jangoergens opened this issue 3 years ago • 2 comments

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

jangoergens avatar Oct 11 '22 08:10 jangoergens

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:

  1. 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 to registerIcon.

  2. 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:

  1. npm install @ui5/webcomponents-tools -D
  2. create a file package-scripts.js and reexport the default build scripts
const getScripts = require("@ui5/webcomponents-tools/components-package/nps.js");

const scripts = getScripts({});

module.exports = {
	scripts
};
  1. create your icon in src/MyIcon.js and put your content (change SVG -> G)
<g>
    <path d="xxx"></path>
</g>
  1. add a build command before all other commands npm run nps build.templates
  2. 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
  3. the generated icon can be imported from dist/generated/templates/MyIconTemplate.lit.js Passing 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 an icon= 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.

pskelin avatar Oct 11 '22 13:10 pskelin

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?

jangoergens avatar Oct 14 '22 13:10 jangoergens

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.

pskelin avatar Oct 20 '22 07:10 pskelin

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?

jangoergens avatar Oct 21 '22 08:10 jangoergens

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.

pskelin avatar Oct 21 '22 12:10 pskelin