Packages
Advanced configurable navigation component for Phoenix LiveView with native DaisyUI integration, Tailwind CSS support, permissions filtering, dropdowns, mega menus, and modern responsive design. Optimized for Phoenix 1.8+ and LiveView 1.1+.
Current section
Files
Jump to
Current section
Files
priv/static/navbuddy-native.js
// Enhanced NavBuddy JavaScript for Native DaisyUI Integration
// This file provides additional functionality for theme switching and mobile navigation
window.navbuddy = {
// Theme management
theme: {
current: localStorage.getItem('navbuddy-theme') || 'light',
set(themeName) {
this.current = themeName;
document.documentElement.setAttribute('data-theme', themeName);
localStorage.setItem('navbuddy-theme', themeName);
this.dispatchThemeChange(themeName);
},
get() {
return this.current;
},
toggle() {
const themes = ['light', 'dark'];
const currentIndex = themes.indexOf(this.current);
const nextTheme = themes[(currentIndex + 1) % themes.length];
this.set(nextTheme);
},
dispatchThemeChange(theme) {
window.dispatchEvent(new CustomEvent('navbuddy:theme-changed', {
detail: { theme }
}));
}
},
// Mobile navigation
mobile: {
toggle(menuId) {
const menu = document.getElementById(menuId);
if (menu) {
menu.classList.toggle('open');
menu.classList.toggle('closed');
// Update ARIA attributes
const isOpen = menu.classList.contains('open');
menu.setAttribute('aria-hidden', !isOpen);
// Focus management
if (isOpen) {
const firstLink = menu.querySelector('a, button');
if (firstLink) firstLink.focus();
}
}
},
close(menuId) {
const menu = document.getElementById(menuId);
if (menu) {
menu.classList.add('closed');
menu.classList.remove('open');
menu.setAttribute('aria-hidden', true);
}
},
closeAll() {
document.querySelectorAll('.navbuddy-mobile-menu').forEach(menu => {
menu.classList.add('closed');
menu.classList.remove('open');
menu.setAttribute('aria-hidden', true);
});
}
},
// Dropdown management
dropdown: {
closeAll() {
document.querySelectorAll('.navbuddy-dropdown').forEach(dropdown => {
dropdown.classList.remove('dropdown-open');
});
},
setup() {
document.querySelectorAll('.navbuddy-dropdown').forEach(dropdown => {
const trigger = dropdown.querySelector('.navbuddy-menu-link');
const menu = dropdown.querySelector('.navbuddy-dropdown-menu');
if (trigger && menu) {
// Keyboard navigation
trigger.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
dropdown.classList.toggle('dropdown-open');
} else if (e.key === 'Escape') {
dropdown.classList.remove('dropdown-open');
trigger.focus();
}
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('dropdown-open');
}
});
}
});
}
},
// Accessibility helpers
a11y: {
setupKeyboardNavigation() {
document.addEventListener('keydown', (e) => {
// Escape key closes all menus
if (e.key === 'Escape') {
navbuddy.mobile.closeAll();
navbuddy.dropdown.closeAll();
}
// Arrow key navigation in menus
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
const activeElement = document.activeElement;
const menu = activeElement.closest('.navbuddy-menu, .navbuddy-dropdown-menu');
if (menu) {
e.preventDefault();
const links = Array.from(menu.querySelectorAll('a, button'));
const currentIndex = links.indexOf(activeElement);
let nextIndex;
if (e.key === 'ArrowDown') {
nextIndex = (currentIndex + 1) % links.length;
} else {
nextIndex = currentIndex > 0 ? currentIndex - 1 : links.length - 1;
}
links[nextIndex].focus();
}
}
});
},
setupARIA() {
// Set up ARIA attributes for mobile menus
document.querySelectorAll('.navbuddy-mobile-menu').forEach(menu => {
menu.setAttribute('aria-hidden', menu.classList.contains('closed'));
menu.setAttribute('role', 'dialog');
menu.setAttribute('aria-modal', 'true');
});
// Set up ARIA attributes for dropdowns
document.querySelectorAll('.navbuddy-dropdown').forEach(dropdown => {
const trigger = dropdown.querySelector('.navbuddy-menu-link');
const menu = dropdown.querySelector('.navbuddy-dropdown-menu');
if (trigger && menu) {
const id = `navbuddy-dropdown-${Math.random().toString(36).substr(2, 9)}`;
trigger.setAttribute('aria-haspopup', 'true');
trigger.setAttribute('aria-controls', id);
trigger.setAttribute('aria-expanded', 'false');
menu.setAttribute('id', id);
menu.setAttribute('role', 'menu');
}
});
}
},
// Initialize everything
init() {
// Set initial theme
const savedTheme = localStorage.getItem('navbuddy-theme');
if (savedTheme) {
this.theme.set(savedTheme);
} else {
// Detect system theme preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
this.theme.set(prefersDark ? 'dark' : 'light');
}
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('navbuddy-theme')) {
this.theme.set(e.matches ? 'dark' : 'light');
}
});
// Setup dropdown behavior
this.dropdown.setup();
// Setup accessibility
this.a11y.setupKeyboardNavigation();
this.a11y.setupARIA();
// Close mobile menus on window resize
window.addEventListener('resize', () => {
if (window.innerWidth >= 1024) { // lg breakpoint
this.mobile.closeAll();
}
});
console.log('NavBuddy Native DaisyUI initialized');
}
};
// Global convenience functions
window.toggleTheme = () => navbuddy.theme.toggle();
window.setTheme = (theme) => navbuddy.theme.set(theme);
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => navbuddy.init());
} else {
navbuddy.init();
}
// Phoenix LiveView integration
document.addEventListener('phx:page-loading-stop', () => {
// Re-initialize after LiveView navigation
setTimeout(() => {
navbuddy.dropdown.setup();
navbuddy.a11y.setupARIA();
}, 100);
});
// Export for ES6 modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = navbuddy;
}