Search code examples
javascriptjavasvgpolymervaadin-flow

Vaadin Flow - how to add a custom icon set?


I am using vaadin flow v21. I like to create a custom component which is using an own svg icon set. I tried to create the set based on the vaadin-icon but the svg definition will not be copied over into the shadow root.

I did the following

  1. Created a CustomIcon Component class which is derived from com.vaadin.flow.component.icon.Icon
  2. added a JSModule which will contain the new icon set as a polymer template.

Custom Component Class

@JsModule("./icons/custom-iconset-svg.js")
public class CustomIcon extends Icon {

    public CustomIcon(String collection, String icon) {
      super(collection,icon);
    }
}

File "custom-iconset-svg.js"

import '@vaadin/vaadin-icon/vaadin-iconset.js';
import '@vaadin/vaadin-icon/vaadin-icon.js';

const $_documentContainer = document.createElement('template');

$_documentContainer.innerHTML = `<vaadin-iconset-svg name="custom" size="16">
<svg>
<defs>
<vaadin-iconset name="vaadin" size="16">
<svg><defs>
<g id="custom:abacus"><path d="..."></path></g>
</defs>
</svg>
</vaadin-iconset-svg>`;

document.head.appendChild($_documentContainer.content);

Usage of the new "CustomIcon" Class

Icon icon = new CustomIcon("custom","abacus")
add(icon);

This creates later on the following html elements

  1. It adds in the <head> section the custom set of icons <vaadin-iconset-svg name="custom" ... >
  2. Creates the element <vaadin-icon icon="custom:abacus">

The issue is that the <svg> section of the shadowroot for that new element is empty. So not sure what I missed here?

<vaadin-icon style="width: var(--lumo-icon-size-s); height: var(--lumo-icon-size-s); margin-right: var(--lumo-space-s);" icon="custom:abacus">
#shadow-root
<style>...</<style>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<!---->
</svg>
</vaadin-icon>

Solution

  • Here's an example that works, tested in Vaadin 21.0.3

    File frontend/icons/custom-iconset.js

    import '@vaadin/vaadin-icon/vaadin-iconset.js';
    import '@vaadin/vaadin-icon/vaadin-icon.js';
    
    const $_documentContainer = document.createElement('template');
    
    $_documentContainer.innerHTML = `<vaadin-iconset name="custom" size="24">
    <svg><defs>
    <g id="custom:sample1"><path d="M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3ZM9 17H7V10H9V17ZM13 17H11V7H13V17ZM17 17H15V13H17V17Z"/></g>
    <g id="custom:sample2"><path d="M7 17L8.4 15.6L5.8 13L16 13L16 11L5.8 11L8.4 8.4L7 7L2 12L7 17Z" /><path d="M19 19H11V21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3H11V5H19V19Z" /></g>
    </defs></svg>
    </vaadin-iconset>`;
    
    document.head.appendChild($_documentContainer.content);
    

    Convenience enum:

    public enum CustomIcon implements IconFactory {
        SAMPLE1, SAMPLE2;
        @Override
        public Icon create() {
            return new Icon("custom", name().toLowerCase(Locale.ENGLISH));
        }
    }
    

    Sample Usage:

    @Route("")
    @JsModule("icons/custom-iconset.js")
    public class ShowIconsView extends Div {
        public ShowIconsView() {
            for (CustomIcon customIcon : CustomIcon.values()) {
                add(customIcon.create());
            }
        }
    }
    

    You could probably add the @JsModule to a common parent layout. Full example on Github.