2025-12-08 12:04:01 +00:00
|
|
|
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`
|
|
|
|
|
<style>
|
|
|
|
|
.demo-mainmenu-container {
|
|
|
|
|
height: 500px;
|
|
|
|
|
background: #1a1a1a;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="demo-mainmenu-container">
|
|
|
|
|
<dees-appui-mainmenu
|
|
|
|
|
.logoIcon=${'lucide:box'}
|
|
|
|
|
.logoText=${'Acme App'}
|
|
|
|
|
.menuGroups=${[
|
|
|
|
|
{
|
|
|
|
|
tabs: [
|
|
|
|
|
{ key: 'Dashboard', iconName: 'lucide:home', action: () => 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') },
|
|
|
|
|
]}
|
|
|
|
|
></dees-appui-mainmenu>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
public static styles = [
|
|
|
|
|
cssManager.defaultStyles,
|
|
|
|
|
css`
|
|
|
|
|
:host {
|
|
|
|
|
display: block;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mainContainer {
|
|
|
|
|
--menuWidth: 200px;
|
|
|
|
|
color: ${cssManager.bdTheme('#666', '#ccc')};
|
|
|
|
|
z-index: ${zIndexLayers.fixed.appBar};
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
position: relative;
|
|
|
|
|
width: var(--menuWidth);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Logo Section */
|
|
|
|
|
.logoSection {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
2025-12-08 14:50:53 +00:00
|
|
|
height: 48px;
|
|
|
|
|
padding: 0 14px;
|
2025-12-08 12:04:01 +00:00
|
|
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
|
|
|
|
flex-shrink: 0;
|
2025-12-08 14:50:53 +00:00
|
|
|
box-sizing: border-box;
|
2025-12-08 12:04:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logoSection dees-icon {
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logoSection .logoText {
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.groupTabs {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Bottom Section */
|
|
|
|
|
.bottomSection {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
public render(): TemplateResult {
|
|
|
|
|
// Get all tabs for selection (from groups or legacy tabs)
|
|
|
|
|
const allTabs = this.getAllTabs();
|
|
|
|
|
|
|
|
|
|
return html`
|
|
|
|
|
<div class="mainContainer" @contextmenu=${(eventArg: MouseEvent) => {
|
|
|
|
|
DeesContextmenu.openContextMenuWithOptions(eventArg, [{
|
|
|
|
|
name: 'app settings',
|
|
|
|
|
action: async () => {},
|
|
|
|
|
iconName: 'gear',
|
|
|
|
|
}])
|
|
|
|
|
}}>
|
|
|
|
|
${this.logoIcon || this.logoText ? html`
|
|
|
|
|
<div class="logoSection">
|
|
|
|
|
${this.logoIcon ? html`<dees-icon .icon="${this.logoIcon}"></dees-icon>` : ''}
|
|
|
|
|
${this.logoText ? html`<span class="logoText">${this.logoText}</span>` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
<div class="menuSection">
|
|
|
|
|
${this.menuGroups.length > 0 ? this.renderMenuGroups() : this.renderLegacyTabs()}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
${this.bottomTabs.length > 0 ? html`
|
|
|
|
|
<div class="bottomSection">
|
|
|
|
|
${this.bottomTabs.map((tabArg) => this.renderTab(tabArg))}
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderMenuGroups(): TemplateResult {
|
|
|
|
|
return html`
|
|
|
|
|
${this.menuGroups.map((group) => html`
|
|
|
|
|
<div class="menuGroup">
|
|
|
|
|
${group.name ? html`<div class="groupHeader">${group.name}</div>` : ''}
|
|
|
|
|
<div class="groupTabs">
|
|
|
|
|
${group.tabs.map((tabArg) => this.renderTab(tabArg))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`)}
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderLegacyTabs(): TemplateResult {
|
|
|
|
|
return html`
|
|
|
|
|
<div class="menuGroup">
|
|
|
|
|
<div class="groupTabs">
|
|
|
|
|
${this.tabs.map((tabArg) => this.renderTab(tabArg))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderTab(tabArg: interfaces.ITab): TemplateResult {
|
|
|
|
|
return html`
|
|
|
|
|
<div
|
|
|
|
|
class="tab ${tabArg === this.selectedTab ? 'selectedTab' : ''}"
|
|
|
|
|
@click="${() => {
|
|
|
|
|
this.updateTab(tabArg);
|
|
|
|
|
}}"
|
|
|
|
|
>
|
|
|
|
|
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
|
|
|
|
|
<span class="tabLabel">${tabArg.key}</span>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|