mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-10 05:10:58 +00:00
feat(ui): let users pin the sidebar
Restore a persistent expanded-sidebar choice while preserving the compact hover rail as the default.
This commit is contained in:
@@ -230,6 +230,39 @@
|
||||
padding: 8px 8px 12px;
|
||||
}
|
||||
|
||||
.sidebar-pin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 0 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--ant-color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.sidebar-pin:hover,
|
||||
.sidebar-pin:focus-visible {
|
||||
background-color: color-mix(in srgb, var(--ant-color-primary) 10%, transparent);
|
||||
color: var(--ant-color-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sidebar-pin .anticon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.ant-layout-sider-collapsed .sidebar-pin {
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sider-version {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
MessageOutlined,
|
||||
MoonFilled,
|
||||
MoonOutlined,
|
||||
PushpinFilled,
|
||||
PushpinOutlined,
|
||||
ReadOutlined,
|
||||
SafetyOutlined,
|
||||
SettingOutlined,
|
||||
@@ -44,6 +46,7 @@ const DOCS_URL = 'https://docs.sanaei.dev/';
|
||||
const REPO_URL = 'https://github.com/MHSanaei/3x-ui';
|
||||
const LOGOUT_KEY = '__logout__';
|
||||
const RAIL_WIDTH = 72;
|
||||
const SIDEBAR_PINNED_KEY = 'sidebar-pinned';
|
||||
const railStyle = { '--sider-rail': `${RAIL_WIDTH}px` } as CSSProperties;
|
||||
|
||||
let hoveredAcrossRemounts = false;
|
||||
@@ -135,6 +138,20 @@ function ThemeCycleButton({ id, isDark, isUltra, onCycle, ariaLabel }: {
|
||||
);
|
||||
}
|
||||
|
||||
function readSidebarPinned() {
|
||||
try {
|
||||
return localStorage.getItem(SIDEBAR_PINNED_KEY) === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSidebarPinned(pinned: boolean) {
|
||||
try {
|
||||
localStorage.setItem(SIDEBAR_PINNED_KEY, String(pinned));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export default function AppSidebar() {
|
||||
const { t } = useTranslation();
|
||||
const { isDark, isUltra, toggleTheme, toggleUltra } = useTheme();
|
||||
@@ -144,8 +161,9 @@ export default function AppSidebar() {
|
||||
const showSubFormats = !!(allSetting.subJsonEnable || allSetting.subClashEnable);
|
||||
|
||||
const [hovered, setHovered] = useState(() => hoveredAcrossRemounts);
|
||||
const [pinned, setPinned] = useState(readSidebarPinned);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const railCollapsed = !hovered;
|
||||
const railCollapsed = !hovered && !pinned;
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const updateHovered = useCallback((value: boolean) => {
|
||||
@@ -153,6 +171,14 @@ export default function AppSidebar() {
|
||||
setHovered(value);
|
||||
}, []);
|
||||
|
||||
const togglePinned = useCallback(() => {
|
||||
setPinned((value) => {
|
||||
const next = !value;
|
||||
saveSidebarPinned(next);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setTimeout(() => {
|
||||
const el = rootRef.current;
|
||||
@@ -309,6 +335,17 @@ export default function AppSidebar() {
|
||||
onClick={onMenuClick}
|
||||
/>
|
||||
<div className="sider-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-pin"
|
||||
aria-label={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
|
||||
aria-pressed={pinned}
|
||||
title={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
|
||||
onClick={togglePinned}
|
||||
>
|
||||
{pinned ? <PushpinFilled /> : <PushpinOutlined />}
|
||||
{!railCollapsed && <span>{t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}</span>}
|
||||
</button>
|
||||
<VersionBadge version={panelVersion} collapsed={railCollapsed} />
|
||||
</div>
|
||||
</Layout.Sider>
|
||||
|
||||
Reference in New Issue
Block a user