From 09eea844d7eead773b2d722062d15e3e6345a9de Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 30 Jun 2025 13:04:19 +0000 Subject: [PATCH] feat(dees-mobilenavigation): update to use zindex registry and shadcn-like design - Replace old zIndexLayers with new zIndexRegistry system - Update design to match shadcn aesthetic with clean borders and shadows - Add support for icons in menu items using Lucide icons - Improve animations with staggered item appearance - Better typography using Geist font family - Add divider support for menu item grouping - Improve hover and active states - Add custom scrollbar styling - Create comprehensive demo showcasing all features - Ensure proper cleanup in disconnectedCallback --- ts_web/elements/dees-mobilenavigation.demo.ts | 215 ++++++++++++++ ts_web/elements/dees-mobilenavigation.ts | 277 ++++++++++++++---- 2 files changed, 439 insertions(+), 53 deletions(-) create mode 100644 ts_web/elements/dees-mobilenavigation.demo.ts diff --git a/ts_web/elements/dees-mobilenavigation.demo.ts b/ts_web/elements/dees-mobilenavigation.demo.ts new file mode 100644 index 0000000..5c7a01c --- /dev/null +++ b/ts_web/elements/dees-mobilenavigation.demo.ts @@ -0,0 +1,215 @@ +import { html, css } from '@design.estate/dees-element'; +import './dees-button.js'; +import './dees-panel.js'; +import '@design.estate/dees-wcctools/demotools'; + +export const demoFunc = () => html` + + + +
+ +
+ { + const { DeesMobilenavigation } = await import('./dees-mobilenavigation.js'); + DeesMobilenavigation.createAndShow([ + { + name: 'Dashboard', + iconName: 'lucide:layout-dashboard', + action: async (nav) => { + console.log('Navigate to dashboard'); + }, + }, + { + name: 'Profile', + iconName: 'lucide:user', + action: async (nav) => { + console.log('Navigate to profile'); + }, + }, + { + name: 'Messages', + iconName: 'lucide:mail', + action: async (nav) => { + console.log('Navigate to messages'); + }, + }, + { + name: 'Settings', + iconName: 'lucide:settings', + action: async (nav) => { + console.log('Navigate to settings'); + }, + }, + { divider: true } as any, + { + name: 'Help & Support', + iconName: 'lucide:help-circle', + action: async (nav) => { + console.log('Show help'); + }, + }, + { + name: 'Sign Out', + iconName: 'lucide:log-out', + action: async (nav) => { + console.log('Sign out'); + }, + }, + ]); + }} + > + Open Navigation Menu + + + { + const { DeesMobilenavigation } = await import('./dees-mobilenavigation.js'); + const nav = await DeesMobilenavigation.createAndShow([ + { + name: 'New Document', + iconName: 'lucide:file-plus', + action: async () => console.log('New document'), + }, + { + name: 'Upload File', + iconName: 'lucide:upload', + action: async () => console.log('Upload file'), + }, + { + name: 'Download', + iconName: 'lucide:download', + action: async () => console.log('Download'), + }, + { divider: true } as any, + { + name: 'Share', + iconName: 'lucide:share-2', + action: async () => console.log('Share'), + }, + { + name: 'Export', + iconName: 'lucide:export', + action: async () => console.log('Export'), + }, + ]); + nav.heading = 'File Actions'; + }} + > + File Actions Menu + + + { + const { DeesMobilenavigation } = await import('./dees-mobilenavigation.js'); + const nav = await DeesMobilenavigation.createAndShow([ + { + name: 'Cut', + iconName: 'lucide:scissors', + action: async () => console.log('Cut'), + }, + { + name: 'Copy', + iconName: 'lucide:copy', + action: async () => console.log('Copy'), + }, + { + name: 'Paste', + iconName: 'lucide:clipboard', + action: async () => console.log('Paste'), + }, + { divider: true } as any, + { + name: 'Select All', + iconName: 'lucide:square-check', + action: async () => console.log('Select all'), + }, + { + name: 'Find', + iconName: 'lucide:search', + action: async () => console.log('Find'), + }, + { + name: 'Replace', + iconName: 'lucide:replace', + action: async () => console.log('Replace'), + }, + ]); + nav.heading = 'Edit'; + }} + > + Edit Menu + +
+
+ + +
+
    +
  • Smooth slide-in animation from the right
  • +
  • Z-index registry integration for proper stacking
  • +
  • Backdrop blur with window layer
  • +
  • Support for icons using Lucide icons
  • +
  • Menu item dividers for grouping
  • +
  • Staggered animation for menu items
  • +
  • Responsive design that adapts to mobile screens
  • +
  • Clean, modern shadcn-style aesthetics
  • +
  • Dark/light theme support
  • +
  • Singleton pattern ensures only one instance
  • +
+
+
+ + +
+
import { DeesMobilenavigation } from '@design.estate/dees-catalog';
+
+DeesMobilenavigation.createAndShow([
+  {
+    name: 'Dashboard',
+    iconName: 'lucide:layout-dashboard',
+    action: async (nav) => {
+      console.log('Navigate to dashboard');
+    },
+  },
+  {
+    name: 'Settings',
+    iconName: 'lucide:settings',
+    action: async (nav) => {
+      console.log('Navigate to settings');
+    },
+  },
+  { divider: true },
+  {
+    name: 'Sign Out',
+    iconName: 'lucide:log-out',
+    action: async (nav) => {
+      console.log('Sign out');
+    },
+  },
+]);
+
+
+
+
+`; \ No newline at end of file diff --git a/ts_web/elements/dees-mobilenavigation.ts b/ts_web/elements/dees-mobilenavigation.ts index b2804a9..7502a66 100644 --- a/ts_web/elements/dees-mobilenavigation.ts +++ b/ts_web/elements/dees-mobilenavigation.ts @@ -1,5 +1,6 @@ import * as plugins from './00plugins.js'; -import { zIndexLayers } from './00zindex.js'; +import { zIndexRegistry } from './00zindex.js'; +import { cssGeistFontFamily } from './00fonts.js'; import { cssManager, css, @@ -9,8 +10,10 @@ import { 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 { @@ -19,14 +22,48 @@ export class DeesMobilenavigation extends DeesElement { { DeesMobilenavigation.createAndShow([ { - name: 'Test', + name: 'Dashboard', + iconName: 'lucide:layout-dashboard', action: async (deesMobileNav) => { - alert('test'); + 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; @@ -44,15 +81,18 @@ export class DeesMobilenavigation extends DeesElement { // INSTANCE @property({ - type: Array, + type: String, }) - public heading: string = `MENU`; + 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() { @@ -74,25 +114,32 @@ export class DeesMobilenavigation extends DeesElement { cssManager.defaultStyles, css` :host { + font-family: ${cssGeistFontFamily}; } .main { - transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); will-change: transform; position: fixed; height: 100vh; - min-width: 280px; - transform: translateX(200px); - color: ${cssManager.bdTheme('#333', '#fff')}; - z-index: ${zIndexLayers.fixed.mobileNav}; + width: 100%; + max-width: 320px; + transform: translateX(100%); + color: ${cssManager.bdTheme('#09090b', '#fafafa')}; + z-index: var(--z-index); opacity: 0; - padding: 16px 32px; right: 0px; top: 0px; bottom: 0px; - background: ${cssManager.bdTheme('#eeeeeb', '#000')}; - border-left: 1px solid ${cssManager.bdTheme('#eeeeeb', '#222')}; + 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 { @@ -101,47 +148,151 @@ export class DeesMobilenavigation extends DeesElement { opacity: 1; } - .menuItem { - text-align: left; - padding: 8px; - margin-left: -8px; - margin-right: -8px; - border-radius: 3px; - } - .menuItem:hover { - background: ${cssManager.bdTheme('#CCC', '#333')};; + .header { + padding: 24px; + border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; } .heading { - text-align: left; - font-size: 24px; - padding: 8px 0px; - font-family: 'Geist Sans', sans-serif; - font-weight: 300; - border-bottom: 1px dashed #444; - margin-top: 16px; - margin-bottom: 16px; + 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}
- ${this.menuItems.map((menuItem) => { - return html` - - `; - })} +
+

${this.heading}

+
+
`; } @@ -154,18 +305,25 @@ export class DeesMobilenavigation extends DeesElement { 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 = new DeesWindowLayer(); - this.windowLayer.options.blur = true; + this.windowLayer = await DeesWindowLayer.createAndShow({ + blur: true, + }); this.windowLayer.addEventListener('click', () => { this.hide(); }); + } else { + document.body.append(this.windowLayer); + await this.windowLayer.show(); } - document.body.append(this.windowLayer); - await domtools.convenience.smartdelay.delayFor(0); - 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(0); + await domtools.convenience.smartdelay.delayFor(10); main.classList.add('show'); } @@ -176,10 +334,23 @@ export class DeesMobilenavigation extends DeesElement { const domtools = await this.domtoolsPromise; const main = this.shadowRoot.querySelector('.main'); main.classList.remove('show'); - this.windowLayer.hide(); + + // Unregister from z-index registry + zIndexRegistry.unregister(this); + + if (this.windowLayer) { + await this.windowLayer.destroy(); + } } async disconnectedCallback() { - document.body.removeChild(this.windowLayer); + super.disconnectedCallback(); + + // Cleanup + zIndexRegistry.unregister(this); + + if (this.windowLayer) { + await this.windowLayer.destroy(); + } } }