initial
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import { injectCssVariables } from '../../00variables.js';
|
||||
|
||||
export const demoFunc = () => {
|
||||
injectCssVariables();
|
||||
return html`
|
||||
<style>
|
||||
.demo-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.demo-section h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--dees-muted-foreground);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.demo-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
.icon-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--dees-radius);
|
||||
background: var(--dees-surface);
|
||||
min-width: 80px;
|
||||
}
|
||||
.icon-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--dees-muted-foreground);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Common Icons</h3>
|
||||
<div class="demo-row">
|
||||
<div class="icon-item">
|
||||
<dees-mobile-icon icon="home" size="24"></dees-mobile-icon>
|
||||
<span class="icon-label">home</span>
|
||||
</div>
|
||||
<div class="icon-item">
|
||||
<dees-mobile-icon icon="settings" size="24"></dees-mobile-icon>
|
||||
<span class="icon-label">settings</span>
|
||||
</div>
|
||||
<div class="icon-item">
|
||||
<dees-mobile-icon icon="user" size="24"></dees-mobile-icon>
|
||||
<span class="icon-label">user</span>
|
||||
</div>
|
||||
<div class="icon-item">
|
||||
<dees-mobile-icon icon="search" size="24"></dees-mobile-icon>
|
||||
<span class="icon-label">search</span>
|
||||
</div>
|
||||
<div class="icon-item">
|
||||
<dees-mobile-icon icon="menu" size="24"></dees-mobile-icon>
|
||||
<span class="icon-label">menu</span>
|
||||
</div>
|
||||
<div class="icon-item">
|
||||
<dees-mobile-icon icon="x" size="24"></dees-mobile-icon>
|
||||
<span class="icon-label">x</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Sizes</h3>
|
||||
<div class="demo-row">
|
||||
<dees-mobile-icon icon="star" size="16"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="star" size="20"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="star" size="24"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="star" size="32"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="star" size="48"></dees-mobile-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Colors</h3>
|
||||
<div class="demo-row">
|
||||
<dees-mobile-icon icon="heart" size="24" color="var(--dees-danger)"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="check-circle" size="24" color="var(--dees-success)"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="alert-triangle" size="24" color="var(--dees-warning)"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="info" size="24" color="var(--dees-primary)"></dees-mobile-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Stroke Width</h3>
|
||||
<div class="demo-row">
|
||||
<dees-mobile-icon icon="circle" size="32" strokeWidth="1"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="circle" size="32" strokeWidth="2"></dees-mobile-icon>
|
||||
<dees-mobile-icon icon="circle" size="32" strokeWidth="3"></dees-mobile-icon>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
189
ts_web/elements/00group-ui/dees-mobile-icon/dees-mobile-icon.ts
Normal file
189
ts_web/elements/00group-ui/dees-mobile-icon/dees-mobile-icon.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
DeesElement,
|
||||
css,
|
||||
cssManager,
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as lucideIcons from 'lucide';
|
||||
import { createElement } from 'lucide';
|
||||
import { demoFunc } from './dees-mobile-icon.demo.js';
|
||||
|
||||
// Create a type-safe icon name type
|
||||
export type LucideIconName = keyof typeof lucideIcons;
|
||||
|
||||
// Cache for rendered icons to improve performance
|
||||
const iconCache = new Map<string, string>();
|
||||
const MAX_CACHE_SIZE = 500;
|
||||
|
||||
function limitCacheSize() {
|
||||
if (iconCache.size > MAX_CACHE_SIZE) {
|
||||
const keysToDelete = Array.from(iconCache.keys()).slice(0, MAX_CACHE_SIZE / 5);
|
||||
keysToDelete.forEach(key => iconCache.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-mobile-icon': DeesMobileIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-mobile-icon')
|
||||
export class DeesMobileIcon extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
|
||||
@property({ type: String })
|
||||
accessor icon: string = '';
|
||||
|
||||
@property({ type: Number })
|
||||
accessor size: number = 20;
|
||||
|
||||
@property({ type: String })
|
||||
accessor color: string = 'currentColor';
|
||||
|
||||
@property({ type: Number })
|
||||
accessor strokeWidth: number = 2;
|
||||
|
||||
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;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private renderLucideIcon(iconName: string): string {
|
||||
const cacheKey = `${iconName}:${this.size}:${this.color}:${this.strokeWidth}`;
|
||||
|
||||
if (iconCache.has(cacheKey)) {
|
||||
return iconCache.get(cacheKey) || '';
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert kebab-case to PascalCase (e.g., "chevron-down" -> "ChevronDown")
|
||||
const pascalCaseName = iconName
|
||||
.split('-')
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join('');
|
||||
|
||||
if (!(lucideIcons as any)[pascalCaseName]) {
|
||||
console.warn(`Lucide icon '${pascalCaseName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const svgElement = createElement((lucideIcons as any)[pascalCaseName], {
|
||||
color: this.color,
|
||||
size: this.size,
|
||||
strokeWidth: this.strokeWidth
|
||||
});
|
||||
|
||||
if (!svgElement) {
|
||||
console.warn(`createElement returned empty result for ${pascalCaseName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const result = svgElement.outerHTML;
|
||||
iconCache.set(cacheKey, result);
|
||||
limitCacheSize();
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`Error rendering Lucide icon ${iconName}:`, error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
#iconContainer {
|
||||
width: ${this.size}px;
|
||||
height: ${this.size}px;
|
||||
}
|
||||
</style>
|
||||
<div id="iconContainer"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
updated() {
|
||||
// Check if we actually need to update the icon
|
||||
if (this.lastIcon === this.icon &&
|
||||
this.lastSize === this.size &&
|
||||
this.lastColor === this.color &&
|
||||
this.lastStrokeWidth === this.strokeWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastIcon = this.icon || null;
|
||||
this.lastSize = this.size;
|
||||
this.lastColor = this.color;
|
||||
this.lastStrokeWidth = this.strokeWidth;
|
||||
|
||||
const container = this.shadowRoot?.querySelector('#iconContainer');
|
||||
if (!container || !this.icon) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
const pascalCaseName = this.icon
|
||||
.split('-')
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join('');
|
||||
|
||||
if ((lucideIcons as any)[pascalCaseName]) {
|
||||
const svgElement = createElement((lucideIcons as any)[pascalCaseName], {
|
||||
color: this.color,
|
||||
size: this.size,
|
||||
strokeWidth: this.strokeWidth
|
||||
});
|
||||
|
||||
if (svgElement) {
|
||||
container.appendChild(svgElement);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to string-based approach
|
||||
const iconHtml = this.renderLucideIcon(this.icon);
|
||||
if (iconHtml) {
|
||||
container.innerHTML = iconHtml;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error updating icon ${this.icon}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
this.lastIcon = null;
|
||||
this.lastSize = null;
|
||||
this.lastColor = null;
|
||||
this.lastStrokeWidth = null;
|
||||
}
|
||||
}
|
||||
1
ts_web/elements/00group-ui/dees-mobile-icon/index.ts
Normal file
1
ts_web/elements/00group-ui/dees-mobile-icon/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dees-mobile-icon.js';
|
||||
Reference in New Issue
Block a user