Improve Features page ordering and mobile UI consistency

- Fix module ordering with local state tracking for immediate UI updates
- Add tab centering when selected (scroll to center)
- Use finally block in handleApplyOrder to ensure state reset
- Add cancelOrder translation key
- Increase order card min-width for better readability
- Normalize mobile top bar height with min-height constraint
- Add display:flex to mobile title sections for proper layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-18 22:15:40 +01:00
parent 8c4a555b88
commit 3074f1685f
9 changed files with 273 additions and 79 deletions

View File

@@ -40,21 +40,14 @@ export default function Sidebar() {
.find((cat) => cat.id === 'main') .find((cat) => cat.id === 'main')
?.modules.filter((m) => { ?.modules.filter((m) => {
if (!m.enabled) return false; if (!m.enabled) return false;
// Dashboard is always shown
if (m.id === 'dashboard') return true;
if (shouldUseUserPermissions) { if (shouldUseUserPermissions) {
return isModuleEnabledForUser(m.id, user?.permissions, user?.is_superuser || false); return isModuleEnabledForUser(m.id, user?.permissions, user?.is_superuser || false);
} }
return isModuleEnabled(m.id); return isModuleEnabled(m.id);
}) || []); }) || []);
// Sort modules based on moduleOrder (dashboard always first, then ordered features) // Sort modules based on moduleOrder
const sortedModules = [...mainModulesFiltered].sort((a, b) => { const sortedModules = [...mainModulesFiltered].sort((a, b) => {
// Dashboard always comes first
if (a.id === 'dashboard') return -1;
if (b.id === 'dashboard') return 1;
// Sort other modules by moduleOrder
const aIndex = moduleOrder.indexOf(a.id); const aIndex = moduleOrder.indexOf(a.id);
const bIndex = moduleOrder.indexOf(b.id); const bIndex = moduleOrder.indexOf(b.id);
@@ -69,13 +62,11 @@ export default function Sidebar() {
// Split modules by position (top = main nav, bottom = above footer) // Split modules by position (top = main nav, bottom = above footer)
const topModules = sortedModules.filter(m => { const topModules = sortedModules.filter(m => {
if (m.id === 'dashboard') return true; // Dashboard always at top
const state = moduleStates[m.id as keyof typeof moduleStates]; const state = moduleStates[m.id as keyof typeof moduleStates];
return !state || state.position === 'top'; return !state || state.position === 'top';
}); });
const bottomModules = sortedModules.filter(m => { const bottomModules = sortedModules.filter(m => {
if (m.id === 'dashboard') return false; // Dashboard never at bottom
const state = moduleStates[m.id as keyof typeof moduleStates]; const state = moduleStates[m.id as keyof typeof moduleStates];
return state && state.position === 'bottom'; return state && state.position === 'bottom';
}); });

View File

@@ -6,6 +6,7 @@ import type { UserPermissions } from '../types';
// User-facing modules that can be toggled // User-facing modules that can be toggled
export const TOGGLEABLE_MODULES = [ export const TOGGLEABLE_MODULES = [
{ id: 'dashboard', icon: 'dashboard', defaultEnabled: true, defaultPosition: 'top' as const },
{ id: 'feature1', icon: 'playlist_play', defaultEnabled: true, defaultPosition: 'top' as const }, { id: 'feature1', icon: 'playlist_play', defaultEnabled: true, defaultPosition: 'top' as const },
{ id: 'feature2', icon: 'download', defaultEnabled: true, defaultPosition: 'top' as const }, { id: 'feature2', icon: 'download', defaultEnabled: true, defaultPosition: 'top' as const },
{ id: 'feature3', icon: 'cast', defaultEnabled: true, defaultPosition: 'top' as const }, { id: 'feature3', icon: 'cast', defaultEnabled: true, defaultPosition: 'top' as const },
@@ -23,7 +24,7 @@ export interface ModuleState {
} }
// Default order for modules (top modules, then bottom modules) // Default order for modules (top modules, then bottom modules)
const DEFAULT_MODULE_ORDER: string[] = ['feature1', 'feature2', 'feature3', 'search', 'notifications']; const DEFAULT_MODULE_ORDER: string[] = ['dashboard', 'feature1', 'feature2', 'feature3', 'search', 'notifications'];
interface ModulesContextType { interface ModulesContextType {
moduleStates: Record<ModuleId, ModuleState>; moduleStates: Record<ModuleId, ModuleState>;
@@ -123,12 +124,14 @@ export function ModulesProvider({ children }: { children: ReactNode }) {
} else { } else {
order = DEFAULT_MODULE_ORDER; order = DEFAULT_MODULE_ORDER;
} }
// Ensure all toggleable modules are included (for newly added modules) // Ensure all toggleable modules are included at correct positions (for newly added modules)
const allModuleIds = TOGGLEABLE_MODULES.map(m => m.id); TOGGLEABLE_MODULES.forEach((module, defaultIndex) => {
const missingModules = allModuleIds.filter(id => !order.includes(id)); if (!order.includes(module.id)) {
if (missingModules.length > 0) { // Insert at the default index position, or at end if index is beyond current length
order = [...order, ...missingModules]; const insertAt = Math.min(defaultIndex, order.length);
order.splice(insertAt, 0, module.id);
} }
});
setModuleOrderState(order); setModuleOrderState(order);
} }

View File

@@ -66,6 +66,7 @@
"orderSection": "Sidebar Order", "orderSection": "Sidebar Order",
"orderDesc": "Drag to reorder features in the sidebar", "orderDesc": "Drag to reorder features in the sidebar",
"applyOrder": "Apply", "applyOrder": "Apply",
"cancelOrder": "Cancel",
"visibility": "Visibility", "visibility": "Visibility",
"topSection": "Main Section", "topSection": "Main Section",
"bottomSection": "Bottom Section", "bottomSection": "Bottom Section",

View File

@@ -66,6 +66,7 @@
"orderSection": "Ordine nella Sidebar", "orderSection": "Ordine nella Sidebar",
"orderDesc": "Trascina per riordinare le funzioni nella barra laterale", "orderDesc": "Trascina per riordinare le funzioni nella barra laterale",
"applyOrder": "Applica", "applyOrder": "Applica",
"cancelOrder": "Annulla",
"visibility": "Visibilità", "visibility": "Visibilità",
"topSection": "Sezione Principale", "topSection": "Sezione Principale",
"bottomSection": "Sezione Inferiore", "bottomSection": "Sezione Inferiore",

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { useTranslation } from '../../contexts/LanguageContext'; import { useTranslation } from '../../contexts/LanguageContext';
import { useSidebar } from '../../contexts/SidebarContext'; import { useSidebar } from '../../contexts/SidebarContext';
@@ -7,7 +7,7 @@ import type { ModuleId } from '../../contexts/ModulesContext';
import Feature1Tab from '../../components/admin/Feature1Tab'; import Feature1Tab from '../../components/admin/Feature1Tab';
import '../../styles/AdminPanel.css'; import '../../styles/AdminPanel.css';
type TabId = 'config' | 'feature1' | 'feature2' | 'feature3' | 'search' | 'notifications'; type TabId = 'config' | 'dashboard' | 'feature1' | 'feature2' | 'feature3' | 'search' | 'notifications';
export default function Features() { export default function Features() {
const { user: currentUser } = useAuth(); const { user: currentUser } = useAuth();
@@ -19,15 +19,64 @@ export default function Features() {
const saveRef = useRef(saveModulesToBackend); const saveRef = useRef(saveModulesToBackend);
const [draggedItem, setDraggedItem] = useState<string | null>(null); const [draggedItem, setDraggedItem] = useState<string | null>(null);
const [localOrder, setLocalOrder] = useState<string[]>([]); const [localOrder, setLocalOrder] = useState<string[]>([]);
const [localPositions, setLocalPositions] = useState<Record<string, 'top' | 'bottom'>>({});
const [hasOrderChanges, setHasOrderChanges] = useState(false); const [hasOrderChanges, setHasOrderChanges] = useState(false);
const tabsContainerRef = useRef<HTMLDivElement>(null);
const isUserEditing = useRef(false); // Track if user is actively editing
// Sync local order with context order // Sync local order with context order (only when moduleOrder changes, not moduleStates)
useEffect(() => { useEffect(() => {
if (moduleOrder.length > 0) { if (moduleOrder.length > 0 && !isUserEditing.current) {
setLocalOrder(moduleOrder); // Start with current order
const fullOrder = [...moduleOrder];
// Insert missing modules at their default positions from TOGGLEABLE_MODULES
TOGGLEABLE_MODULES.forEach((module, defaultIndex) => {
if (!fullOrder.includes(module.id)) {
// Insert at the default index position, or at end if index is beyond current length
const insertAt = Math.min(defaultIndex, fullOrder.length);
fullOrder.splice(insertAt, 0, module.id);
}
});
setLocalOrder(fullOrder);
setHasOrderChanges(false); setHasOrderChanges(false);
} }
}, [moduleOrder]); }, [moduleOrder]);
// Sync positions from moduleStates only on initial load or when not editing
useEffect(() => {
if (!isUserEditing.current) {
const positions: Record<string, 'top' | 'bottom'> = {};
TOGGLEABLE_MODULES.forEach(module => {
const state = moduleStates[module.id];
positions[module.id] = state?.position || module.defaultPosition;
});
setLocalPositions(positions);
}
}, [moduleStates]);
// Scroll active tab to center of container
const scrollActiveTabIntoView = useCallback((tabId: string) => {
if (tabsContainerRef.current) {
const container = tabsContainerRef.current;
const activeButton = container.querySelector(`[data-tab-id="${tabId}"]`) as HTMLElement;
if (activeButton) {
const containerWidth = container.clientWidth;
const buttonLeft = activeButton.offsetLeft;
const buttonWidth = activeButton.offsetWidth;
// Calculate scroll position to center the button
const scrollLeft = buttonLeft - (containerWidth / 2) + (buttonWidth / 2);
container.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
}
}
}, []);
// Handle tab change with scroll
const handleTabChange = useCallback((tabId: TabId) => {
setActiveTab(tabId);
setTimeout(() => scrollActiveTabIntoView(tabId), 50);
}, [scrollActiveTabIntoView]);
// Keep saveRef updated with latest function // Keep saveRef updated with latest function
useEffect(() => { useEffect(() => {
saveRef.current = saveModulesToBackend; saveRef.current = saveModulesToBackend;
@@ -133,24 +182,32 @@ export default function Features() {
e.stopPropagation(); e.stopPropagation();
if (!draggedItem) return; if (!draggedItem) return;
const draggedPosition = moduleStates[draggedItem as ModuleId]?.position || 'top'; const draggedPosition = localPositions[draggedItem] || 'top';
// If dropping on same item, just change section if different // If dropping on same item, just change section if different
if (draggedItem === targetModuleId) { if (draggedItem === targetModuleId) {
if (draggedPosition !== targetSection) { if (draggedPosition !== targetSection) {
isUserEditing.current = true; // Mark as user editing
hasUserMadeChanges.current = true; hasUserMadeChanges.current = true;
setModulePosition(draggedItem as ModuleId, targetSection); setModulePosition(draggedItem as ModuleId, targetSection);
// Update local positions immediately for UI responsiveness
setLocalPositions(prev => ({ ...prev, [draggedItem]: targetSection }));
setHasOrderChanges(true);
} }
return; return;
} }
// Change position if moving to different section // Change position if moving to different section
if (draggedPosition !== targetSection) { if (draggedPosition !== targetSection) {
isUserEditing.current = true; // Mark as user editing
hasUserMadeChanges.current = true; hasUserMadeChanges.current = true;
setModulePosition(draggedItem as ModuleId, targetSection); setModulePosition(draggedItem as ModuleId, targetSection);
// Update local positions immediately for UI responsiveness
setLocalPositions(prev => ({ ...prev, [draggedItem]: targetSection }));
} }
// Reorder within the list // Reorder within the list
isUserEditing.current = true; // Mark as user editing
const newOrder = [...localOrder]; const newOrder = [...localOrder];
const draggedIndex = newOrder.indexOf(draggedItem); const draggedIndex = newOrder.indexOf(draggedItem);
const targetIndex = newOrder.indexOf(targetModuleId); const targetIndex = newOrder.indexOf(targetModuleId);
@@ -168,12 +225,16 @@ export default function Features() {
e.preventDefault(); e.preventDefault();
if (!draggedItem) return; if (!draggedItem) return;
const draggedPosition = moduleStates[draggedItem as ModuleId]?.position || 'top'; const draggedPosition = localPositions[draggedItem] || 'top';
// Change position if moving to different section // Change position if moving to different section
if (draggedPosition !== section) { if (draggedPosition !== section) {
isUserEditing.current = true; // Mark as user editing
hasUserMadeChanges.current = true; hasUserMadeChanges.current = true;
setModulePosition(draggedItem as ModuleId, section); setModulePosition(draggedItem as ModuleId, section);
// Update local positions immediately for UI responsiveness
setLocalPositions(prev => ({ ...prev, [draggedItem]: section }));
setHasOrderChanges(true);
} }
}; };
@@ -181,26 +242,41 @@ export default function Features() {
try { try {
setModuleOrder(localOrder); setModuleOrder(localOrder);
await saveModuleOrder(localOrder); await saveModuleOrder(localOrder);
setHasOrderChanges(false);
} catch (error) { } catch (error) {
console.error('Failed to save order:', error); console.error('Failed to save order:', error);
} finally {
isUserEditing.current = false;
setHasOrderChanges(false);
} }
}; };
const handleCancelOrder = () => {
isUserEditing.current = false; // Done editing
setLocalOrder(moduleOrder);
// Reset positions from moduleStates
const positions: Record<string, 'top' | 'bottom'> = {};
TOGGLEABLE_MODULES.forEach(module => {
const state = moduleStates[module.id];
positions[module.id] = state?.position || module.defaultPosition;
});
setLocalPositions(positions);
setHasOrderChanges(false);
};
const getModuleInfo = (moduleId: string) => { const getModuleInfo = (moduleId: string) => {
const module = TOGGLEABLE_MODULES.find(m => m.id === moduleId); const module = TOGGLEABLE_MODULES.find(m => m.id === moduleId);
return module || { id: moduleId, icon: 'extension', defaultEnabled: true }; return module || { id: moduleId, icon: 'extension', defaultEnabled: true };
}; };
// Split modules by position for the config tab // Split modules by position for the config tab (using localPositions for immediate UI updates)
const topOrderModules = localOrder.filter(id => { const topOrderModules = localOrder.filter(id => {
const state = moduleStates[id as ModuleId]; const position = localPositions[id];
return !state || state.position === 'top'; return !position || position === 'top';
}); });
const bottomOrderModules = localOrder.filter(id => { const bottomOrderModules = localOrder.filter(id => {
const state = moduleStates[id as ModuleId]; const position = localPositions[id];
return state && state.position === 'bottom'; return position === 'bottom';
}); });
const renderConfigTab = () => { const renderConfigTab = () => {
@@ -233,7 +309,6 @@ export default function Features() {
</div> </div>
<div className="order-card-info"> <div className="order-card-info">
<span className="order-card-name">{moduleName}</span> <span className="order-card-name">{moduleName}</span>
<span className="order-card-desc">{t.featuresPage?.orderDesc || 'Trascina per riordinare'}</span>
</div> </div>
<div className="order-card-handle"> <div className="order-card-handle">
<span className="material-symbols-outlined">drag_indicator</span> <span className="material-symbols-outlined">drag_indicator</span>
@@ -274,7 +349,6 @@ export default function Features() {
</div> </div>
<div className="order-card-info"> <div className="order-card-info">
<span className="order-card-name">{moduleName}</span> <span className="order-card-name">{moduleName}</span>
<span className="order-card-desc">{t.featuresPage?.orderDesc || 'Trascina per riordinare'}</span>
</div> </div>
<div className="order-card-handle"> <div className="order-card-handle">
<span className="material-symbols-outlined">drag_indicator</span> <span className="material-symbols-outlined">drag_indicator</span>
@@ -290,6 +364,10 @@ export default function Features() {
{hasOrderChanges && ( {hasOrderChanges && (
<div className="order-actions"> <div className="order-actions">
<button className="btn-secondary" onClick={handleCancelOrder}>
<span className="material-symbols-outlined">close</span>
{t.featuresPage?.cancelOrder || 'Annulla'}
</button>
<button className="btn-primary" onClick={handleApplyOrder}> <button className="btn-primary" onClick={handleApplyOrder}>
<span className="material-symbols-outlined">check</span> <span className="material-symbols-outlined">check</span>
{t.featuresPage?.applyOrder || 'Applica'} {t.featuresPage?.applyOrder || 'Applica'}
@@ -307,6 +385,19 @@ export default function Features() {
switch (activeTab) { switch (activeTab) {
case 'config': case 'config':
return renderConfigTab(); return renderConfigTab();
case 'dashboard':
return (
<>
{renderModuleToggle('dashboard')}
<div className="tab-content-placeholder">
<div className="placeholder-icon">
<span className="material-symbols-outlined">dashboard</span>
</div>
<h3>{t.sidebar.dashboard}</h3>
<p>{t.features.comingSoon}</p>
</div>
</>
);
case 'feature1': case 'feature1':
return ( return (
<> <>
@@ -374,7 +465,7 @@ export default function Features() {
return ( return (
<main className="main-content admin-panel-root"> <main className="main-content admin-panel-root">
<div className="page-tabs-container"> <div className="page-tabs-container">
<div className="page-tabs-slider"> <div className="page-tabs-slider" ref={tabsContainerRef}>
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}> <button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span> <span className="material-symbols-outlined">menu</span>
</button> </button>
@@ -384,21 +475,24 @@ export default function Features() {
</div> </div>
<div className="page-tabs-divider"></div> <div className="page-tabs-divider"></div>
<button <button
data-tab-id="config"
className={`page-tab-btn ${activeTab === 'config' ? 'active' : ''}`} className={`page-tab-btn ${activeTab === 'config' ? 'active' : ''}`}
onClick={() => setActiveTab('config')} onClick={() => handleTabChange('config')}
> >
<span className="material-symbols-outlined">tune</span> <span className="material-symbols-outlined">tune</span>
<span>{t.featuresPage?.configTab || 'Configurazione'}</span> <span>{t.featuresPage?.configTab || 'Configurazione'}</span>
</button> </button>
{TOGGLEABLE_MODULES.map((module) => { {[...topOrderModules, ...bottomOrderModules].map((moduleId) => {
const moduleName = t.sidebar[module.id as keyof typeof t.sidebar] || module.id; const moduleInfo = getModuleInfo(moduleId);
const moduleName = t.sidebar[moduleId as keyof typeof t.sidebar] || moduleId;
return ( return (
<button <button
key={module.id} key={moduleId}
className={`page-tab-btn ${activeTab === module.id ? 'active' : ''}`} data-tab-id={moduleId}
onClick={() => setActiveTab(module.id as TabId)} className={`page-tab-btn ${activeTab === moduleId ? 'active' : ''}`}
onClick={() => handleTabChange(moduleId as TabId)}
> >
<span className="material-symbols-outlined">{module.icon}</span> <span className="material-symbols-outlined">{moduleInfo.icon}</span>
<span>{moduleName}</span> <span>{moduleName}</span>
</button> </button>
); );

View File

@@ -2542,7 +2542,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.25rem; gap: 0.25rem;
min-width: 0; min-width: 280px;
} }
.order-card-name { .order-card-name {
@@ -2642,6 +2642,30 @@
font-size: 20px; font-size: 20px;
} }
.order-actions .btn-secondary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
font-size: 0.95rem;
font-weight: 500;
background: var(--color-bg-elevated);
color: var(--color-text-primary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
}
.order-actions .btn-secondary:hover {
background: var(--color-bg-card);
border-color: var(--color-text-secondary);
}
.order-actions .btn-secondary .material-symbols-outlined {
font-size: 20px;
}
/* Mobile Responsive */ /* Mobile Responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.order-cards { .order-cards {

View File

@@ -34,10 +34,16 @@
.admin-tabs-container { .admin-tabs-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 0.75rem var(--page-padding-x); padding: 0.75rem;
background: transparent; background: transparent;
} }
/* Ensure no extra margin from body */
body {
margin: 0;
padding: 0;
}
/* Page header slider - rounded pill style (like admin panel) */ /* Page header slider - rounded pill style (like admin panel) */
.page-tabs-slider, .page-tabs-slider,
.admin-tabs-slider { .admin-tabs-slider {
@@ -51,6 +57,16 @@
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
max-width: 100%; max-width: 100%;
backdrop-filter: blur(14px) saturate(1.15); backdrop-filter: blur(14px) saturate(1.15);
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
}
.page-tabs-slider::-webkit-scrollbar,
.admin-tabs-slider::-webkit-scrollbar {
display: none;
} }
@supports (color: color-mix(in srgb, black, white)) { @supports (color: color-mix(in srgb, black, white)) {
@@ -149,7 +165,6 @@
background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-hover) 100%); background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
color: white; color: white;
box-shadow: 0 2px 8px rgba(var(--color-accent-rgb), 0.3); box-shadow: 0 2px 8px rgba(var(--color-accent-rgb), 0.3);
transform: translateY(-1px);
} }
.page-subtitle { .page-subtitle {
@@ -291,7 +306,7 @@
.mobile-menu-btn { .mobile-menu-btn {
display: flex; display: flex;
position: absolute; position: absolute;
left: 4px; left: 16px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
z-index: 1; z-index: 1;
@@ -315,30 +330,39 @@
.page-tabs-container, .page-tabs-container,
.admin-tabs-container { .admin-tabs-container {
padding: var(--page-padding-y-mobile) var(--page-padding-x-mobile); padding: 0.75rem;
} }
.page-tabs-slider, .page-tabs-slider,
.admin-tabs-slider { .admin-tabs-slider {
width: 100%; width: 100%;
flex-wrap: wrap; flex-wrap: nowrap;
justify-content: flex-start; justify-content: flex-start;
gap: 8px; gap: 4px;
position: relative; position: relative;
min-height: 48px;
} }
.page-title-section, .page-title-section,
.admin-title-section { .admin-title-section {
display: flex;
flex: 1; flex: 1;
justify-content: flex-start; justify-content: flex-start;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
padding-left: 48px; padding-left: 72px;
font-size: 1rem; font-size: 1rem;
height: 100%;
align-items: center;
} }
.page-title-section .material-symbols-outlined, .page-title-section .material-symbols-outlined,
.admin-title-section .material-symbols-outlined { .admin-title-section .material-symbols-outlined {
font-size: 24px; font-size: 22px;
}
.page-title-text,
.admin-title-text {
font-size: 0.95rem;
} }
/* Hide divider on mobile */ /* Hide divider on mobile */
@@ -349,19 +373,29 @@
/* Hide title section when tabs are present on mobile */ /* Hide title section when tabs are present on mobile */
.page-tabs-slider:has(.page-tab-btn) .page-title-section, .page-tabs-slider:has(.page-tab-btn) .page-title-section,
.admin-tabs-slider:has(.admin-tab-btn) .admin-title-section { .page-tabs-slider:has(.admin-tab-btn) .page-title-section,
.admin-tabs-slider:has(.admin-tab-btn) .admin-title-section,
.admin-tabs-slider:has(.page-tab-btn) .admin-title-section {
display: none; display: none;
} }
/* Center title section absolutely when no tabs are present on mobile */ /* Add padding-left when tabs are present to avoid logo overlap */
.page-tabs-slider:not(:has(.page-tab-btn)), .page-tabs-slider:has(.page-tab-btn),
.admin-tabs-slider:not(:has(.admin-tab-btn)) { .page-tabs-slider:has(.admin-tab-btn),
justify-content: center; .admin-tabs-slider:has(.admin-tab-btn),
min-height: 48px; .admin-tabs-slider:has(.page-tab-btn) {
padding-left: 72px;
} }
.page-tabs-slider:not(:has(.page-tab-btn)) .page-title-section, /* Center title section absolutely when no tabs are present on mobile */
.admin-tabs-slider:not(:has(.admin-tab-btn)) .admin-title-section { .page-tabs-slider:not(:has(.page-tab-btn)):not(:has(.admin-tab-btn)),
.admin-tabs-slider:not(:has(.admin-tab-btn)):not(:has(.page-tab-btn)) {
justify-content: center;
}
.page-tabs-slider:not(:has(.page-tab-btn)):not(:has(.admin-tab-btn)) .page-title-section,
.admin-tabs-slider:not(:has(.admin-tab-btn)):not(:has(.page-tab-btn)) .admin-title-section {
display: flex;
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
@@ -371,19 +405,19 @@
} }
/* Lighter icon color in dark theme when only title is shown */ /* Lighter icon color in dark theme when only title is shown */
.page-tabs-slider:not(:has(.page-tab-btn)) .page-title-section .material-symbols-outlined, .page-tabs-slider:not(:has(.page-tab-btn)):not(:has(.admin-tab-btn)) .page-title-section .material-symbols-outlined,
.admin-tabs-slider:not(:has(.admin-tab-btn)) .admin-title-section .material-symbols-outlined { .admin-tabs-slider:not(:has(.admin-tab-btn)):not(:has(.page-tab-btn)) .admin-title-section .material-symbols-outlined {
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
/* Tabs on second row - full width */ /* Tabs - expand to fill, but scrollable when overflow */
.page-tab-btn, .page-tab-btn,
.admin-tab-btn { .admin-tab-btn {
flex: 1; flex: 1 0 auto;
justify-content: center; justify-content: center;
padding: 0.75rem 1rem; padding: 0.5rem 0.75rem;
font-size: 0.9rem; font-size: 0.9rem;
min-width: 0; min-width: 44px;
} }
/* Hide text on mobile, show only icons */ /* Hide text on mobile, show only icons */
@@ -492,6 +526,52 @@
font-size: 1rem; font-size: 1rem;
} }
/* ========== DARK THEME OVERRIDES ========== */
/* Top bar in dark mode: match sidebar style exactly */
[data-theme='dark'] .page-tabs-slider,
[data-theme='dark'] .admin-tabs-slider {
background: var(--color-bg-sidebar);
border: 1px solid var(--color-sidebar-border);
border-radius: var(--radius-lg);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
}
@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) 88%, transparent);
backdrop-filter: blur(18px) saturate(1.15);
}
}
/* Text colors in dark top bar */
[data-theme='dark'] .page-title-section,
[data-theme='dark'] .admin-title-section {
color: #f1f5f9;
}
[data-theme='dark'] .page-title-text,
[data-theme='dark'] .admin-title-text {
color: #f1f5f9;
}
[data-theme='dark'] .page-tab-btn,
[data-theme='dark'] .admin-tab-btn {
color: rgba(241, 245, 249, 0.7);
}
[data-theme='dark'] .page-tab-btn:hover:not(.active),
[data-theme='dark'] .admin-tab-btn:hover:not(.active) {
color: #f1f5f9;
background: rgba(255, 255, 255, 0.08);
}
[data-theme='dark'] .page-tabs-divider,
[data-theme='dark'] .admin-tabs-divider {
background: rgba(255, 255, 255, 0.12);
}
/* ========== DARK THEME + AUTO ACCENT OVERRIDES ========== */ /* ========== DARK THEME + AUTO ACCENT OVERRIDES ========== */
/* Tab buttons with auto accent in dark mode: use off-white background with dark text */ /* Tab buttons with auto accent in dark mode: use off-white background with dark text */

View File

@@ -1,17 +1,17 @@
.sidebar { .sidebar {
width: 260px; width: 260px;
height: calc(100vh - 2rem); height: calc(100vh - 1.5rem);
/* Floating height */ /* Floating height */
max-height: calc(100vh - 2rem); max-height: calc(100vh - 1.5rem);
background: var(--color-bg-sidebar); background: var(--color-bg-sidebar);
color: var(--color-text-sidebar); color: var(--color-text-sidebar);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: fixed; position: fixed;
left: 1rem; left: 0.75rem;
/* Floating position */ /* Floating position */
top: 1rem; top: 0.75rem;
bottom: 1rem; bottom: 0.75rem;
box-shadow: var(--shadow-xl); box-shadow: var(--shadow-xl);
/* Enhanced shadow for floating effect */ /* Enhanced shadow for floating effect */
z-index: 1000; z-index: 1000;
@@ -657,12 +657,12 @@ button.nav-item {
/* Ensure fully hidden */ /* Ensure fully hidden */
width: 280px; width: 280px;
z-index: 1001; z-index: 1001;
height: calc(100dvh - 2rem); height: calc(100dvh - 1.5rem);
/* Floating height on mobile too */ /* Floating height on mobile too */
max-height: calc(100dvh - 2rem); max-height: calc(100dvh - 1.5rem);
left: 1rem; left: 0.75rem;
top: 1rem; top: 0.75rem;
bottom: 1rem; bottom: 0.75rem;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
/* Rounded on mobile */ /* Rounded on mobile */
margin: 0; margin: 0;

View File

@@ -42,12 +42,12 @@
--height-header: 70px; --height-header: 70px;
/* Sidebar Dimensions */ /* Sidebar Dimensions */
/* Sidebar is positioned at left: 1rem (16px) with these widths */ /* Sidebar is positioned at left: 0.75rem (12px) with these widths */
/* margin-left = left offset (16px) + sidebar width */ /* margin-left = left offset (12px) + sidebar width */
--sidebar-width: 276px; --sidebar-width: 272px;
/* 16px offset + 260px width */ /* 12px offset + 260px width */
--sidebar-width-collapsed: 96px; --sidebar-width-collapsed: 92px;
/* 16px offset + 80px width */ /* 12px offset + 80px width */
--sidebar-mobile-width: 280px; --sidebar-mobile-width: 280px;
/* Page Layout Spacing */ /* Page Layout Spacing */