logo dile-components dile-components

Icon Catalog

This is the full icon catalog — all icons from Lucide, Material, FontAwesome, Phosphor and Tabler combined, searchable and paginated — using the dile-crud component, with its insert/edit/delete features turned off through configuration.

Click any icon to see how to use it in your own project.

<script type="module">
  import { LitElement, html, css } from 'lit';
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
  import { CrudConfigBuilder } from '@dile/crud/lib/CrudConfigBuilder';
  import { ResponseApiAdapter } from '@dile/crud/lib/ResponseApiAdapter';
  import { contentCopyIcon } from '@dile/icons';
  import '@dile/crud/components/crud/crud.js';
  import '@dile/ui/components/modal/modal.js';
  import '@dile/ui/components/copy-text/copy-text.js';
  import '@dile/ui/components/icon/icon.js';

  const LIBRARY_DOCS_URL = {
    lucide: '/icons/lucide-icons/',
    material: '/icons/material-icons/',
    fontawesome: '/icons/fontawesome-icons/',
    phosphor: '/icons/phosphor-icons/',
    tabler: '/icons/tabler-icons/',
  };

  class DemoIconCatalogItem extends LitElement {
    static styles = css`
      :host { 
        display: flex;
        flex-direction: column;
        flex: 1;      
        width: 100%;  
        justify-content: space-between;
      }
      .icon-catalog-item {
        display: flex;
        align-items: center;
        gap: 0.75rem;
        padding: 0.5rem 0;
      }
      .icon-preview svg {
        width: 28px;
        height: 28px;
        color: #444;
        display: block;
      }
      .meta {
        display: flex;
        flex-direction: column;
        gap: 0.15rem;
      }
      .name {
        font-weight: 600;
      }
      .badges {
        display: flex;
        gap: 0.15rem;
        font-size: 0.75rem;
        line-height: 0.75rem;
      }
      .badge {
        padding: 0.25rem 0.25rem;
        border-radius: 5px;
        background: #eee;
        color: #555;
      }
      .badge.brand {
        background: #ffe4b5;
        color: #7a4b00;
      }
      .icon-catalog-item {
        cursor: pointer;
      }
    `;

    static get properties() {
      return { icon: { type: Object } };
    }

    render() {
      if (!this.icon) return html``;
      return html`
        <div class="icon-catalog-item" @click="${this.selectIcon}">
          <span class="icon-preview">${unsafeSVG(this.icon.svg)}</span>
          <div class="meta">
            <span class="name">${this.icon.name}</span>
            <span class="badges">
              <span class="badge">${this.icon.library}</span>
              ${this.icon.is_brand ? html`<span class="badge brand">brand</span>` : ''}
            </span>
          </div>
        </div>
      `;
    }

    selectIcon() {
      this.dispatchEvent(new CustomEvent('icon-selected', {
        bubbles: true,
        composed: true,
        detail: { icon: this.icon },
      }));
    }
  }
  customElements.define('demo-icon-catalog-item', DemoIconCatalogItem);

  // Listens for the "icon-selected" event dispatched (bubbles + composed, so it
  // crosses every shadow boundary up to window) by demo-icon-catalog-item, and shows
  // usage instructions for that icon in a dile-modal.
  class DemoIconUsageModal extends LitElement {
    static styles = css`
      :host {
        display: block;
        --dile-modal-width: 90%;
        --dile-modal-max-width: 690px;
      }
      .icon-preview svg {
        width: 40px;
        height: 40px;
        color: #444;
      }
      .usage-content {
        font-size: 0.85rem;
      }
      .usage-content h2 {
        font-size: 1.15rem;
        margin: 0.4rem 0;
      }
      .usage-content h3 {
        font-size: 0.95rem;
        margin-bottom: 0.2rem;
      }
      .usage-content p {
        margin-top: 0.2rem;
      }
      dile-copy-text {
        display: block;
        --dile-copy-text-hover-color: inherit;
        --dile-copy-text-hover-text-decoration: none;
      }
      .code-block {
        display: flex;
        align-items: center;
        gap: 0.5rem;
      }
      pre {
        flex: 1;
        min-width: 0;
        background: #f5f5f5;
        padding: 0.6rem 0.8rem;
        border-radius: 6px;
        overflow-x: auto;
        margin: 0;
      }
      .copy-icon {
        flex-shrink: 0;
        --dile-icon-size: 16px;
        --dile-icon-color: #999;
        pointer-events: none;
      }
      dile-copy-text:hover .copy-icon {
        --dile-icon-color: #0066cc;
      }
    `;

    static get properties() {
      return { icon: { type: Object } };
    }

    connectedCallback() {
      super.connectedCallback();
      this.handleIconSelected = this.handleIconSelected.bind(this);
      window.addEventListener('icon-selected', this.handleIconSelected);
    }

    disconnectedCallback() {
      window.removeEventListener('icon-selected', this.handleIconSelected);
      super.disconnectedCallback();
    }

    handleIconSelected(e) {
      this.icon = e.detail.icon;
      this.shadowRoot.getElementById('modal').open();
    }

    render() {
      return html`
        <dile-modal id="modal" showCloseIcon>
          ${this.icon ? this.usageTemplate(this.icon) : ''}
        </dile-modal>
      `;
    }

    usageTemplate(icon) {
      const libraryDocsUrl = LIBRARY_DOCS_URL[icon.library];
      const installCommand = 'npm install @dile/iconlib';
      const importStatement = `import '${icon.import}';`;
      const specificTag = `<${icon.tag}></${icon.tag}>`;
      const genericTag = `<dile-iconlib icon="${icon.library}.${icon.name}"></dile-iconlib>`;
      return html`
        <div class="usage-content">
          <span class="icon-preview">${unsafeSVG(icon.svg)}</span>
          <h2>${icon.name} icon</h2>

          <h3>1. Install the package</h3>
          <dile-copy-text content="${installCommand}">
            <div class="code-block">
              <pre><code>${installCommand}</code></pre>
              <dile-icon class="copy-icon" .icon="${contentCopyIcon}"></dile-icon>
            </div>
          </dile-copy-text>

          <h3>2. Import the icon component</h3>
          <dile-copy-text content="${importStatement}">
            <div class="code-block">
              <pre><code>${importStatement}</code></pre>
              <dile-icon class="copy-icon" .icon="${contentCopyIcon}"></dile-icon>
            </div>
          </dile-copy-text>
          <h3>3. Use its specific tag</h3>
          <dile-copy-text content="${specificTag}">
            <div class="code-block">
              <pre><code>${specificTag}</code></pre>
              <dile-icon class="copy-icon" .icon="${contentCopyIcon}"></dile-icon>
            </div>
          </dile-copy-text>
          <p>More usage examples on the <a href="${libraryDocsUrl}">${icon.library} icons page</a>.</p>

          <h3>4. Or use the generic dile-iconlib tag</h3>
          <dile-copy-text content="${genericTag}">
            <div class="code-block">
              <pre><code>${genericTag}</code></pre>
              <dile-icon class="copy-icon" .icon="${contentCopyIcon}"></dile-icon>
            </div>
          </dile-copy-text>
          <p>See the <a href="/icons/dile-iconlib/">dile-iconlib documentation</a> for details on the generic component.</p>
        </div>
      `;
    }
  }
  customElements.define('demo-icon-usage-modal', DemoIconUsageModal);

  // The API wraps the paginated result as { data: { countItems, result: { data: [...], current_page, next_page_url, prev_page_url } } }.
  // That already matches ResponseApiAdapter's default getPaginationData() (response.data), so only
  // getElementList() needs to be overridden to reach into result.data for the actual icon array.
  class IconResponseApiAdapter extends ResponseApiAdapter {
    getElementList() {
      return this.response.data.result.data;
    }
  }

  const iconsConfig = new CrudConfigBuilder('https://api-icons.dile-components.com/api/icons', {
    customization: {
      disablePagination: false,
      disableKeywordSearch: false,
      disableSort: false,
      disableFilter: false,
      hideCheckboxSelection: true,
      disableInsert: true,
      disableEdit: true,
      disableUpdate: true,
      disableDelete: true,
      disableRestore: true,
      disableHelp: true,
    },
    pageSize: {
      available: [24, 48],
      initial: 24,
    },
    responseAdapter: new IconResponseApiAdapter(),
    templates: {
      item: (icon) => html`<demo-icon-catalog-item .icon=${icon}></demo-icon-catalog-item>`,
    },
    sort: {
      options: [{ name: 'name', label: 'Name', direction: 'asc' }],
      initialSortField: 'name',
    },
    availableFilters: [
      {
        name: 'library',
        label: 'Library',
        active: false,
        value: false,
        type: 'select',
        options: [
          { value: 'lucide', label: 'Lucide' },
          { value: 'material', label: 'Material' },
          { value: 'fontawesome', label: 'FontAwesome' },
          { value: 'phosphor', label: 'Phosphor' },
          { value: 'tabler', label: 'Tabler' },
        ],
      },
      {
        name: 'is_brand',
        label: 'Only brands',
        active: false,
        value: false,
        type: 'boolean',
      },
    ],
  });

  // <dile-crud> already includes the search box and filters panel, gated by
  // config.customization — no need to wire <dile-input-search>/<dile-crud-filters> by hand.
  class DemoIconCatalogList extends LitElement {
    static styles = css`
      :host { 
        display: block;
      }
      @media(min-width: 420px) {
        :host { 
          --dile-crud-list-item-display: flex;
          --dile-crud-list-elements-container-template-columns: 1fr 1fr;
          --dile-crud-list-item-line-separator: none;

        }
      }
      @media(min-width: 700px) {
        :host { 
          --dile-crud-list-elements-container-template-columns: 1fr 1fr 1fr;
        }
      }
      @media(min-width: 950px) {
        :host { 
          --dile-crud-list-elements-container-template-columns: 1fr 1fr 1fr 1fr;
        }
      }
    `;

    static get properties() {
      return { config: { type: Object } };
    }

    constructor() {
      super();
      this.config = iconsConfig.getConfig();
    }

    render() {
      return html`<dile-crud title="Icon Catalog" .config="${this.config}"></dile-crud>`;
    }
  }
  customElements.define('demo-icon-catalog-list', DemoIconCatalogList);
</script>
<demo-icon-catalog-list></demo-icon-catalog-list>
<demo-icon-usage-modal></demo-icon-usage-modal>