66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import { useCallback, useSyncExternalStore, useState, type SetStateAction } from 'react';
|
|
|
|
const STORAGE_KEY = 'mujoco-platform-layout';
|
|
function readPreference(): { left: boolean; right: boolean } | null {
|
|
try {
|
|
const value = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? 'null');
|
|
return typeof value?.left === 'boolean' && typeof value?.right === 'boolean' ? value : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
const resolve = (next: SetStateAction<boolean>, current: boolean) =>
|
|
typeof next === 'function' ? next(current) : next;
|
|
|
|
/** 窄屏开关是临时状态;不会用临时折叠覆盖桌面偏好。侧栏始终挂载。 */
|
|
export function useSidebarLayout() {
|
|
const [preference, setPreference] = useState(readPreference);
|
|
const [narrowSide, setNarrowSide] = useState<'left' | 'right' | null>(null);
|
|
const subscribe = useCallback((notify: () => void) => {
|
|
const resize = () => {
|
|
if (window.innerWidth >= 1024) setNarrowSide(null);
|
|
notify();
|
|
};
|
|
window.addEventListener('resize', resize);
|
|
return () => window.removeEventListener('resize', resize);
|
|
}, []);
|
|
const width = useSyncExternalStore(subscribe, () => window.innerWidth);
|
|
const narrow = width < 1024;
|
|
const desktop = preference ?? { left: width >= 1440, right: true };
|
|
// Viewer 与导入回调会长期持有 setter;不能捕获首次渲染时的屏幕宽度。
|
|
const setSide = useCallback((side: 'left' | 'right', next: SetStateAction<boolean>) => {
|
|
const currentWidth = window.innerWidth;
|
|
if (currentWidth < 1024) {
|
|
setNarrowSide((current) =>
|
|
resolve(next, current === side) ? side : current === side ? null : current,
|
|
);
|
|
return;
|
|
}
|
|
setPreference((current) => {
|
|
const value = current ?? { left: currentWidth >= 1440, right: true };
|
|
const updated = { ...value, [side]: resolve(next, value[side]) };
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
|
} catch {
|
|
/* 会话内仍可调整 */
|
|
}
|
|
return updated;
|
|
});
|
|
}, []);
|
|
const setLeftOpen = useCallback(
|
|
(value: SetStateAction<boolean>) => setSide('left', value),
|
|
[setSide],
|
|
);
|
|
const setRightOpen = useCallback(
|
|
(value: SetStateAction<boolean>) => setSide('right', value),
|
|
[setSide],
|
|
);
|
|
return {
|
|
width,
|
|
leftOpen: narrow ? narrowSide === 'left' : desktop.left,
|
|
rightOpen: narrow ? narrowSide === 'right' : desktop.right,
|
|
setLeftOpen,
|
|
setRightOpen,
|
|
};
|
|
}
|