feat: 优化web
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import type { MenuNode } from '@/lib/api/types/menu';
|
||||
|
||||
const HOVER_LEAVE_MS = 280;
|
||||
|
||||
type FlyoutState = {
|
||||
flyoutRoot: MenuNode | null;
|
||||
l1AnchorRect: DOMRect | null;
|
||||
openFlyout: (node: MenuNode, anchorEl: HTMLDivElement) => void;
|
||||
scheduleCloseFlyout: () => void;
|
||||
clearCloseTimer: () => void;
|
||||
closeFlyoutNow: () => void;
|
||||
toggleFlyoutClick: (node: MenuNode, wrapper: HTMLDivElement | null) => void;
|
||||
l1AnchorElRef: React.RefObject<HTMLDivElement | null>;
|
||||
railScrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
/** 从 ClassicCollapsedSidebar 提取的 flyout 状态管理逻辑 */
|
||||
export function useFlyoutState(pathname: string): FlyoutState {
|
||||
const [flyoutRoot, setFlyoutRoot] = useState<MenuNode | null>(null);
|
||||
const [l1AnchorRect, setL1AnchorRect] = useState<DOMRect | null>(null);
|
||||
const l1AnchorElRef = useRef<HTMLDivElement | null>(null);
|
||||
const railScrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const clearCloseTimer = useCallback(() => {
|
||||
if (closeTimerRef.current) {
|
||||
clearTimeout(closeTimerRef.current);
|
||||
closeTimerRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const closeFlyout = useCallback(() => {
|
||||
setFlyoutRoot(null);
|
||||
setL1AnchorRect(null);
|
||||
l1AnchorElRef.current = null;
|
||||
}, []);
|
||||
|
||||
const scheduleCloseFlyout = useCallback(() => {
|
||||
clearCloseTimer();
|
||||
closeTimerRef.current = setTimeout(() => {
|
||||
closeFlyout();
|
||||
closeTimerRef.current = null;
|
||||
}, HOVER_LEAVE_MS);
|
||||
}, [clearCloseTimer, closeFlyout]);
|
||||
|
||||
const closeFlyoutNow = useCallback(() => {
|
||||
clearCloseTimer();
|
||||
closeFlyout();
|
||||
}, [clearCloseTimer, closeFlyout]);
|
||||
|
||||
const syncL1Anchor = useCallback(() => {
|
||||
const el = l1AnchorElRef.current;
|
||||
if (el) setL1AnchorRect(el.getBoundingClientRect());
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (flyoutRoot) syncL1Anchor();
|
||||
}, [flyoutRoot, syncL1Anchor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!flyoutRoot) return;
|
||||
const onResize = () => syncL1Anchor();
|
||||
window.addEventListener('resize', onResize);
|
||||
const rs = railScrollRef.current;
|
||||
rs?.addEventListener('scroll', onResize, { passive: true });
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
rs?.removeEventListener('scroll', onResize);
|
||||
};
|
||||
}, [flyoutRoot, syncL1Anchor]);
|
||||
|
||||
useEffect(() => {
|
||||
closeFlyout();
|
||||
}, [pathname, closeFlyout]);
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') closeFlyout();
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [closeFlyout]);
|
||||
|
||||
useEffect(() => clearCloseTimer, [clearCloseTimer]);
|
||||
|
||||
const openFlyout = useCallback(
|
||||
(node: MenuNode, anchorEl: HTMLDivElement) => {
|
||||
clearCloseTimer();
|
||||
l1AnchorElRef.current = anchorEl;
|
||||
setL1AnchorRect(anchorEl.getBoundingClientRect());
|
||||
setFlyoutRoot(node);
|
||||
},
|
||||
[clearCloseTimer],
|
||||
);
|
||||
|
||||
const toggleFlyoutClick = useCallback(
|
||||
(node: MenuNode, wrapper: HTMLDivElement | null) => {
|
||||
setFlyoutRoot((prev) => {
|
||||
if (prev?.id === node.id) {
|
||||
setL1AnchorRect(null);
|
||||
l1AnchorElRef.current = null;
|
||||
return null;
|
||||
}
|
||||
if (wrapper) {
|
||||
l1AnchorElRef.current = wrapper;
|
||||
setL1AnchorRect(wrapper.getBoundingClientRect());
|
||||
return node;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
flyoutRoot,
|
||||
l1AnchorRect,
|
||||
openFlyout,
|
||||
scheduleCloseFlyout,
|
||||
clearCloseTimer,
|
||||
closeFlyoutNow,
|
||||
toggleFlyoutClick,
|
||||
l1AnchorElRef,
|
||||
railScrollRef,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user