mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-04 03:19:34 +00:00
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.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<IconName, ComponentType> = {
|
||||
apidocs: ApiOutlined,
|
||||
};
|
||||
|
||||
function readCollapsed(): boolean {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(SIDEBAR_COLLAPSED_KEY) || 'false');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function DonateButton({ ariaLabel }: { ariaLabel: string }) {
|
||||
return (
|
||||
<a
|
||||
@@ -125,8 +116,9 @@ export default function AppSidebar() {
|
||||
const { allSetting } = useAllSettings();
|
||||
const showSubFormats = !!(allSetting.subJsonEnable || allSetting.subClashEnable);
|
||||
|
||||
const [collapsed, setCollapsed] = useState<boolean>(() => 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 (
|
||||
<div className="ant-sidebar">
|
||||
<div
|
||||
className="ant-sidebar is-rail"
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<Layout.Sider
|
||||
theme={currentTheme}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
breakpoint="md"
|
||||
onCollapse={onSiderCollapse}
|
||||
collapsed={collapsedView}
|
||||
trigger={null}
|
||||
>
|
||||
<div className={`sider-brand${collapsed ? ' sider-brand-collapsed' : ''}`}>
|
||||
<div className={`sider-brand${collapsedView ? ' sider-brand-collapsed' : ''}`}>
|
||||
<div className="brand-block">
|
||||
<span className="brand-text">{collapsed ? '3X' : '3X-UI'}</span>
|
||||
<span className="brand-text">{collapsedView ? '3X' : '3X-UI'}</span>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
{!collapsedView && (
|
||||
<div className="brand-actions">
|
||||
<DonateButton ariaLabel={t('menu.donate') || 'Donate'} />
|
||||
<ThemeCycleButton
|
||||
@@ -260,7 +247,7 @@ export default function AppSidebar() {
|
||||
theme={currentTheme}
|
||||
mode="inline"
|
||||
selectedKeys={[selectedKey]}
|
||||
openKeys={collapsed ? undefined : openKeys}
|
||||
openKeys={collapsedView ? undefined : openKeys}
|
||||
onOpenChange={(keys) => setOpenKeys(keys as string[])}
|
||||
className="sider-nav"
|
||||
items={toMenuItems(navItems)}
|
||||
@@ -275,7 +262,7 @@ export default function AppSidebar() {
|
||||
onClick={onMenuClick}
|
||||
/>
|
||||
<div className="sider-footer">
|
||||
<VersionBadge version={panelVersion} collapsed={collapsed} />
|
||||
<VersionBadge version={panelVersion} collapsed={collapsedView} />
|
||||
</div>
|
||||
</Layout.Sider>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user