Files
catalog/ts_web/elements/sio-icon.ts

163 lines
4.2 KiB
TypeScript

import {
DeesElement,
html,
property,
customElement,
cssManager,
css,
type TemplateResult,
} from '@design.estate/dees-element';
import * as lucideIcons from 'lucide';
import { createElement } from 'lucide';
declare global {
interface HTMLElementTagNameMap {
'sio-icon': SioIcon;
}
}
@customElement('sio-icon')
export class SioIcon extends DeesElement {
public static demo = () => html`
<div style="display: flex; gap: 16px; align-items: center;">
<sio-icon icon="search"></sio-icon>
<sio-icon icon="message-square" color="#3b82f6"></sio-icon>
<sio-icon icon="x" size="32"></sio-icon>
<sio-icon icon="send" strokeWidth="3"></sio-icon>
</div>
`;
@property({ type: String })
public accessor icon: string;
@property({ type: Number })
public accessor size: number = 24;
@property({ type: String })
public accessor color: string = 'currentColor';
@property({ type: Number })
public accessor strokeWidth: number = 2;
// Cache for rendered icons
private static iconCache = new Map<string, string>();
private static readonly MAX_CACHE_SIZE = 100;
// Track last rendered properties to avoid unnecessary updates
private lastIcon: string | null = null;
private lastSize: number | null = null;
private lastColor: string | null = null;
private lastStrokeWidth: number | null = null;
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
vertical-align: middle;
}
#iconContainer {
display: flex;
align-items: center;
justify-content: center;
}
#iconContainer svg {
display: block;
width: 100%;
height: 100%;
}
`,
];
public render(): TemplateResult {
return html`
<div id="iconContainer" style="width: ${this.size}px; height: ${this.size}px;"></div>
`;
}
public updated() {
// Check if we need to update
if (
this.lastIcon === this.icon &&
this.lastSize === this.size &&
this.lastColor === this.color &&
this.lastStrokeWidth === this.strokeWidth
) {
return;
}
// Update tracking properties
this.lastIcon = this.icon;
this.lastSize = this.size;
this.lastColor = this.color;
this.lastStrokeWidth = this.strokeWidth;
const container = this.shadowRoot?.querySelector('#iconContainer') as HTMLElement;
if (!container || !this.icon) return;
// Clear container
container.innerHTML = '';
// Create cache key
const cacheKey = `${this.icon}:${this.size}:${this.color}:${this.strokeWidth}`;
// Check cache
if (SioIcon.iconCache.has(cacheKey)) {
container.innerHTML = SioIcon.iconCache.get(cacheKey)!;
return;
}
try {
// Convert icon name to PascalCase (e.g., 'message-square' -> 'MessageSquare')
const pascalCaseName = this.icon
.split('-')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join('');
const iconComponent = (lucideIcons as any)[pascalCaseName];
if (!iconComponent) {
console.warn(`Lucide icon '${pascalCaseName}' not found`);
return;
}
// Create the icon element
const svgElement = createElement(iconComponent, {
size: this.size,
color: this.color,
strokeWidth: this.strokeWidth,
});
if (svgElement) {
// Cache the result
const svgString = svgElement.outerHTML;
SioIcon.iconCache.set(cacheKey, svgString);
// Limit cache size
if (SioIcon.iconCache.size > SioIcon.MAX_CACHE_SIZE) {
const firstKey = SioIcon.iconCache.keys().next().value;
SioIcon.iconCache.delete(firstKey);
}
// Append to container
container.appendChild(svgElement);
}
} catch (error) {
console.error(`Error rendering icon ${this.icon}:`, error);
}
}
public async disconnectedCallback() {
await super.disconnectedCallback();
// Clear references
this.lastIcon = null;
this.lastSize = null;
this.lastColor = null;
this.lastStrokeWidth = null;
}
}