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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user