From a8f0e5659e6a042ea02b05f10707d55cd15e1b5c Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 17 Jun 2025 09:55:28 +0000 Subject: [PATCH] feat: Add profile dropdown component and integrate with appbar for user menu --- ts_web/elements/dees-appui-appbar.ts | 65 ++- ts_web/elements/dees-appui-base.demo.ts | 15 + ts_web/elements/dees-appui-base.ts | 15 + ts_web/elements/dees-appui-profiledropdown.ts | 401 ++++++++++++++++++ ts_web/elements/dees-appui-tabs.ts | 2 +- ts_web/elements/index.ts | 1 + 6 files changed, 482 insertions(+), 17 deletions(-) create mode 100644 ts_web/elements/dees-appui-profiledropdown.ts diff --git a/ts_web/elements/dees-appui-appbar.ts b/ts_web/elements/dees-appui-appbar.ts index 3c7bedf..f9bc892 100644 --- a/ts_web/elements/dees-appui-appbar.ts +++ b/ts_web/elements/dees-appui-appbar.ts @@ -11,11 +11,13 @@ import { import * as domtools from '@design.estate/dees-domtools'; import * as interfaces from './interfaces/index.js'; +import * as plugins from './00plugins.js'; import { demoFunc } from './dees-appui-appbar.demo.js'; // Import required components import './dees-icon.js'; import './dees-windowcontrols.js'; +import './dees-appui-profiledropdown.js'; declare global { interface HTMLElementTagNameMap { @@ -44,10 +46,14 @@ export class DeesAppuiBar extends DeesElement { @property({ type: Object }) public user?: { name: string; + email?: string; avatar?: string; status?: 'online' | 'offline' | 'busy' | 'away'; }; + @property({ type: Array }) + public profileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = []; + @property({ type: Boolean }) public showSearch: boolean = false; @@ -64,6 +70,9 @@ export class DeesAppuiBar extends DeesElement { @state() private focusedDropdownItem: number = -1; + @state() + private isProfileDropdownOpen: boolean = false; + public static styles = [ cssManager.defaultStyles, css` @@ -102,7 +111,7 @@ export class DeesAppuiBar extends DeesElement { border-radius: 4px; -webkit-app-region: no-drag; transition: all 0.2s ease; - cursor: pointer; + cursor: default; outline: none; display: flex; align-items: center; @@ -162,7 +171,7 @@ export class DeesAppuiBar extends DeesElement { .dropdown-item { padding: 8px 16px; - cursor: pointer; + cursor: default; display: flex; align-items: center; gap: 8px; @@ -206,7 +215,7 @@ export class DeesAppuiBar extends DeesElement { .breadcrumb-item { color: ${cssManager.bdTheme('#00000080', '#ffffff80')}; - cursor: pointer; + cursor: default; transition: color 0.2s; } @@ -229,7 +238,7 @@ export class DeesAppuiBar extends DeesElement { } .search-icon { - cursor: pointer; + cursor: default; opacity: 0.7; transition: opacity 0.2s; } @@ -242,7 +251,7 @@ export class DeesAppuiBar extends DeesElement { display: flex; align-items: center; gap: 8px; - cursor: pointer; + cursor: default; padding: 4px 8px; border-radius: 4px; transition: background 0.2s; @@ -413,22 +422,31 @@ export class DeesAppuiBar extends DeesElement { ${this.showSearch ? html` ` : ''} ${this.user ? html` -
-
- ${this.user.avatar ? - html`${this.user.name}` : - html`${this.user.name.charAt(0).toUpperCase()}` - } - ${this.user.status ? html` -
- ` : ''} +
+ - ${this.user.name} + this.handleProfileMenuSelect(e)} + >
` : ''} `; @@ -536,12 +554,26 @@ export class DeesAppuiBar extends DeesElement { } private handleUserClick() { + this.isProfileDropdownOpen = !this.isProfileDropdownOpen; + + // Also emit the event for backward compatibility this.dispatchEvent(new CustomEvent('user-menu-open', { bubbles: true, composed: true })); } + private handleProfileMenuSelect(e: CustomEvent) { + this.isProfileDropdownOpen = false; + + // Re-emit the event + this.dispatchEvent(new CustomEvent('profile-menu-select', { + detail: e.detail, + bubbles: true, + composed: true + })); + } + // Lifecycle async connectedCallback() { await super.connectedCallback(); @@ -564,6 +596,7 @@ export class DeesAppuiBar extends DeesElement { // Close all dropdowns when clicking outside this.activeMenu = null; this.focusedDropdownItem = -1; + // Note: Profile dropdown handles its own outside clicks } private handleDropdownKeydown(e: KeyboardEvent, items: interfaces.IAppBarMenuItem[], _parentId: string) { diff --git a/ts_web/elements/dees-appui-base.demo.ts b/ts_web/elements/dees-appui-base.demo.ts index 9694615..27a2210 100644 --- a/ts_web/elements/dees-appui-base.demo.ts +++ b/ts_web/elements/dees-appui-base.demo.ts @@ -3,6 +3,7 @@ import type { DeesAppuiBase } from './dees-appui-base.js'; import type { IAppBarMenuItem } from './interfaces/appbarmenuitem.js'; import type { ITab } from './interfaces/tab.js'; import type { ISelectionOption } from './interfaces/selectionoption.js'; +import * as plugins from './00plugins.js'; import '@design.estate/dees-wcctools/demotools'; export const demoFunc = () => { @@ -86,6 +87,17 @@ export const demoFunc = () => { { key: 'Metrics', iconName: 'lineChart', action: () => console.log('Metrics tab') }, ]; + // Profile menu items + const profileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [ + { name: 'Profile Settings', iconName: 'user', action: async () => console.log('Profile settings') }, + { name: 'Account', iconName: 'settings', action: async () => console.log('Account settings') }, + { 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') } + ]; + return html`