import { demoFunc } from './dees-statsgrid.demo.js'; import * as plugins from './00plugins.js'; import { customElement, html, DeesElement, property, state, css, unsafeCSS, cssManager, } from '@design.estate/dees-element'; import type { TemplateResult } from '@design.estate/dees-element'; import './dees-icon.js'; import './dees-contextmenu.js'; import './dees-button.js'; declare global { interface HTMLElementTagNameMap { 'dees-statsgrid': DeesStatsGrid; } } export interface IStatsTile { id: string; title: string; value: number | string; unit?: string; type: 'number' | 'gauge' | 'percentage' | 'trend' | 'text'; // For gauge type gaugeOptions?: { min: number; max: number; thresholds?: Array<{value: number; color: string}>; }; // For trend type trendData?: number[]; // Visual customization color?: string; icon?: string; description?: string; // Tile-specific actions actions?: plugins.tsclass.website.IMenuItem[]; } @customElement('dees-statsgrid') export class DeesStatsGrid extends DeesElement { public static demo = demoFunc; @property({ type: Array }) public tiles: IStatsTile[] = []; @property({ type: Number }) public minTileWidth: number = 250; @property({ type: Number }) public gap: number = 16; @property({ type: Array }) public gridActions: plugins.tsclass.website.IMenuItem[] = []; @state() private contextMenuVisible = false; @state() private contextMenuPosition = { x: 0, y: 0 }; @state() private contextMenuActions: plugins.tsclass.website.IMenuItem[] = []; public static styles = [ cssManager.defaultStyles, css` :host { display: block; width: 100%; } /* CSS Variables for consistent spacing and sizing */ :host { --grid-gap: 16px; --tile-padding: 24px; --header-spacing: 16px; --content-min-height: 48px; --value-font-size: 30px; --unit-font-size: 16px; --label-font-size: 13px; --title-font-size: 14px; --description-spacing: 12px; --border-radius: 8px; --transition-duration: 0.15s; } /* Grid Layout */ .grid-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: calc(var(--grid-gap) * 1.5); min-height: 40px; } .grid-title { font-size: 16px; font-weight: 500; color: ${cssManager.bdTheme('#09090b', '#fafafa')}; letter-spacing: -0.01em; } .grid-actions { display: flex; gap: 6px; } .grid-actions dees-button { font-size: var(--label-font-size); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(${unsafeCSS(250)}px, 1fr)); gap: ${unsafeCSS(16)}px; width: 100%; } /* Tile Base Styles */ .stats-tile { background: ${cssManager.bdTheme('#ffffff', '#09090b')}; border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 11.8%)')}; border-radius: var(--border-radius); padding: var(--tile-padding); transition: all var(--transition-duration) ease; cursor: default; position: relative; overflow: hidden; display: flex; flex-direction: column; } .stats-tile:hover { background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 10.2%)')}; border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 85%)', 'hsl(215 20.2% 16.8%)')}; } .stats-tile.clickable { cursor: pointer; } .stats-tile.clickable:hover { transform: translateY(-1px); box-shadow: 0 2px 8px ${cssManager.bdTheme('rgba(0,0,0,0.04)', 'rgba(0,0,0,0.2)')}; } /* Tile Header */ .tile-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--header-spacing); flex-shrink: 0; } .tile-title { font-size: var(--title-font-size); font-weight: 500; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; margin: 0; letter-spacing: -0.01em; line-height: 1.2; } .tile-icon { opacity: 0.7; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; font-size: 16px; flex-shrink: 0; } /* Tile Content */ .tile-content { min-height: var(--content-min-height); display: flex; flex-direction: column; justify-content: center; flex: 1; } .tile-value { font-size: var(--value-font-size); font-weight: 600; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; line-height: 1; display: flex; align-items: baseline; gap: 4px; letter-spacing: -0.025em; } .tile-unit { font-size: var(--unit-font-size); font-weight: 400; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; letter-spacing: -0.01em; } .tile-description { font-size: var(--label-font-size); color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')}; margin-top: var(--description-spacing); letter-spacing: -0.01em; flex-shrink: 0; } /* Gauge Styles */ .gauge-wrapper { width: 100%; display: flex; justify-content: center; align-items: center; } .gauge-container { width: 140px; height: 80px; position: relative; margin-top: -10px; } .gauge-svg { width: 100%; height: 100%; } .gauge-background { fill: none; stroke: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')}; stroke-width: 8; } .gauge-fill { fill: none; stroke-width: 8; stroke-linecap: round; transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1); } .gauge-text { fill: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; font-size: var(--value-font-size); font-weight: 600; text-anchor: middle; dominant-baseline: alphabetic; letter-spacing: -0.025em; } .gauge-unit { font-size: var(--unit-font-size); fill: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; font-weight: 400; } /* Percentage Styles */ .percentage-wrapper { width: 100%; position: relative; } .percentage-value { font-size: var(--value-font-size); font-weight: 600; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; letter-spacing: -0.025em; margin-bottom: 8px; } .percentage-bar { width: 100%; height: 8px; background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')}; border-radius: 4px; overflow: hidden; } .percentage-fill { height: 100%; background: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 4px; } /* Trend Styles */ .trend-container { width: 100%; display: flex; flex-direction: column; gap: 8px; } .trend-header { display: flex; align-items: baseline; gap: 8px; } .trend-value { font-size: var(--value-font-size); font-weight: 600; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; letter-spacing: -0.025em; } .trend-unit { font-size: var(--unit-font-size); font-weight: 400; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; letter-spacing: -0.01em; } .trend-label { font-size: var(--label-font-size); font-weight: 500; color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')}; letter-spacing: -0.01em; margin-left: auto; } .trend-graph { width: 100%; height: 32px; position: relative; } .trend-svg { width: 100%; height: 100%; display: block; } .trend-line { fill: none; stroke: ${cssManager.bdTheme('hsl(215.4 16.3% 66.9%)', 'hsl(215 20.2% 55.1%)')}; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; } .trend-area { fill: ${cssManager.bdTheme('hsl(215.4 16.3% 66.9% / 0.1)', 'hsl(215 20.2% 55.1% / 0.08)')}; } /* Text Value Styles */ .text-value { font-size: var(--value-font-size); font-weight: 600; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; letter-spacing: -0.025em; } /* Context Menu */ dees-contextmenu { position: fixed; z-index: 1000; } `, ]; constructor() { super(); } public render(): TemplateResult { return html` ${this.gridActions.length > 0 ? html`