feat(dashboard): implement responsive header with tab scrolling and overflow menus

Add comprehensive responsive header system with Google-quality UX:

Tab Navigation:
- Add TabScrollManager for horizontal scrolling tabs
- Left/Right navigation arrows appear when scrollable
- Edge fade indicators show more content exists
- Smooth scroll behavior with momentum
- Progressive sizing: full → icon+name → icon-only
- Automatic scroll position tracking

Button Overflow System:
- Add HeaderOverflowManager with ResizeObserver
- Three responsive modes based on container width:
  * Full mode (>900px): all buttons visible
  * Overflow mode (500-900px): priority + "More" (⋮) menu
  * Compact mode (<500px): priority + hamburger (☰) menu
- Priority buttons (Lock + Edit) always visible
- Smooth transitions between modes

Dropdown Menu:
- Professional slide-down animation
- Full keyboard navigation (arrows, Home, End, Escape)
- Click-outside-to-close behavior
- ARIA attributes for accessibility
- Focus management and trap
- Auto-refresh on edit mode changes
- High z-index (10003) ensures visibility above all UI

Cross-Tab Widget Dragging:
- Add collision detection for widget placement
- Implement moveWidgetToTab() with collision avoidance
- Find available positions in target tab automatically
- Update dragDrop.js to detect tab hover
- Visual feedback with tab highlight on hover
- Proper widget positioning after cross-tab move

Additional Features:
- Sort Tab button for current-tab-only auto-layout
- Mobile optimizations with compact buttons
- Responsive breakpoints at 768px and 480px
- Hardware-accelerated animations
- Touch-friendly 44px minimum targets

Files changed:
- New: tabScrollManager.js, headerOverflowManager.js
- Modified: dashboardTemplate.html, dashboardIntegration.js
- Modified: dashboardManager.js, dragDrop.js, style.css
- Modified: editModeManager.js (lock state default)
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-27 14:48:38 +11:00
parent 45c5853dcb
commit f566ad1d93
8 changed files with 1474 additions and 41 deletions
+62 -1
View File
@@ -25,6 +25,7 @@ export class DragDropHandler {
constructor(gridEngine, options = {}) {
this.gridEngine = gridEngine;
this.editManager = options.editManager || null; // Reference to EditModeManager for lock state
this.dashboardManager = options.dashboardManager || null; // Reference to DashboardManager for cross-tab moves
this.options = {
showGrid: true,
showCollisions: true,
@@ -40,6 +41,7 @@ export class DragDropHandler {
this.gridOverlay = null;
this.touchTimer = null;
this.mouseDragPending = null; // Tracks potential mouse drag before threshold
this.hoveredTab = null; // Currently hovered tab during drag
// Bound event handlers for cleanup
this.boundMouseMove = this.onMouseMove.bind(this);
@@ -305,6 +307,9 @@ export class DragDropHandler {
if (this.gridOverlay) {
this.highlightGridCells(snapped.x, snapped.y, widget.w, widget.h);
}
// Check for tab hover (for cross-tab dragging)
this.updateTabHover(clientX, clientY);
}
/**
@@ -355,7 +360,20 @@ export class DragDropHandler {
const dragHandle = element.querySelector('.drag-handle') || element;
dragHandle.style.cursor = 'grab';
// Check for collision before committing
// Check if dropped on a tab (cross-tab move)
if (this.hoveredTab && this.dashboardManager) {
const targetTabId = this.hoveredTab.dataset.tabId;
console.log('[DragDrop] Dropped on tab:', targetTabId);
// Move widget to target tab
this.dashboardManager.moveWidgetToTab(widget.id, targetTabId);
this.cleanup();
console.log('[DragDrop] Widget moved to tab:', widget.id, '->', targetTabId);
return;
}
// Normal grid drop - check for collision before committing
const otherWidgets = widgets.filter(w => w.id !== widget.id);
const collision = this.gridEngine.detectCollision(widget, otherWidgets);
@@ -410,6 +428,9 @@ export class DragDropHandler {
// Remove grid overlay
this.hideGridOverlay();
// Clear tab hover highlight
this.clearTabHover();
// Remove event listeners
document.removeEventListener('mousemove', this.boundMouseMove);
document.removeEventListener('mouseup', this.boundMouseUp);
@@ -524,6 +545,46 @@ export class DragDropHandler {
}
}
/**
* Update tab hover state during drag
* @param {number} clientX - Pointer X coordinate
* @param {number} clientY - Pointer Y coordinate
*/
updateTabHover(clientX, clientY) {
if (!this.dragState) return;
// Find tab element at pointer position
const elementAtPoint = document.elementFromPoint(clientX, clientY);
const tabElement = elementAtPoint?.closest('.rpg-dashboard-tab');
// Check if hover state changed
if (tabElement !== this.hoveredTab) {
// Clear previous highlight
if (this.hoveredTab) {
this.hoveredTab.classList.remove('drop-target');
}
// Set new hover state
this.hoveredTab = tabElement;
// Add highlight to new tab
if (this.hoveredTab) {
this.hoveredTab.classList.add('drop-target');
console.log('[DragDrop] Hovering over tab:', this.hoveredTab.dataset.tabId);
}
}
}
/**
* Clear tab hover highlight
*/
clearTabHover() {
if (this.hoveredTab) {
this.hoveredTab.classList.remove('drop-target');
this.hoveredTab = null;
}
}
/**
* Check if current drag position has collisions
* @param {Array<Object>} widgets - Array of other widgets