Files
Mujoco_WASM/web_platform/src/app/hooks/useSidebarLayout.ts
T
chenlin 13e35be98b
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
feat(web-platform): release V0.9.3 全模块界面重构
2026-09-08 17:57:57 +08:00

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,
};
}