import * as plugins from '../../00plugins.js'; import * as interfaces from '../../interfaces/index.js'; import { zIndexLayers } from '../../00zindex.js'; import { DeesElement, type TemplateResult, property, customElement, html, css, cssManager, } from '@design.estate/dees-element'; import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js'; /** * the most left menu * usually used as organization selector */ @customElement('dees-appui-mainmenu') export class DeesAppuiMainmenu extends DeesElement { public static demo = () => html`
console.log('Dashboard') }, { key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox') }, ] }, { name: 'Workspace', tabs: [ { key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') }, { key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks') }, { key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents') }, ] }, { name: 'Analytics', tabs: [ { key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports') }, { key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights') }, ] } ]} .bottomTabs=${[ { key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') }, { key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help') }, ]} >
`; // INSTANCE // Logo properties @property({ type: String }) accessor logoIcon: string = ''; @property({ type: String }) accessor logoText: string = ''; // Menu groups (new way) @property({ type: Array }) accessor menuGroups: interfaces.IMenuGroup[] = []; // Bottom tabs (pinned to bottom) @property({ type: Array }) accessor bottomTabs: interfaces.ITab[] = []; // Legacy tabs property (for backward compatibility) @property({ type: Array }) accessor tabs: interfaces.ITab[] = []; @property() accessor selectedTab: interfaces.ITab; @property({ type: Boolean, reflect: true }) accessor collapsed: boolean = false; public static styles = [ cssManager.defaultStyles, css` :host { --menu-width-expanded: 200px; --menu-width-collapsed: 56px; --tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')}; --tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')}; display: block; height: 100%; } .mainContainer { color: ${cssManager.bdTheme('#666', '#ccc')}; z-index: ${zIndexLayers.fixed.appBar}; display: flex; flex-direction: column; position: relative; width: var(--menu-width-expanded); height: 100%; background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; user-select: none; border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')}; font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; transition: width 0.25s ease; } :host([collapsed]) .mainContainer { width: var(--menu-width-collapsed); } /* Floating collapse toggle button */ .collapse-toggle { position: absolute; right: -12px; top: 24px; transform: translateY(-50%); width: 24px; height: 24px; border-radius: 50%; background: ${cssManager.bdTheme('#ffffff', '#27272a')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')}; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); cursor: pointer; z-index: 10; display: flex; align-items: center; justify-content: center; color: ${cssManager.bdTheme('#737373', '#a1a1aa')}; opacity: 0; transition: opacity 0.2s ease, background 0.15s ease; padding: 0; } .collapse-toggle:hover { background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')}; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } :host(:hover) .collapse-toggle { opacity: 1; } .collapse-toggle dees-icon { font-size: 14px; } /* Logo Section */ .logoSection { display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 14px; border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')}; flex-shrink: 0; box-sizing: border-box; } .logoSection .logoIcon { font-size: 22px; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; flex-shrink: 0; } .logoSection .logoText { flex: 1; font-size: 15px; font-weight: 600; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity 0.2s ease, width 0.25s ease; } :host([collapsed]) .logoSection { justify-content: center; padding: 0 8px; } :host([collapsed]) .logoSection .logoText { opacity: 0; width: 0; overflow: hidden; } /* Middle Section (scrollable) */ .menuSection { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 8px 0; } .menuSection::-webkit-scrollbar { width: 6px; } .menuSection::-webkit-scrollbar-track { background: transparent; } .menuSection::-webkit-scrollbar-thumb { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')}; border-radius: 3px; } .menuSection::-webkit-scrollbar-thumb:hover { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')}; } /* Menu Group */ .menuGroup { padding: 0 8px; margin-bottom: 8px; } .menuGroup:last-child { margin-bottom: 0; } .groupHeader { padding: 8px 12px 6px; font-size: 11px; font-weight: 600; color: ${cssManager.bdTheme('#737373', '#737373')}; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; overflow: hidden; transition: opacity 0.2s ease, max-height 0.25s ease; max-height: 30px; } :host([collapsed]) .groupHeader { opacity: 0; max-height: 0; padding: 0; margin: 0; } .groupTabs { display: flex; flex-direction: column; gap: 2px; } :host([collapsed]) .menuGroup { padding: 0 4px; } /* Tab Item */ .tab { position: relative; display: flex; align-items: center; gap: 12px; padding: 10px 12px; font-size: 13px; font-weight: 500; border-radius: 6px; cursor: pointer; transition: all 0.15s ease; color: ${cssManager.bdTheme('#525252', '#a3a3a3')}; } .tab:hover { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')}; color: ${cssManager.bdTheme('#262626', '#e5e5e5')}; } .tab:active { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')}; } .tab.selectedTab { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')}; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } .tab.selectedTab::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 16px; background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; border-radius: 0 2px 2px 0; } .tab dees-icon { font-size: 18px; opacity: 0.85; flex-shrink: 0; } .tab.selectedTab dees-icon { opacity: 1; } .tab .tabLabel { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity 0.2s ease, width 0.25s ease; } /* Collapsed tab styles */ :host([collapsed]) .tab { justify-content: center; padding: 10px; gap: 0; } :host([collapsed]) .tab .tabLabel { opacity: 0; width: 0; position: absolute; } :host([collapsed]) .tab.selectedTab::before { left: -4px; } /* Tooltip for collapsed state */ .tab-tooltip { position: absolute; left: 100%; top: 50%; transform: translateY(-50%); margin-left: 12px; padding: 6px 12px; background: var(--tooltip-bg); color: var(--tooltip-fg); border-radius: 6px; font-size: 13px; font-weight: 500; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.15s ease; z-index: 1000; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .tab-tooltip::before { content: ''; position: absolute; left: -4px; top: 50%; transform: translateY(-50%); border: 4px solid transparent; border-right-color: var(--tooltip-bg); } :host([collapsed]) .tab:hover .tab-tooltip { opacity: 1; transition-delay: 1s; } /* Bottom Section */ .bottomSection { flex-shrink: 0; padding: 8px; border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')}; display: flex; flex-direction: column; gap: 2px; } :host([collapsed]) .bottomSection { padding: 8px 4px; } `, ]; public render(): TemplateResult { // Get all tabs for selection (from groups or legacy tabs) const allTabs = this.getAllTabs(); return html`
{ DeesContextmenu.openContextMenuWithOptions(eventArg, [{ name: 'app settings', action: async () => {}, iconName: 'gear', }]) }}> ${this.logoIcon || this.logoText ? html`
${this.logoIcon ? html`` : ''} ${this.logoText ? html`${this.logoText}` : ''}
` : ''} ${this.bottomTabs.length > 0 ? html`
${this.bottomTabs.map((tabArg) => this.renderTab(tabArg))}
` : ''}
`; } private renderMenuGroups(): TemplateResult { return html` ${this.menuGroups.map((group) => html` `)} `; } private renderLegacyTabs(): TemplateResult { return html` `; } private renderTab(tabArg: interfaces.ITab): TemplateResult { return html`
${tabArg.key} ${tabArg.key}
`; } private getAllTabs(): interfaces.ITab[] { if (this.menuGroups.length > 0) { const groupTabs = this.menuGroups.flatMap(group => group.tabs); return [...groupTabs, ...this.bottomTabs]; } return [...this.tabs, ...this.bottomTabs]; } updateTab(tabArg: interfaces.ITab) { this.selectedTab = tabArg; this.selectedTab.action(); // Emit tab-select event this.dispatchEvent(new CustomEvent('tab-select', { detail: { tab: tabArg }, bubbles: true, composed: true })); } firstUpdated() { const allTabs = this.getAllTabs(); if (allTabs.length > 0) { this.updateTab(allTabs[0]); } } public toggleCollapse(): void { this.collapsed = !this.collapsed; this.dispatchEvent(new CustomEvent('collapse-change', { detail: { collapsed: this.collapsed }, bubbles: true, composed: true })); } }