diff --git a/frontend/src/components/SwipeTabs.tsx b/frontend/src/components/SwipeTabs.tsx index d88f377..4e9587f 100644 --- a/frontend/src/components/SwipeTabs.tsx +++ b/frontend/src/components/SwipeTabs.tsx @@ -95,8 +95,9 @@ export function SwipeTabs({ const shouldIgnoreSwipe = useCallback((target: EventTarget | null) => { const element = resolveSwipeTarget(target); if (!element) return false; + // Don't ignore role="button" - we handle tap vs swipe via delayed pointer capture if (element.closest( - '[data-swipe-ignore="true"], button, a, input, select, textarea, [role="button"], [role="link"], [role="switch"], [contenteditable="true"]' + '[data-swipe-ignore="true"], button, a, input, select, textarea, [role="link"], [role="switch"], [contenteditable="true"]' )) { return true; } @@ -287,6 +288,14 @@ export function SwipeTabs({ state.isDragging = true; isDraggingRef.current = true; setDraggingClass(true); + // Capture pointer now that drag has started + if (state.pointerId !== null && containerRef.current?.setPointerCapture) { + try { + containerRef.current.setPointerCapture(state.pointerId); + } catch { + // Ignore capture failures + } + } } if (!state.isDragging) return; @@ -371,13 +380,8 @@ export function SwipeTabs({ if (event.pointerType === 'mouse' && event.button !== 0) return; if (dragRef.current.isActive) return; startDrag(event.clientX, event.clientY, event.pointerId, null); - if (containerRef.current?.setPointerCapture) { - try { - containerRef.current.setPointerCapture(event.pointerId); - } catch { - // Ignore pointer capture failures. - } - } + // Don't capture pointer immediately - let taps work normally + // Capture will happen in updateDrag when drag actually starts }; const handlePointerMove = (event: PointerEvent) => { diff --git a/frontend/src/pages/admin/Features.tsx b/frontend/src/pages/admin/Features.tsx index 35f2c0b..e8709c1 100644 --- a/frontend/src/pages/admin/Features.tsx +++ b/frontend/src/pages/admin/Features.tsx @@ -88,6 +88,17 @@ export default function Features() { return positions; }, [moduleStates]); + const hasChangesFromInitial = useCallback((nextOrder: string[], nextPositions: Record) => { + const initialOrder = initialOrderRef.current; + const initialPositions = initialPositionsRef.current; + if (nextOrder.length !== initialOrder.length) return true; + if (nextOrder.some((id, index) => id !== initialOrder[index])) return true; + for (const id of nextOrder) { + if ((nextPositions[id] || 'top') !== (initialPositions[id] || 'top')) return true; + } + return false; + }, []); + const beginOrderEdit = useCallback(() => { if (!isUserEditing.current) { const snapshotOrder = localOrder.length ? [...localOrder] : buildFullOrder(moduleOrder); @@ -294,9 +305,15 @@ export default function Features() { localPositionsRef.current = nextPositions; setLocalPositions(nextPositions); setLocalOrder(nextOrder); - setHasOrderChanges(true); + // Only show buttons if different from initial saved state + const hasChanges = hasChangesFromInitial(nextOrder, nextPositions); + setHasOrderChanges(hasChanges); + // Reset editing state when order returns to initial + if (!hasChanges) { + isUserEditing.current = false; + } } - }, [buildFullOrder, buildPositionsFromStates, getInsertIndex, getSectionForY, moduleOrder]); + }, [buildFullOrder, buildPositionsFromStates, getInsertIndex, getSectionForY, hasChangesFromInitial, moduleOrder]); const handleOrderPointerMove = useCallback((event: React.PointerEvent) => { const dragState = dragPointerRef.current; @@ -363,7 +380,7 @@ export default function Features() { const handleOrderPointerDown = useCallback((event: React.PointerEvent, moduleId: string) => { if (event.pointerType === 'mouse' && event.button !== 0) return; beginOrderEdit(); - setDraggedItem(moduleId); + // Don't set draggedItem here - only set it when drag actually starts (in handleOrderPointerMove) dragPointerRef.current.pointerId = event.pointerId; dragPointerRef.current.touchId = null; dragPointerRef.current.activeId = moduleId; @@ -432,7 +449,7 @@ export default function Features() { const handleOrderTouchStart = useCallback((event: React.TouchEvent, moduleId: string) => { if (event.changedTouches.length === 0) return; beginOrderEdit(); - setDraggedItem(moduleId); + // Don't set draggedItem here - only set it when drag actually starts (in handleGlobalTouchMove) const touch = event.changedTouches[0]; dragPointerRef.current.pointerId = null; dragPointerRef.current.touchId = touch.identifier; @@ -565,7 +582,6 @@ export default function Features() {
{topOrderModules.map((moduleId) => { @@ -576,11 +592,6 @@ export default function Features() { key={moduleId} className={`order-card ${draggedItem === moduleId ? 'dragging' : ''}`} data-module-id={moduleId} - onPointerDown={supportsPointerEvents ? (e) => handleOrderPointerDown(e, moduleId) : undefined} - onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined} - onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined} - onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined} - onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined} >
{moduleInfo.icon} @@ -588,7 +599,15 @@ export default function Features() {
{moduleName}
-
+
handleOrderPointerDown(e, moduleId) : undefined} + onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined} + onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined} + onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined} + onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined} + > drag_indicator
@@ -606,7 +625,6 @@ export default function Features() {
{bottomOrderModules.map((moduleId) => { @@ -617,11 +635,6 @@ export default function Features() { key={moduleId} className={`order-card ${draggedItem === moduleId ? 'dragging' : ''}`} data-module-id={moduleId} - onPointerDown={supportsPointerEvents ? (e) => handleOrderPointerDown(e, moduleId) : undefined} - onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined} - onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined} - onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined} - onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined} >
{moduleInfo.icon} @@ -629,7 +642,15 @@ export default function Features() {
{moduleName}
-
+
handleOrderPointerDown(e, moduleId) : undefined} + onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined} + onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined} + onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined} + onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined} + > drag_indicator
diff --git a/frontend/src/pages/admin/ThemeSettings.tsx b/frontend/src/pages/admin/ThemeSettings.tsx index 10f694a..f29cd05 100644 --- a/frontend/src/pages/admin/ThemeSettings.tsx +++ b/frontend/src/pages/admin/ThemeSettings.tsx @@ -291,8 +291,16 @@ export default function ThemeSettings() { {colors.map((color) => (
handleAccentColorChange(color.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleAccentColorChange(color.id); + } + }} >
{accentColor === color.id && ( @@ -318,8 +326,16 @@ export default function ThemeSettings() { return (
handleColorPaletteChange(palette.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleColorPaletteChange(palette.id); + } + }} >
@@ -363,8 +379,16 @@ export default function ThemeSettings() { {radii.map((radius) => (
handleBorderRadiusChange(radius.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleBorderRadiusChange(radius.id); + } + }} >
(
handleSidebarStyleChange(style.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleSidebarStyleChange(style.id); + } + }} >
@@ -415,8 +447,16 @@ export default function ThemeSettings() { {sidebarModes.map((mode) => (
setSidebarMode(mode.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setSidebarMode(mode.id); + } + }} >
@@ -441,8 +481,16 @@ export default function ThemeSettings() { {densities.map((d) => (
handleDensityChange(d.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleDensityChange(d.id); + } + }} >
@@ -468,8 +516,16 @@ export default function ThemeSettings() { {fonts.map((f) => (
handleFontFamilyChange(f.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleFontFamilyChange(f.id); + } + }} >
diff --git a/frontend/src/styles/AdminPanel.css b/frontend/src/styles/AdminPanel.css index c365967..fc5ca07 100644 --- a/frontend/src/styles/AdminPanel.css +++ b/frontend/src/styles/AdminPanel.css @@ -2350,22 +2350,9 @@ justify-content: flex-start; } - /* Admin Tab Tooltip - Mobile Only */ + /* Admin Tab Tooltip - Disabled on mobile */ .admin-tab-tooltip { - display: block; - position: fixed; - top: calc(var(--tabs-height, 56px) + 8px); - transform: translateX(-50%); - background: var(--color-bg-elevated); - color: var(--color-text-primary); - padding: 6px 12px; - border-radius: var(--radius-sm); - font-size: 0.8rem; - white-space: nowrap; - z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - border: 1px solid var(--color-border); - pointer-events: none; + display: none; } } diff --git a/frontend/src/styles/Layout.css b/frontend/src/styles/Layout.css index e047c70..9aac148 100644 --- a/frontend/src/styles/Layout.css +++ b/frontend/src/styles/Layout.css @@ -100,6 +100,14 @@ label, -ms-overflow-style: none; } +@supports (color: color-mix(in srgb, black, transparent)) { + .page-tabs-slider, + .admin-tabs-slider { + background: color-mix(in srgb, var(--color-bg-elevated) 75%, transparent); + backdrop-filter: blur(20px) saturate(1.15); + } +} + .page-tabs-slider::-webkit-scrollbar, .admin-tabs-slider::-webkit-scrollbar { display: none; @@ -791,7 +799,8 @@ label, @supports (color: color-mix(in srgb, black, white)) { [data-theme='dark'] .page-tabs-slider, [data-theme='dark'] .admin-tabs-slider { - background: color-mix(in srgb, var(--color-bg-sidebar) 95%, transparent); + background: color-mix(in srgb, var(--color-bg-sidebar) 72%, transparent); + backdrop-filter: blur(24px) saturate(1.2); } } diff --git a/frontend/src/styles/Sidebar.css b/frontend/src/styles/Sidebar.css index 2929788..50f58b9 100644 --- a/frontend/src/styles/Sidebar.css +++ b/frontend/src/styles/Sidebar.css @@ -27,14 +27,14 @@ @supports (color: color-mix(in srgb, black, transparent)) { .sidebar { - background: color-mix(in srgb, var(--color-bg-sidebar) 88%, transparent); + background: color-mix(in srgb, var(--color-bg-sidebar) 72%, transparent); } - /* Avoid backdrop-filter on collapsed sidebar: it can break fixed-position dropdowns */ + /* Avoid backdrop-filter on collapsed sidebar: it breaks fixed-position dropdowns */ .sidebar:not(.collapsed), .sidebar.dynamic.collapsed.expanded-force, .sidebar.open { - backdrop-filter: blur(18px) saturate(1.15); + backdrop-filter: blur(24px) saturate(1.2); } } @@ -666,10 +666,6 @@ button.nav-item { border-radius: var(--radius-lg); /* Rounded on mobile */ margin: 0; - background: var(--color-bg-sidebar); - /* Use theme color */ - backdrop-filter: blur(12px); - /* Blur effect */ border: 1px solid var(--color-sidebar-border); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } @@ -1027,7 +1023,7 @@ button.nav-item { border-radius: var(--radius-md); transition: all 0.2s ease; background: transparent; - border: none; + border: 1px solid transparent; width: 100%; cursor: pointer; font-size: 0.95rem; @@ -1059,10 +1055,17 @@ button.nav-item { color: var(--color-accent); } +/* Light theme: make active menu item more visible */ +[data-theme='light'] .user-menu-item.active { + background: rgba(var(--color-accent-rgb), 0.15); + border: 1px solid rgba(var(--color-accent-rgb), 0.25); +} + /* Auto accent override for active menu item */ [data-accent='auto'] .user-menu-item.active { background: #f3f4f6; color: #111827; + border: 1px solid #e5e7eb; } [data-accent='auto'] .user-menu-item.active .material-symbols-outlined {