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
This commit is contained in:
92
admin-ui/js/core/navigation.js
Normal file
92
admin-ui/js/core/navigation.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user