mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-29 06:31:58 +00:00
fix(ui): reserve space for pinned sidebar
Keep page content accessible when the desktop sidebar remains expanded and cover the complete pin lifecycle.
This commit is contained in:
1 parent
91c5d7b19f
commit
ac584cfc90
3 files changed
+32
-12
No files matched your search
@@ -12,7 +12,7 @@
|
|||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-sidebar > .ant-layout-sider:not(.ant-layout-sider-collapsed) {
|
.ant-sidebar:not(.sidebar-pinned) > .ant-layout-sider:not(.ant-layout-sider-collapsed) {
|
||||||
box-shadow: 0 0 32px rgba(0, 0, 0, 0.22);
|
box-shadow: 0 0 32px rgba(0, 0, 0, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ const DOCS_URL = 'https://docs.sanaei.dev/';
|
|||||||
const REPO_URL = 'https://github.com/MHSanaei/3x-ui';
|
const REPO_URL = 'https://github.com/MHSanaei/3x-ui';
|
||||||
const LOGOUT_KEY = '__logout__';
|
const LOGOUT_KEY = '__logout__';
|
||||||
const RAIL_WIDTH = 72;
|
const RAIL_WIDTH = 72;
|
||||||
|
const SIDER_WIDTH = 220;
|
||||||
const SIDEBAR_PINNED_KEY = 'sidebar-pinned';
|
const SIDEBAR_PINNED_KEY = 'sidebar-pinned';
|
||||||
const railStyle = { '--sider-rail': `${RAIL_WIDTH}px` } as CSSProperties;
|
|
||||||
|
|
||||||
let hoveredAcrossRemounts = false;
|
let hoveredAcrossRemounts = false;
|
||||||
|
|
||||||
@@ -164,6 +164,10 @@ export default function AppSidebar() {
|
|||||||
const [pinned, setPinned] = useState(readSidebarPinned);
|
const [pinned, setPinned] = useState(readSidebarPinned);
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const railCollapsed = !hovered && !pinned;
|
const railCollapsed = !hovered && !pinned;
|
||||||
|
const railStyle = useMemo(
|
||||||
|
() => ({ '--sider-rail': `${pinned ? SIDER_WIDTH : RAIL_WIDTH}px` }) as CSSProperties,
|
||||||
|
[pinned],
|
||||||
|
);
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const updateHovered = useCallback((value: boolean) => {
|
const updateHovered = useCallback((value: boolean) => {
|
||||||
@@ -172,12 +176,10 @@ export default function AppSidebar() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const togglePinned = useCallback(() => {
|
const togglePinned = useCallback(() => {
|
||||||
setPinned((value) => {
|
const next = !pinned;
|
||||||
const next = !value;
|
saveSidebarPinned(next);
|
||||||
saveSidebarPinned(next);
|
setPinned(next);
|
||||||
return next;
|
}, [pinned]);
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
@@ -287,14 +289,14 @@ export default function AppSidebar() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
className="ant-sidebar"
|
className={`ant-sidebar${pinned ? ' sidebar-pinned' : ''}`}
|
||||||
style={railStyle}
|
style={railStyle}
|
||||||
onMouseEnter={() => updateHovered(true)}
|
onMouseEnter={() => updateHovered(true)}
|
||||||
onMouseLeave={() => updateHovered(false)}
|
onMouseLeave={() => updateHovered(false)}
|
||||||
>
|
>
|
||||||
<Layout.Sider
|
<Layout.Sider
|
||||||
theme={currentTheme}
|
theme={currentTheme}
|
||||||
width={220}
|
width={SIDER_WIDTH}
|
||||||
collapsedWidth={RAIL_WIDTH}
|
collapsedWidth={RAIL_WIDTH}
|
||||||
collapsed={railCollapsed}
|
collapsed={railCollapsed}
|
||||||
>
|
>
|
||||||
@@ -307,7 +309,7 @@ export default function AppSidebar() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="sidebar-pin"
|
className="sidebar-pin"
|
||||||
aria-label={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
|
aria-label={t('menu.pinSidebar')}
|
||||||
aria-pressed={pinned}
|
aria-pressed={pinned}
|
||||||
title={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
|
title={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
|
||||||
onClick={togglePinned}
|
onClick={togglePinned}
|
||||||
|
|||||||
@@ -37,13 +37,31 @@ test('keeps the sidebar expanded after pinning it from the header and restores t
|
|||||||
fireEvent.mouseLeave(sidebarRoot!);
|
fireEvent.mouseLeave(sidebarRoot!);
|
||||||
|
|
||||||
expect(sidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(false);
|
expect(sidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(false);
|
||||||
|
expect(sidebarRoot?.getAttribute('style')).toContain('--sider-rail: 220px');
|
||||||
expect(localStorage.getItem('sidebar-pinned')).toBe('true');
|
expect(localStorage.getItem('sidebar-pinned')).toBe('true');
|
||||||
|
|
||||||
first.unmount();
|
first.unmount();
|
||||||
|
|
||||||
const second = renderSidebar();
|
const second = renderSidebar();
|
||||||
const restoredSidebar = second.container.querySelector('.ant-layout-sider');
|
const restoredSidebar = second.container.querySelector('.ant-layout-sider');
|
||||||
|
const restoredSidebarRoot = second.container.querySelector('.ant-sidebar');
|
||||||
|
|
||||||
expect(restoredSidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(false);
|
expect(restoredSidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(false);
|
||||||
expect(screen.getByRole('button', { name: 'Unpin sidebar' })).not.toBeNull();
|
expect(restoredSidebarRoot?.getAttribute('style')).toContain('--sider-rail: 220px');
|
||||||
|
expect(screen.getByRole('button', { name: 'Pin sidebar' })).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns to the compact rail after unpinning', () => {
|
||||||
|
const view = renderSidebar();
|
||||||
|
const sidebar = view.container.querySelector('.ant-layout-sider');
|
||||||
|
const sidebarRoot = view.container.querySelector('.ant-sidebar');
|
||||||
|
|
||||||
|
fireEvent.mouseEnter(sidebarRoot!);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Pin sidebar' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Pin sidebar' }));
|
||||||
|
fireEvent.mouseLeave(sidebarRoot!);
|
||||||
|
|
||||||
|
expect(sidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(true);
|
||||||
|
expect(sidebarRoot?.getAttribute('style')).toContain('--sider-rail: 72px');
|
||||||
|
expect(localStorage.getItem('sidebar-pinned')).toBe('false');
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user