Icon ComboBox using Hilla Lit

I made a client sided ComboBox with Vaadin Icons as value.
Feel free to use or give some feedback :slight_smile:

import "@vaadin/combo-box";
import "@vaadin/icon";
import "@vaadin/icons";
import '@vaadin/item';
import '@vaadin/list-box';
import '@vaadin/multi-select-combo-box';
import "@vaadin/tooltip";

import { ComboBoxFilterChangedEvent, ComboBoxValueChangedEvent } from "@vaadin/combo-box";
import type { ComboBoxLitRenderer } from '@vaadin/combo-box/lit.js';
import { comboBoxRenderer } from '@vaadin/combo-box/lit.js';
import { SelectValueChangedEvent } from "@vaadin/select";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { View } from "../view";

import fontIcons from '@vaadin/icons/assets/vaadin-font-icons.json' assert { type: 'json' };
// import { Iconset } from '@vaadin/icon/vaadin-iconset.js';

declare type FontIconType = {
    name: string
    code: string
    meta: string[]
    categories: string[]
}

@customElement("icon-combo-box")
export class IconComboBox extends View {

    @state()
    private items: FontIconType[] = [];
    @state()
    private filteredItems: FontIconType[] = [];

    @property({ type: String })
    value?: string;

    @property({ type: Boolean })
    disabled: boolean = false
    @property({ type: Boolean })
    readonly: boolean = false

    connectedCallback() {
        super.connectedCallback();
        this.loadFiles();
    }

    private loadFiles() {
        this.items = fontIcons;
        this.items.sort((i1, i2) => i1.name.localeCompare(i2.name));
        this.filteredItems = this.items

        // optionally get only names
        // const iconset: any = Iconset.getIconset('vaadin');
        // const vaadinIcons = iconset._icons;
        // this.icons = Array.from(new Map(Object.entries(vaadinIcons)).keys()).filter((key): key is string => typeof key === 'string');
    }

    private filterChanged(event: ComboBoxFilterChangedEvent) {
        const filter = event.detail.value;
        this.filteredItems = this.items.filter(({ name, meta, categories }) =>
            name.toLowerCase().startsWith(filter.toLowerCase()) ||
            meta.some(m => m.toLowerCase().startsWith(filter.toLowerCase())) ||
            categories.some(c => c.toLowerCase().startsWith(filter.toLowerCase()))
        );
    }

    private valueChanged(event: ComboBoxValueChangedEvent) {
        this.value = event.detail.value
    }

    render() {
        return html`
            <vaadin-combo-box
                placeholder="Select an icon"
                .filteredItems="${this.filteredItems}"
                @value-changed="${this.valueChanged}"
                @filter-changed="${this.filterChanged}"
                item-value-path="name"
                item-label-path="name"
                clear-button-visible
                @selected-items-changed=${(e: SelectValueChangedEvent) => { }}
                ?readonly=${this.readonly || this.disabled}
                ${comboBoxRenderer(this.itemRenderer(), [])}
                >
            </vaadin-combo-box>
        `
    }

    private itemRenderer(): ComboBoxLitRenderer<FontIconType> {
        return icon => html`
            <div id=${'icon-item-' + icon.name} class="flex flex-row gap-m items-center" title=${this.generateItemDescription(icon)}>
                <vaadin-icon 
                    style="height: var(--lumo-space-m); width: var(--lumo-space-m);"
                    icon=${'vaadin:' + icon.name}>
                </vaadin-icon>
                <span class="text-s">
                    ${icon.name}
                </span>
            </div>
        `;
    }

    generateItemDescription(icon: FontIconType): string {
        if (!icon || (!icon.categories && !icon.meta)) {
            return ''
        }
        return 'Categorie: ' + icon.categories.join(' ') + ' - Tags: ' + icon.meta.join(' ')
    }
}
2 Likes

Maybe adding the Lumo Iconset too, but I didn’t found a nice json like for Vaadin icons. :slight_smile: