Files
dss/admin-ui/js/core/navigation.js
Digital Production Factory 276ed71f31 Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
2025-12-09 18:45:48 -03:00

93 lines
2.4 KiB
JavaScript

/**
* admin-ui/js/core/navigation.js
* Manages active state and keyboard navigation for flat navigation structure.
*/
class NavigationManager {
constructor(navElement) {
if (!navElement) return;
this.nav = navElement;
this.items = Array.from(this.nav.querySelectorAll('.nav-item'));
this.init();
}
init() {
this.bindEvents();
this.updateActiveState();
}
bindEvents() {
// Listen for navigation changes
window.addEventListener('hashchange', this.updateActiveState.bind(this));
window.addEventListener('app-navigate', this.updateActiveState.bind(this));
// Handle keyboard navigation
this.nav.addEventListener('keydown', this.onKeyDown.bind(this));
// Handle manual nav item clicks
this.items.forEach(item => {
item.addEventListener('click', this.onItemClick.bind(this));
});
}
onItemClick(event) {
const item = event.currentTarget;
const page = item.dataset.page;
if (page) {
window.location.hash = `#${page}`;
this.updateActiveState();
}
}
updateActiveState() {
const currentPage = window.location.hash.substring(1) || 'dashboard';
this.items.forEach(item => {
const itemPage = item.dataset.page;
const isActive = itemPage === currentPage;
item.classList.toggle('active', isActive);
// Update aria-current for accessibility
if (isActive) {
item.setAttribute('aria-current', 'page');
} else {
item.removeAttribute('aria-current');
}
});
}
onKeyDown(event) {
const activeElement = document.activeElement;
if (!this.nav.contains(activeElement)) return;
const visibleItems = this.items.filter(el => el.offsetParent !== null);
const currentIndex = visibleItems.indexOf(activeElement);
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
if (currentIndex < visibleItems.length - 1) {
visibleItems[currentIndex + 1].focus();
}
break;
case 'ArrowUp':
event.preventDefault();
if (currentIndex > 0) {
visibleItems[currentIndex - 1].focus();
}
break;
case 'Enter':
case ' ':
event.preventDefault();
activeElement.click();
break;
case 'Tab':
// Allow default Tab behavior (move to next focusable element)
break;
}
}
}
export default NavigationManager;