From 573c43e4458239c068db61318f7cf67ce1b1ec38 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 3 Jun 2026 15:24:55 +0200 Subject: [PATCH] feat(sidebar): collapse to icon rail, expand on hover Sidebar is icon-only by default and expands as an overlay on hover, so the dashboard content underneath no longer reflows. Drops the persisted collapse state and the click trigger that conflicted with hover. --- frontend/src/layouts/AppSidebar.css | 23 ++++++++++++++++ frontend/src/layouts/AppSidebar.tsx | 41 ++++++++++------------------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/frontend/src/layouts/AppSidebar.css b/frontend/src/layouts/AppSidebar.css index 4784572f..76265d03 100644 --- a/frontend/src/layouts/AppSidebar.css +++ b/frontend/src/layouts/AppSidebar.css @@ -5,6 +5,20 @@ align-self: flex-start; } +.ant-sidebar.is-rail { + flex: 0 0 80px; + width: 80px; + overflow: visible; +} + +.ant-sidebar.is-rail > .ant-layout-sider { + z-index: 100; +} + +.ant-sidebar.is-rail:hover > .ant-layout-sider { + box-shadow: 2px 0 16px rgba(0, 0, 0, 0.18); +} + .sider-brand, .drawer-brand { font-weight: 600; @@ -245,6 +259,15 @@ min-width: 0 !important; width: 0 !important; } + + .ant-sidebar, + .ant-sidebar.is-rail { + flex: 0 0 0 !important; + width: 0 !important; + min-width: 0 !important; + max-width: 0 !important; + overflow: hidden !important; + } } body.dark .ant-drawer-content, diff --git a/frontend/src/layouts/AppSidebar.tsx b/frontend/src/layouts/AppSidebar.tsx index 8e662ed2..52a89580 100644 --- a/frontend/src/layouts/AppSidebar.tsx +++ b/frontend/src/layouts/AppSidebar.tsx @@ -35,7 +35,6 @@ import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme'; import { useAllSettings } from '@/api/queries/useAllSettings'; import './AppSidebar.css'; -const SIDEBAR_COLLAPSED_KEY = 'isSidebarCollapsed'; const DONATE_URL = 'https://donate.sanaei.dev/'; const REPO_URL = 'https://github.com/MHSanaei/3x-ui'; const LOGOUT_KEY = '__logout__'; @@ -54,14 +53,6 @@ const iconByName: Record = { apidocs: ApiOutlined, }; -function readCollapsed(): boolean { - try { - return JSON.parse(localStorage.getItem(SIDEBAR_COLLAPSED_KEY) || 'false'); - } catch { - return false; - } -} - function DonateButton({ ariaLabel }: { ariaLabel: string }) { return ( (() => readCollapsed()); + const [hovered, setHovered] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false); + const collapsedView = !hovered; const currentTheme: 'light' | 'dark' = isDark ? 'dark' : 'light'; const panelVersion = window.X_UI_CUR_VER || ''; @@ -210,13 +202,6 @@ export default function AppSidebar() { openLink(String(key)); }, [openLink]); - const onSiderCollapse = useCallback((isCollapsed: boolean, type: 'clickTrigger' | 'responsive') => { - if (type === 'clickTrigger') { - localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(isCollapsed)); - setCollapsed(isCollapsed); - } - }, []); - const cycleTheme = useCallback((id: string) => { pauseAnimationsUntilLeave(id); if (!isDark) { @@ -231,19 +216,21 @@ export default function AppSidebar() { }, [isDark, isUltra, toggleTheme, toggleUltra]); return ( -
+
setHovered(true)} + onMouseLeave={() => setHovered(false)} + > -
+
- {collapsed ? '3X' : '3X-UI'} + {collapsedView ? '3X' : '3X-UI'}
- {!collapsed && ( + {!collapsedView && (
setOpenKeys(keys as string[])} className="sider-nav" items={toMenuItems(navItems)} @@ -275,7 +262,7 @@ export default function AppSidebar() { onClick={onMenuClick} />
- +