hanav
Version:
A React Navigation Menu Component Library.
140 lines (125 loc) • 4.96 kB
JavaScript
import React, { useContext, useLayoutEffect, useState, useEffect, useCallback, useRef } from "react";
import { getSlateWrapperTranslateVal } from "../../utils";
import { useEntryExitFocus } from "../../hooks";
import { ContextForContent } from "../../context";
export default function ContentWrapper({
children, outer = {}, style, style2, innerStyle2, tp,
onExpanding = () => {}, onExpanded = () => {}, onCollapsing = () => {}, onCollapsed = () => {}, ...wrapperInnerProps }) {
const {
openedMenuIdx,
overMenuPanel,
leaveMenuPanel,
dur,
contentWrapperRef,
onlyKeyFocus,
prevMenuIdxRef,
isKeyActive,
btnsRef,
panelsRef,
headFocusItemInContent,
close,
gap,
dynamicWidth,
} = useContext(ContextForContent);
const [calcLayout, setLayout] = useState(false); // 获取尺寸
const [show, setS] = useState(false); // 展示动画,横贯面板展开之前和收起之后
/** 是否装载 dom */
const loaded = openedMenuIdx > -1 || (openedMenuIdx < 0 && show);
/** 面板们的宽度 */
const panelsClientWidthRef = useRef([]);
/** 面板的元素们的高度,完成过渡动画 */
const panelsHeightRef = useRef([]);
/** 面板元素们的宽度,完成过渡动画 */
const panelsWidthRef = useRef([]);
/** 动画正在进行吗,正在进行则不允许 tab 聚焦 */
const transRunning = useRef(false);
useLayoutEffect(() => {
if (openedMenuIdx > -1 && calcLayout === false) {
setLayout(true);
// 缓存的元素们的尺寸
panelsClientWidthRef.current = panelsRef.current.map(e => e?.clientWidth || 0);
panelsHeightRef.current = panelsRef.current.map(e => e?.clientHeight || 0);
panelsWidthRef.current = panelsRef.current.map(e => e?.clientWidth || 0);
}
}, [openedMenuIdx, calcLayout]);
useEffect(() => {
if (calcLayout) {
transRunning.current = true;
setS(true);
}
}, [calcLayout]);
useEffect(() => {
if (show && prevMenuIdxRef.current === -1) onExpanding();
else if (show && openedMenuIdx < 0) onCollapsing();
}, [show, openedMenuIdx]);
// 焦点的入口和出口控制
useEntryExitFocus(openedMenuIdx, onlyKeyFocus, prevMenuIdxRef, isKeyActive, btnsRef, panelsRef, headFocusItemInContent, show);
const transitionEnd = useCallback(e => {
const contentWrapper = contentWrapperRef.current;
if (tp !== e.propertyName) return; // 仅生效一个属性的 transitionend
if (e.target !== contentWrapper && e.target !== contentWrapper.parentNode) return; // 过滤冒泡的 transitionend 事件
transRunning.current = false;
if (openedMenuIdx < 0) {
setS(false);
setLayout(false);
prevMenuIdxRef.current = -1;
onCollapsed();
} else onExpanded();
}, [openedMenuIdx, onExpanded]);
// loaded > 获取尺寸 > 初始状态 > tick > 动画状态
if (loaded) {
/** 是否为收起菜单操作 */
const isCollapse = openedMenuIdx < 0 && prevMenuIdxRef.current > -1;
const collapseOrTEnded = (!show || isCollapse);
const xTransform = moveX(collapseOrTEnded, close, openedMenuIdx, prevMenuIdxRef, btnsRef, panelsClientWidthRef, gap);
const { style: outerStyle, ...otherOuterProps } = outer;
const width = !dynamicWidth ?
"100%" :
openedMenuIdx === -1 ?
(panelsWidthRef.current[prevMenuIdxRef.current] || 0) :
(panelsWidthRef.current[openedMenuIdx] || 0);
const _innerStyle2 = typeof innerStyle2 === "function" ? innerStyle2(collapseOrTEnded, gap, dur) : innerStyle2;
const _style2 = typeof style2 === "function" ? style2(gap) : style2;
return <div
style={{
..._style2,
position: "absolute",
left: 0,
width,
height: isCollapse ? panelsHeightRef.current[prevMenuIdxRef.current] : panelsHeightRef.current[openedMenuIdx],
transition: show ? `transform ${dur}s, height ${dur}s, width ${dur}s` : null,
transform: xTransform,
...outerStyle,
}}
{...otherOuterProps}>
<div
ref={contentWrapperRef}
onMouseOver={overMenuPanel}
onMouseLeave={leaveMenuPanel}
style={{
..._innerStyle2,
width: "100%",
height: "100%",
overflow: "hidden",
...style,
}}
onTransitionEnd={transitionEnd}
{...wrapperInnerProps}>
{children(transRunning)}
</div>
</div>;
}
return null;
function moveX(collapseOrTEnded, close, openedMenuIdx, prevMenuIdxRef, btnsRef, panelsClientWidthRef, gap) {
if (close) {
const menuIdx = collapseOrTEnded && openedMenuIdx < 0 ? prevMenuIdxRef.current : openedMenuIdx;
return getSlateWrapperTranslateVal(
gap,
menuIdx,
btnsRef,
panelsClientWidthRef,
close === true ? 0 : Array.isArray(close) ? close[menuIdx] || 0 : close);
}
return `translateY(${gap}px)`;
}
}