fix: responsive layout - sidebar collapse, mobile adaptation, prevent text jumping

- Fix text jumping on right side when screen narrows (min-width:0 on flex item)
- Fix sidebar toggle button not working (retry icon init, proper state sync)
- Mobile adaptation: sidebar as overlay with slide animation
- Auto-collapse sidebar on mobile viewport (<=768px)
- Click outside sidebar to close on mobile
- Responsive padding for stat cards and main content
- Hide brand title on very small screens (<=480px)
- Smooth transitions for sidebar collapse/expand
This commit is contained in:
yumoqing 2026-05-28 16:58:29 +08:00
parent 79a04be92b
commit 3659533102
2 changed files with 141 additions and 22 deletions

View File

@ -128,7 +128,9 @@ body {
overflow-y: auto;
overflow-x: hidden;
flex-shrink: 0;
transition: width 0.25s ease;
transition: width 0.3s ease, transform 0.3s ease;
position: relative;
z-index: 50;
}
.sage-sidebar.collapsed {
@ -141,12 +143,48 @@ body {
overflow: hidden;
}
/* Mobile: sidebar as overlay */
@media (max-width: 768px) {
.sage-sidebar {
position: fixed;
left: 0;
top: var(--sage-topbar-height);
height: calc(100vh - var(--sage-topbar-height));
transform: translateX(0);
box-shadow: var(--sage-shadow-lg);
}
.sage-sidebar.collapsed {
transform: translateX(-100%);
width: var(--sage-sidebar-width); /* Keep full width when hidden on mobile */
}
.sage-main {
width: 100%;
}
}
.sage-main {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 24px;
background-color: var(--sage-bg-primary);
min-width: 0; /* Prevent flex item from overflowing */
transition: margin-left 0.3s ease;
}
/* Responsive padding for mobile */
@media (max-width: 768px) {
.sage-main {
padding: 16px;
}
}
@media (max-width: 480px) {
.sage-main {
padding: 12px;
}
}
/* ===== Stat Cards ===== */
@ -719,18 +757,43 @@ body {
color: var(--sage-text-secondary);
}
/* ===== Responsive ===== */
/* ===== Responsive - Topbar ===== */
@media (max-width: 768px) {
.sage-sidebar {
position: fixed;
left: 0;
top: var(--sage-topbar-height);
bottom: 0;
z-index: 200;
transform: translateX(-100%);
transition: transform 0.25s ease;
.sage-topbar {
padding: 0 8px;
gap: 6px;
}
.sage-sidebar.mobile-open {
transform: translateX(0);
.sage-brand-title {
font-size: 16px;
}
}
@media (max-width: 480px) {
.sage-brand-title {
display: none;
}
}
/* ===== Responsive - Stat Cards ===== */
@media (max-width: 768px) {
.stat-card {
padding: 14px;
}
.stat-card .stat-value {
font-size: 22px;
}
.stat-card .stat-label {
font-size: 12px;
}
.stat-card .stat-icon {
width: 32px;
height: 32px;
margin-bottom: 8px;
}
.quick-link {
padding: 14px;
}
.section-title {
font-size: 16px;
}
}

View File

@ -42,12 +42,61 @@
function initSidebar() {
var collapsed = false;
try { collapsed = localStorage.getItem(SIDEBAR_KEY) === 'true'; } catch(e) {}
// Auto-collapse on mobile
if (isMobile()) {
collapsed = true;
}
var sidebar = document.getElementById('sage_sidebar');
if (sidebar && collapsed) {
sidebar.classList.add('collapsed');
}
}
// Handle window resize - auto-collapse on mobile
function handleResize() {
var sidebar = document.getElementById('sage_sidebar');
if (!sidebar) return;
if (isMobile() && !sidebar.classList.contains('collapsed')) {
sidebar.classList.add('collapsed');
try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(e) {}
updateSidebarIcon(true);
}
}
// Check if we're on mobile viewport
function isMobile() {
return window.innerWidth <= 768;
}
// Close sidebar when clicking outside on mobile
function setupMobileOverlay() {
document.addEventListener('click', function(e) {
if (!isMobile()) return;
var sidebar = document.getElementById('sage_sidebar');
var toggleBtn = document.getElementById('sidebar_toggle_btn');
if (!sidebar || sidebar.classList.contains('collapsed')) return;
// If click is outside sidebar and toggle button, close sidebar
if (!sidebar.contains(e.target) && (!toggleBtn || !toggleBtn.contains(e.target))) {
sidebar.classList.add('collapsed');
try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(ex) {}
updateSidebarIcon(true);
}
});
}
function updateSidebarIcon(isCollapsed) {
var btn = document.getElementById('sidebar_toggle_btn');
if (!btn) return;
if (isCollapsed) {
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>';
} else {
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
}
}
// Toggle sidebar collapse
function toggleSidebar() {
var sidebar = document.getElementById('sage_sidebar');
@ -55,16 +104,7 @@
sidebar.classList.toggle('collapsed');
var isCollapsed = sidebar.classList.contains('collapsed');
try { localStorage.setItem(SIDEBAR_KEY, isCollapsed); } catch(e) {}
// Update toggle icon
var btn = document.getElementById('sidebar_toggle_btn');
if (btn) {
if (isCollapsed) {
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>';
} else {
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
}
}
updateSidebarIcon(isCollapsed);
}
// Initialize SPA Router
@ -115,11 +155,15 @@
initTheme();
initSidebar();
initRouter();
setupMobileOverlay();
window.addEventListener('resize', handleResize);
});
} else {
initTheme();
initSidebar();
initRouter();
setupMobileOverlay();
window.addEventListener('resize', handleResize);
}
// Bricks widgets render asynchronously after DOMContentLoaded.
@ -134,6 +178,18 @@
}
})();
// Retry sidebar icon init until the button element exists
(function retrySidebarIcon() {
var sidebar = document.getElementById('sage_sidebar');
var btn = document.getElementById('sidebar_toggle_btn');
if (btn && sidebar) {
var isCollapsed = sidebar.classList.contains('collapsed');
updateSidebarIcon(isCollapsed);
} else {
setTimeout(retrySidebarIcon, 200);
}
})();
// Expose global functions for bricks bind access
window.sageToggleTheme = toggleTheme;
window.sageToggleSidebar = toggleSidebar;