import * as plugins from './00plugins.js'; import { DeesElement, type TemplateResult, property, customElement, html, css, cssManager, state, } from '@design.estate/dees-element'; @customElement('dees-appui-profiledropdown') export class DeesAppuiProfileDropdown extends DeesElement { public static demo = () => html` console.log('Profile') }, { name: 'Account', iconName: 'settings', action: async () => console.log('Account') }, { divider: true }, { name: 'Help & Support', iconName: 'helpCircle', action: async () => console.log('Help') }, { name: 'Keyboard Shortcuts', iconName: 'keyboard', shortcut: 'Cmd+K', action: async () => console.log('Shortcuts') }, { divider: true }, { name: 'Sign Out', iconName: 'logOut', action: async () => console.log('Sign out') } ]} .isOpen=${true} > `; @property({ type: Object }) public user?: { name: string; email?: string; avatar?: string; status?: 'online' | 'offline' | 'busy' | 'away'; }; @property({ type: Array }) public menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = []; @property({ type: Boolean, reflect: true }) public isOpen: boolean = false; @property({ type: String }) public position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right'; public static styles = [ cssManager.defaultStyles, css` :host { display: block; position: absolute; top: 100%; left: 0; right: 0; pointer-events: none; } .dropdown { position: absolute; min-width: 220px; background: ${cssManager.bdTheme('#ffffff', '#000000')}; border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; border-radius: 4px; box-shadow: ${cssManager.bdTheme( '0 4px 12px rgba(0, 0, 0, 0.15)', '0 4px 12px rgba(0, 0, 0, 0.3)' )}; z-index: 1000; opacity: 0; transform: scale(0.95) translateY(-10px); transition: opacity 0.2s, transform 0.2s; pointer-events: none; overflow: hidden; font-size: 12px; } :host([isopen]) .dropdown { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; } .backdrop { display: none; } /* Position variants */ .dropdown.top-right { top: 100%; right: 0; margin-top: 4px; } .dropdown.top-left { top: 100%; left: 0; margin-top: 8px; } .dropdown.bottom-right { bottom: 100%; right: 0; margin-bottom: 8px; } .dropdown.bottom-left { bottom: 100%; left: 0; margin-bottom: 8px; } /* User section */ .user-section { padding: 12px; border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; } .user-info { display: flex; align-items: center; gap: 10px; } .user-avatar { position: relative; width: 36px; height: 36px; border-radius: 50%; background: ${cssManager.bdTheme('#f0f0f0', '#1a1a1a')}; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; color: ${cssManager.bdTheme('#666', '#999')}; overflow: hidden; } .user-avatar img { width: 100%; height: 100%; object-fit: cover; } .user-status { position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; border-radius: 50%; border: 2px solid ${cssManager.bdTheme('#ffffff', '#000000')}; } .user-status.online { background: #4caf50; } .user-status.offline { background: #757575; } .user-status.busy { background: #f44336; } .user-status.away { background: #ff9800; } .user-details { flex: 1; min-width: 0; } .user-name { font-size: 13px; font-weight: 600; color: ${cssManager.bdTheme('#000', '#fff')}; line-height: 1.2; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .user-email { font-size: 11px; color: ${cssManager.bdTheme('#666', '#999')}; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Menu section */ .menu-section { padding: 4px 0; } .menu-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: default; transition: background 0.1s; color: ${cssManager.bdTheme('#333', '#ccc')}; font-size: 12px; line-height: 1; user-select: none; } .menu-item:hover { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; } .menu-item:active { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; } .menu-item dees-icon { font-size: 14px; opacity: 0.7; } .menu-item-text { flex: 1; } .menu-shortcut { font-size: 11px; color: ${cssManager.bdTheme('#999', '#666')}; margin-left: auto; opacity: 0.7; } .menu-divider { height: 1px; background: ${cssManager.bdTheme('#e0e0e0', '#202020')}; margin: 4px 0; } /* Backdrop for mobile */ @media (max-width: 768px) { .backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.3); z-index: 999; opacity: 0; transition: opacity 0.2s; display: none; } :host([isopen]) .backdrop { display: block; opacity: 1; pointer-events: auto; } .dropdown { position: fixed; top: 50%; left: 50%; right: auto; bottom: auto; transform: translate(-50%, -50%) scale(0.95); margin: 0; max-width: calc(100vw - 32px); max-height: calc(100vh - 32px); overflow-y: auto; } :host([isopen]) .dropdown { transform: translate(-50%, -50%) scale(1); } } `, ]; public render(): TemplateResult { return html`
this.close()}>
`; } private renderMenuItem(item: plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true }): TemplateResult { if ('divider' in item && item.divider) { return html``; } const menuItem = item as plugins.tsclass.website.IMenuItem & { iconName?: string; shortcut?: string }; return html` `; } private getInitials(name: string): string { return name .split(' ') .map(part => part[0]) .join('') .toUpperCase() .slice(0, 2); } private async handleMenuClick(item: plugins.tsclass.website.IMenuItem & { iconName?: string; shortcut?: string }) { await item.action(); this.close(); // Emit menu-select event this.dispatchEvent(new CustomEvent('menu-select', { detail: { item }, bubbles: true, composed: true })); } public open() { this.isOpen = true; } public close() { this.isOpen = false; } public toggle() { this.isOpen = !this.isOpen; } // Handle clicks outside the dropdown async connectedCallback() { await super.connectedCallback(); this.handleOutsideClick = this.handleOutsideClick.bind(this); document.addEventListener('click', this.handleOutsideClick); } async disconnectedCallback() { await super.disconnectedCallback(); document.removeEventListener('click', this.handleOutsideClick); } private handleOutsideClick(event: MouseEvent) { if (this.isOpen && !this.contains(event.target as Node)) { // Check if the click is on the parent element (which contains the profile button) const parentElement = this.parentElement; if (parentElement && parentElement.contains(event.target as Node)) { // Don't close if clicking within the parent element (e.g., on the profile button) return; } this.close(); } } }