import * as plugins from './00plugins.js'; import { zIndexRegistry } from './00zindex.js'; import { cssGeistFontFamily } from './00fonts.js'; import { cssManager, css, type CSSResult, customElement, DeesElement, domtools, html, property, state, } from '@design.estate/dees-element'; import { DeesWindowLayer } from './dees-windowlayer.js'; import './dees-icon.js'; @customElement('dees-mobilenavigation') export class DeesMobilenavigation extends DeesElement { // STATIC public static demo = () => html` { DeesMobilenavigation.createAndShow([ { name: 'Dashboard', iconName: 'lucide:layout-dashboard', action: async (deesMobileNav) => { console.log('Navigate to dashboard'); return null; }, }, { name: 'Profile', iconName: 'lucide:user', action: async (deesMobileNav) => { console.log('Navigate to profile'); return null; }, }, { name: 'Settings', iconName: 'lucide:settings', action: async (deesMobileNav) => { console.log('Navigate to settings'); return null; }, }, { divider: true } as any, { name: 'Help', iconName: 'lucide:help-circle', action: async (deesMobileNav) => { console.log('Show help'); return null; }, }, { name: 'Sign Out', iconName: 'lucide:log-out', action: async (deesMobileNav) => { console.log('Sign out'); return null; }, }, ]); }}>Open Mobile Navigation `; private static singletonRef: DeesMobilenavigation; public static async createAndShow(menuItemsArg: plugins.tsclass.website.IMenuItem[]) { if (!this.singletonRef) { this.singletonRef = new DeesMobilenavigation(); document.body.append(this.singletonRef); await this.singletonRef.init(); } this.singletonRef.menuItems = menuItemsArg; await this.singletonRef.readyDeferred.promise; this.singletonRef.show(); return this.singletonRef; } // INSTANCE @property({ type: String, }) public heading: string = `Menu`; @property({ type: Array, }) public menuItems: plugins.tsclass.website.IMenuItem[] = []; @state() private mobileNavZIndex: number = 1000; readyDeferred: plugins.smartpromise.Deferred = domtools.plugins.smartpromise.defer(); constructor() { super(); /* this.init().then(() => { this.show(); }); */ } /** * inits the mobile navigation */ public async init() { await this.updateComplete; this.readyDeferred.resolve(); } public static styles = [ cssManager.defaultStyles, css` :host { font-family: ${cssGeistFontFamily}; } .main { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); will-change: transform; position: fixed; height: 100vh; width: 100%; max-width: 320px; transform: translateX(100%); color: ${cssManager.bdTheme('#09090b', '#fafafa')}; z-index: var(--z-index); opacity: 0; right: 0px; top: 0px; bottom: 0px; background: ${cssManager.bdTheme('#ffffff', '#09090b')}; border-left: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; pointer-events: none; box-shadow: ${cssManager.bdTheme( '-20px 0 25px -5px rgba(0, 0, 0, 0.1), -10px 0 10px -5px rgba(0, 0, 0, 0.04)', '-20px 0 25px -5px rgba(0, 0, 0, 0.3), -10px 0 10px -5px rgba(0, 0, 0, 0.2)' )}; display: flex; flex-direction: column; } .main.show { pointer-events: all; transform: translateX(0px); opacity: 1; } .header { padding: 24px; border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; } .heading { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; color: ${cssManager.bdTheme('#09090b', '#fafafa')}; margin: 0; } .menu-container { flex: 1; overflow-y: auto; padding: 8px; } .menuItem { display: flex; align-items: center; gap: 12px; padding: 12px 16px; margin-bottom: 2px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; position: relative; user-select: none; } .menuItem:hover { background: ${cssManager.bdTheme('#f4f4f5', '#27272a')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')}; } .menuItem:active { background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')}; transform: scale(0.98); } .menuItem dees-icon { flex-shrink: 0; color: ${cssManager.bdTheme('#71717a', '#71717a')}; transition: color 0.15s ease; } .menuItem:hover dees-icon { color: ${cssManager.bdTheme('#09090b', '#fafafa')}; } .menuItem-text { flex: 1; letter-spacing: -0.01em; } .menuItem-divider { height: 1px; background: ${cssManager.bdTheme('#e5e7eb', '#27272a')}; margin: 8px 16px; } /* Mobile responsiveness */ @media (max-width: 400px) { .main { max-width: 100vw; width: 85vw; } } /* Animation for menu items */ @keyframes slideInRight { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } .main.show .menuItem { animation: slideInRight 0.3s ease-out forwards; animation-delay: calc(var(--item-index, 0) * 0.05s); opacity: 0; } /* Scrollbar styling */ .menu-container::-webkit-scrollbar { width: 6px; } .menu-container::-webkit-scrollbar-track { background: transparent; } .menu-container::-webkit-scrollbar-thumb { background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')}; border-radius: 3px; } .menu-container::-webkit-scrollbar-thumb:hover { background: ${cssManager.bdTheme('#d1d5db', '#52525b')}; } `, ]; public render() { return html`

${this.heading}

`; } private windowLayer: DeesWindowLayer; /** * inits the show */ public async show() { const domtools = await this.domtoolsPromise; const main = this.shadowRoot.querySelector('.main'); // Create window layer first (it will get its own z-index) if (!this.windowLayer) { this.windowLayer = await DeesWindowLayer.createAndShow({ blur: true, }); this.windowLayer.addEventListener('click', () => { this.hide(); }); } else { document.body.append(this.windowLayer); await this.windowLayer.show(); } // Get z-index for mobile nav (will be above window layer) this.mobileNavZIndex = zIndexRegistry.getNextZIndex(); zIndexRegistry.register(this, this.mobileNavZIndex); await domtools.convenience.smartdelay.delayFor(10); main.classList.add('show'); } /** * inits the hide function */ public async hide() { const domtools = await this.domtoolsPromise; const main = this.shadowRoot.querySelector('.main'); main.classList.remove('show'); // Unregister from z-index registry zIndexRegistry.unregister(this); if (this.windowLayer) { await this.windowLayer.destroy(); } } async disconnectedCallback() { super.disconnectedCallback(); // Cleanup zIndexRegistry.unregister(this); if (this.windowLayer) { await this.windowLayer.destroy(); } } }