import { customElement, DeesElement, type TemplateResult, html, property, css, cssManager, } from '@design.estate/dees-element'; import { DeesIcon } from '@design.estate/dees-catalog'; import { demo } from './eco-applauncher-wifimenu.demo.js'; // Ensure dees-icon is registered DeesIcon; declare global { interface HTMLElementTagNameMap { 'eco-applauncher-wifimenu': EcoApplauncherWifimenu; } } export interface IWifiNetwork { ssid: string; signalStrength: number; // 0-100 secured: boolean; connected?: boolean; } @customElement('eco-applauncher-wifimenu') export class EcoApplauncherWifimenu extends DeesElement { public static demo = demo; public static demoGroup = 'App Launcher'; public static styles = [ cssManager.defaultStyles, css` :host { display: block; position: relative; } .menu-container { background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 10%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 20%)')}; border-radius: 12px; box-shadow: ${cssManager.bdTheme( '0 8px 32px rgba(0, 0, 0, 0.15)', '0 8px 32px rgba(0, 0, 0, 0.4)' )}; min-width: 280px; overflow: hidden; opacity: 0; transform: scale(0.95) translateY(-8px); transition: all 0.2s ease-out; pointer-events: none; } :host([open]) .menu-container { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; } .menu-header { display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 15%)')}; } .menu-title { font-size: 15px; font-weight: 600; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; display: flex; align-items: center; gap: 10px; } .toggle-switch { position: relative; width: 44px; height: 24px; background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(240 5% 25%)')}; border-radius: 12px; cursor: pointer; transition: background 0.2s ease; } .toggle-switch.active { background: hsl(217 91% 60%); } .toggle-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: white; border-radius: 50%; transition: transform 0.2s ease; box-shadow: ${cssManager.bdTheme('0 1px 3px rgba(0,0,0,0.2)', 'none')}; } .toggle-switch.active::after { transform: translateX(20px); } .network-list { max-height: 240px; overflow-y: auto; } .network-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer; transition: background 0.15s ease; } .network-item:hover { background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(240 5% 15%)')}; } .network-item.connected { background: ${cssManager.bdTheme('hsl(217 91% 95%)', 'hsl(217 91% 60% / 0.15)')}; } .network-item.connected:hover { background: ${cssManager.bdTheme('hsl(217 91% 92%)', 'hsl(217 91% 60% / 0.25)')}; } .signal-bars { display: flex; align-items: flex-end; gap: 2px; height: 16px; width: 20px; } .signal-bar { width: 4px; background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 40%)')}; border-radius: 1px; transition: background 0.2s ease; } .signal-bar.active { background: ${cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 90%)')}; } .signal-bar:nth-child(1) { height: 4px; } .signal-bar:nth-child(2) { height: 8px; } .signal-bar:nth-child(3) { height: 12px; } .signal-bar:nth-child(4) { height: 16px; } .network-info { flex: 1; min-width: 0; } .network-name { font-size: 14px; font-weight: 500; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .network-status { font-size: 12px; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')}; margin-top: 2px; } .network-secured { color: ${cssManager.bdTheme('hsl(0 0% 60%)', 'hsl(0 0% 50%)')}; } .menu-footer { padding: 12px 16px; border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 15%)')}; } .settings-link { display: flex; align-items: center; gap: 8px; font-size: 14px; color: hsl(217 91% 60%); cursor: pointer; transition: color 0.15s ease; } .settings-link:hover { color: hsl(217 91% 50%); } .disabled-message { padding: 32px 16px; text-align: center; color: ${cssManager.bdTheme('hsl(0 0% 60%)', 'hsl(0 0% 50%)')}; font-size: 14px; } `, ]; @property({ type: Boolean, reflect: true }) accessor open = false; @property({ type: Array }) accessor networks: IWifiNetwork[] = []; @property({ type: String }) accessor connectedNetwork: string | null = null; @property({ type: Boolean }) accessor wifiEnabled = true; private boundHandleClickOutside = this.handleClickOutside.bind(this); public render(): TemplateResult { return html` `; } private renderNetworkList(): TemplateResult { const sortedNetworks = [...this.networks].sort((a, b) => { // Connected network first, then by signal strength if (a.ssid === this.connectedNetwork) return -1; if (b.ssid === this.connectedNetwork) return 1; return b.signalStrength - a.signalStrength; }); return html`
${sortedNetworks.map((network) => this.renderNetworkItem(network))}
`; } private renderNetworkItem(network: IWifiNetwork): TemplateResult { const isConnected = network.ssid === this.connectedNetwork; const signalBars = this.getSignalBars(network.signalStrength); return html`
this.handleNetworkSelect(network)} >
${[1, 2, 3, 4].map((bar) => html`
`)}
${network.ssid}
${isConnected ? html`
Connected
` : ''}
${network.secured ? html` ` : ''}
`; } private renderDisabledMessage(): TemplateResult { return html`
Wi-Fi is turned off
`; } private getSignalBars(strength: number): number { if (strength >= 75) return 4; if (strength >= 50) return 3; if (strength >= 25) return 2; return 1; } private handleToggleWifi(): void { this.wifiEnabled = !this.wifiEnabled; this.dispatchEvent(new CustomEvent('wifi-toggle', { detail: { enabled: this.wifiEnabled }, bubbles: true, composed: true, })); } private handleNetworkSelect(network: IWifiNetwork): void { this.dispatchEvent(new CustomEvent('network-select', { detail: { network }, bubbles: true, composed: true, })); } private handleSettingsClick(): void { this.dispatchEvent(new CustomEvent('settings-click', { bubbles: true, composed: true, })); } private handleClickOutside(e: MouseEvent): void { if (this.open && !this.contains(e.target as Node)) { this.open = false; this.dispatchEvent(new CustomEvent('menu-close', { bubbles: true, composed: true, })); } } async connectedCallback(): Promise { await super.connectedCallback(); // Delay to prevent immediate close when clicking to open setTimeout(() => { document.addEventListener('click', this.boundHandleClickOutside); }, 0); } async disconnectedCallback(): Promise { await super.disconnectedCallback(); document.removeEventListener('click', this.boundHandleClickOutside); } }