Spaces:
Sleeping
Sleeping
| /** | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * COMPLETE MENU SYSTEM | |
| * All Menus Implementation with Smooth Animations | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| */ | |
| class MenuSystem { | |
| constructor() { | |
| this.menus = new Map(); | |
| this.activeMenu = null; | |
| this.init(); | |
| } | |
| init() { | |
| this.setupDropdownMenus(); | |
| this.setupContextMenus(); | |
| this.setupMobileMenus(); | |
| this.setupSubmenus(); | |
| this.setupKeyboardNavigation(); | |
| } | |
| /** | |
| * Dropdown Menus | |
| */ | |
| setupDropdownMenus() { | |
| document.querySelectorAll('[data-menu-trigger]').forEach(trigger => { | |
| const menuId = trigger.dataset.menuTrigger; | |
| const menu = document.querySelector(`[data-menu="${menuId}"]`); | |
| if (!menu) return; | |
| // Show menu initially for positioning | |
| menu.style.display = 'block'; | |
| menu.style.visibility = 'hidden'; | |
| this.menus.set(menuId, { trigger, menu, type: 'dropdown' }); | |
| trigger.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| this.toggleMenu(menuId); | |
| }); | |
| // Handle menu item clicks | |
| menu.querySelectorAll('.menu-item').forEach(item => { | |
| item.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| const action = item.dataset.action; | |
| if (action) { | |
| this.handleMenuAction(action); | |
| } | |
| this.closeMenu(menu); | |
| }); | |
| }); | |
| }); | |
| // Close on outside click | |
| document.addEventListener('click', (e) => { | |
| if (!e.target.closest('[data-menu]') && !e.target.closest('[data-menu-trigger]')) { | |
| this.closeAllMenus(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Context Menus (Right-click) | |
| */ | |
| setupContextMenus() { | |
| document.querySelectorAll('[data-context-menu]').forEach(element => { | |
| const menuId = element.dataset.contextMenu; | |
| const menu = document.querySelector(`[data-context-menu-target="${menuId}"]`); | |
| if (!menu) return; | |
| element.addEventListener('contextmenu', (e) => { | |
| e.preventDefault(); | |
| this.showContextMenu(menu, e.clientX, e.clientY); | |
| }); | |
| }); | |
| // Close context menu on click | |
| document.addEventListener('click', () => { | |
| document.querySelectorAll('[data-context-menu-target]').forEach(menu => { | |
| menu.classList.remove('context-menu-open'); | |
| }); | |
| }); | |
| } | |
| /** | |
| * Mobile Menu | |
| */ | |
| setupMobileMenus() { | |
| const mobileMenuToggle = document.querySelector('[data-mobile-menu-toggle]'); | |
| const mobileMenu = document.querySelector('[data-mobile-menu]'); | |
| if (mobileMenuToggle && mobileMenu) { | |
| mobileMenuToggle.addEventListener('click', () => { | |
| mobileMenu.classList.toggle('mobile-menu-open'); | |
| mobileMenuToggle.classList.toggle('mobile-menu-active'); | |
| }); | |
| } | |
| } | |
| /** | |
| * Submenus | |
| */ | |
| setupSubmenus() { | |
| document.querySelectorAll('[data-submenu-trigger]').forEach(trigger => { | |
| const submenu = trigger.nextElementSibling; | |
| if (!submenu || !submenu.classList.contains('submenu')) return; | |
| trigger.addEventListener('mouseenter', () => { | |
| this.showSubmenu(submenu, trigger); | |
| }); | |
| trigger.addEventListener('mouseleave', () => { | |
| setTimeout(() => { | |
| if (!submenu.matches(':hover')) { | |
| this.hideSubmenu(submenu); | |
| } | |
| }, 200); | |
| }); | |
| submenu.addEventListener('mouseleave', () => { | |
| this.hideSubmenu(submenu); | |
| }); | |
| }); | |
| } | |
| /** | |
| * Keyboard Navigation | |
| */ | |
| setupKeyboardNavigation() { | |
| document.addEventListener('keydown', (e) => { | |
| // ESC to close menus | |
| if (e.key === 'Escape') { | |
| this.closeAllMenus(); | |
| } | |
| // Arrow keys for navigation | |
| if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { | |
| const activeMenu = document.querySelector('.menu-open, .context-menu-open'); | |
| if (activeMenu) { | |
| e.preventDefault(); | |
| this.navigateMenu(activeMenu, e.key === 'ArrowDown' ? 1 : -1); | |
| } | |
| } | |
| }); | |
| } | |
| toggleMenu(menuId) { | |
| const menuData = this.menus.get(menuId); | |
| if (!menuData) return; | |
| const { menu, trigger } = menuData; | |
| // Close other menus | |
| if (this.activeMenu && this.activeMenu !== menu) { | |
| this.closeMenu(this.activeMenu); | |
| } | |
| // Toggle current menu | |
| if (menu.classList.contains('menu-open')) { | |
| this.closeMenu(menu); | |
| } else { | |
| this.openMenu(menu, trigger); | |
| } | |
| } | |
| openMenu(menu, trigger) { | |
| menu.style.visibility = 'visible'; | |
| menu.classList.add('menu-open'); | |
| trigger?.classList.add('menu-trigger-active'); | |
| this.activeMenu = menu; | |
| // Animate in | |
| this.animateMenuIn(menu, trigger); | |
| } | |
| closeMenu(menu) { | |
| menu.classList.remove('menu-open'); | |
| const trigger = Array.from(this.menus.values()).find(m => m.menu === menu)?.trigger; | |
| trigger?.classList.remove('menu-trigger-active'); | |
| if (this.activeMenu === menu) { | |
| this.activeMenu = null; | |
| } | |
| // Animate out | |
| this.animateMenuOut(menu); | |
| } | |
| closeAllMenus() { | |
| document.querySelectorAll('.menu-open, .context-menu-open').forEach(menu => { | |
| this.closeMenu(menu); | |
| }); | |
| } | |
| showContextMenu(menu, x, y) { | |
| // Close other context menus | |
| document.querySelectorAll('[data-context-menu-target]').forEach(m => { | |
| m.classList.remove('context-menu-open'); | |
| }); | |
| menu.style.left = `${x}px`; | |
| menu.style.top = `${y}px`; | |
| menu.classList.add('context-menu-open'); | |
| this.activeMenu = menu; | |
| this.animateMenuIn(menu); | |
| } | |
| showSubmenu(submenu, trigger) { | |
| const triggerRect = trigger.getBoundingClientRect(); | |
| submenu.style.top = `${triggerRect.top}px`; | |
| submenu.style.left = `${triggerRect.right + 8}px`; | |
| submenu.classList.add('submenu-open'); | |
| } | |
| hideSubmenu(submenu) { | |
| submenu.classList.remove('submenu-open'); | |
| } | |
| navigateMenu(menu, direction) { | |
| const items = menu.querySelectorAll('.menu-item:not(.disabled)'); | |
| if (items.length === 0) return; | |
| let currentIndex = Array.from(items).findIndex(item => item.classList.contains('menu-item-active')); | |
| if (currentIndex === -1) { | |
| currentIndex = direction > 0 ? 0 : items.length - 1; | |
| } else { | |
| currentIndex += direction; | |
| if (currentIndex < 0) currentIndex = items.length - 1; | |
| if (currentIndex >= items.length) currentIndex = 0; | |
| } | |
| items.forEach((item, index) => { | |
| item.classList.toggle('menu-item-active', index === currentIndex); | |
| }); | |
| items[currentIndex]?.focus(); | |
| } | |
| animateMenuIn(menu, trigger) { | |
| menu.style.opacity = '0'; | |
| menu.style.transform = 'translateY(-10px) scale(0.95)'; | |
| menu.style.pointerEvents = 'none'; | |
| requestAnimationFrame(() => { | |
| menu.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)'; | |
| menu.style.opacity = '1'; | |
| menu.style.transform = 'translateY(0) scale(1)'; | |
| menu.style.pointerEvents = 'auto'; | |
| }); | |
| } | |
| animateMenuOut(menu) { | |
| menu.style.transition = 'all 0.15s cubic-bezier(0.4, 0, 0.2, 1)'; | |
| menu.style.opacity = '0'; | |
| menu.style.transform = 'translateY(-10px) scale(0.95)'; | |
| setTimeout(() => { | |
| menu.style.pointerEvents = 'none'; | |
| menu.style.visibility = 'hidden'; | |
| }, 150); | |
| } | |
| handleMenuAction(action) { | |
| switch(action) { | |
| case 'theme-light': | |
| document.body.setAttribute('data-theme', 'light'); | |
| break; | |
| case 'theme-dark': | |
| document.body.setAttribute('data-theme', 'dark'); | |
| break; | |
| case 'settings': | |
| // Navigate to settings page | |
| const settingsBtn = document.querySelector('[data-nav="page-settings"]'); | |
| if (settingsBtn) settingsBtn.click(); | |
| break; | |
| default: | |
| console.log('Menu action:', action); | |
| } | |
| } | |
| } | |
| // Initialize menu system | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.menuSystem = new MenuSystem(); | |
| }); | |
| } else { | |
| window.menuSystem = new MenuSystem(); | |
| } | |