feat: Add profile dropdown component and integrate with appbar for user menu
This commit is contained in:
@ -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`
|
||||
<dees-icon
|
||||
class="search-icon"
|
||||
.iconName=${'search'}
|
||||
.icon=${'lucide:search'}
|
||||
@click=${this.handleSearchClick}
|
||||
></dees-icon>
|
||||
` : ''}
|
||||
${this.user ? html`
|
||||
<div class="user-info" @click=${this.handleUserClick}>
|
||||
<div class="user-avatar">
|
||||
${this.user.avatar ?
|
||||
html`<img src="${this.user.avatar}" alt="${this.user.name}">` :
|
||||
html`${this.user.name.charAt(0).toUpperCase()}`
|
||||
}
|
||||
${this.user.status ? html`
|
||||
<div class="user-status ${this.user.status}"></div>
|
||||
` : ''}
|
||||
<div style="position: relative;">
|
||||
<div class="user-info" @click=${this.handleUserClick}>
|
||||
<div class="user-avatar">
|
||||
${this.user.avatar ?
|
||||
html`<img src="${this.user.avatar}" alt="${this.user.name}">` :
|
||||
html`${this.user.name.charAt(0).toUpperCase()}`
|
||||
}
|
||||
${this.user.status ? html`
|
||||
<div class="user-status ${this.user.status}"></div>
|
||||
` : ''}
|
||||
</div>
|
||||
<span>${this.user.name}</span>
|
||||
</div>
|
||||
<span>${this.user.name}</span>
|
||||
<dees-appui-profiledropdown
|
||||
.user=${this.user}
|
||||
.menuItems=${this.profileMenuItems}
|
||||
.isOpen=${this.isProfileDropdownOpen}
|
||||
.position=${'top-right'}
|
||||
@menu-select=${(e: CustomEvent) => this.handleProfileMenuSelect(e)}
|
||||
></dees-appui-profiledropdown>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
@ -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) {
|
||||
|
@ -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`
|
||||
<dees-demowrapper>
|
||||
<style>
|
||||
@ -106,8 +118,10 @@ export const demoFunc = () => {
|
||||
.appbarBreadcrumbs=${'Dashboard'}
|
||||
.appbarUser=${{
|
||||
name: 'Jane Smith',
|
||||
email: 'jane.smith@example.com',
|
||||
status: 'online' as 'online' | 'offline' | 'busy' | 'away'
|
||||
}}
|
||||
.appbarProfileMenuItems=${profileMenuItems}
|
||||
.appbarShowWindowControls=${true}
|
||||
.appbarShowSearch=${true}
|
||||
.mainmenuTabs=${mainMenuTabs}
|
||||
@ -117,6 +131,7 @@ export const demoFunc = () => {
|
||||
@appbar-breadcrumb-navigate=${(e: CustomEvent) => console.log('Breadcrumb:', e.detail)}
|
||||
@appbar-search-click=${() => console.log('Search clicked')}
|
||||
@appbar-user-menu-open=${() => console.log('User menu opened')}
|
||||
@appbar-profile-menu-select=${(e: CustomEvent) => console.log('Profile menu selected:', e.detail)}
|
||||
@mainmenu-tab-select=${(e: CustomEvent) => console.log('Tab selected:', e.detail)}
|
||||
@mainselector-option-select=${(e: CustomEvent) => console.log('Option selected:', e.detail)}
|
||||
>
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
import * as plugins from './00plugins.js';
|
||||
import type { DeesAppuiBar } from './dees-appui-appbar.js';
|
||||
import type { DeesAppuiMainmenu } from './dees-appui-mainmenu.js';
|
||||
import type { DeesAppuiMainselector } from './dees-appui-mainselector.js';
|
||||
@ -44,10 +45,14 @@ export class DeesAppuiBase extends DeesElement {
|
||||
@property({ type: Object })
|
||||
public appbarUser?: {
|
||||
name: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
status?: 'online' | 'offline' | 'busy' | 'away';
|
||||
};
|
||||
|
||||
@property({ type: Array })
|
||||
public appbarProfileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
public appbarShowSearch: boolean = false;
|
||||
|
||||
@ -115,11 +120,13 @@ export class DeesAppuiBase extends DeesElement {
|
||||
.breadcrumbSeparator=${this.appbarBreadcrumbSeparator}
|
||||
.showWindowControls=${this.appbarShowWindowControls}
|
||||
.user=${this.appbarUser}
|
||||
.profileMenuItems=${this.appbarProfileMenuItems}
|
||||
.showSearch=${this.appbarShowSearch}
|
||||
@menu-select=${(e: CustomEvent) => this.handleAppbarMenuSelect(e)}
|
||||
@breadcrumb-navigate=${(e: CustomEvent) => this.handleAppbarBreadcrumbNavigate(e)}
|
||||
@search-click=${() => this.handleAppbarSearchClick()}
|
||||
@user-menu-open=${() => this.handleAppbarUserMenuOpen()}
|
||||
@profile-menu-select=${(e: CustomEvent) => this.handleAppbarProfileMenuSelect(e)}
|
||||
></dees-appui-appbar>
|
||||
<div class="maingrid">
|
||||
<dees-appui-mainmenu
|
||||
@ -182,6 +189,14 @@ export class DeesAppuiBase extends DeesElement {
|
||||
}));
|
||||
}
|
||||
|
||||
private handleAppbarProfileMenuSelect(e: CustomEvent) {
|
||||
this.dispatchEvent(new CustomEvent('appbar-profile-menu-select', {
|
||||
detail: e.detail,
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
|
||||
// Event handlers for mainmenu
|
||||
private handleMainmenuTabSelect(e: CustomEvent) {
|
||||
this.mainmenuSelectedTab = e.detail.tab;
|
||||
|
401
ts_web/elements/dees-appui-profiledropdown.ts
Normal file
401
ts_web/elements/dees-appui-profiledropdown.ts
Normal file
@ -0,0 +1,401 @@
|
||||
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`
|
||||
<dees-appui-profiledropdown
|
||||
.user=${{
|
||||
name: 'John Doe',
|
||||
email: 'john.doe@example.com',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/1.jpg',
|
||||
status: 'online' as 'online'
|
||||
}}
|
||||
.menuItems=${[
|
||||
{ name: 'Profile Settings', iconName: 'user', action: async () => 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}
|
||||
></dees-appui-profiledropdown>
|
||||
`;
|
||||
|
||||
@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`
|
||||
<div class="backdrop" @click=${() => this.close()}></div>
|
||||
<div class="dropdown ${this.position}">
|
||||
${this.user ? html`
|
||||
<div class="user-section">
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">
|
||||
${this.user.avatar
|
||||
? html`<img src="${this.user.avatar}" alt="${this.user.name}">`
|
||||
: this.getInitials(this.user.name)
|
||||
}
|
||||
${this.user.status ? html`
|
||||
<div class="user-status ${this.user.status}"></div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<div class="user-name">${this.user.name}</div>
|
||||
${this.user.email ? html`
|
||||
<div class="user-email">${this.user.email}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="menu-section">
|
||||
${this.menuItems.map(item => this.renderMenuItem(item))}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderMenuItem(item: plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true }): TemplateResult {
|
||||
if ('divider' in item && item.divider) {
|
||||
return html`<div class="menu-divider"></div>`;
|
||||
}
|
||||
|
||||
const menuItem = item as plugins.tsclass.website.IMenuItem & { iconName?: string; shortcut?: string };
|
||||
return html`
|
||||
<div class="menu-item" @click=${() => this.handleMenuClick(menuItem)}>
|
||||
${menuItem.iconName ? html`
|
||||
<dees-icon .icon="${`lucide:${menuItem.iconName}`}"></dees-icon>
|
||||
` : ''}
|
||||
<span class="menu-item-text">${menuItem.name}</span>
|
||||
${menuItem.shortcut ? html`
|
||||
<span class="menu-shortcut">${menuItem.shortcut}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
.tab {
|
||||
color: ${cssManager.bdTheme('#666', '#a0a0a0')};
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
cursor: default;
|
||||
transition: color 0.1s;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ export * from './dees-appui-base.js';
|
||||
export * from './dees-appui-maincontent.js';
|
||||
export * from './dees-appui-mainmenu.js';
|
||||
export * from './dees-appui-mainselector.js';
|
||||
export * from './dees-appui-profiledropdown.js';
|
||||
export * from './dees-appui-tabs.js';
|
||||
export * from './dees-appui-view.js';
|
||||
export * from './dees-badge.js';
|
||||
|
Reference in New Issue
Block a user