[React Ocean 组件库] 实现导航菜单 | 手风琴

2,064 阅读1分钟

普通模式

e07c4280-ae8a-4199-b989-261fbac37648.gif

手风琴模式

08f6c9fd-c660-4400-a8ce-5a68aae8dc3d.gif

第一:菜单基本使用

const MenuUse = () => {
    const items: MenuItem[] = [
      getItem('Navigation One', 'sub1', <MailOutlined />, [
        getItem('Item 1', 'g1', null),
        getItem('Item 2', 'g2', null),
  ]),

      getItem('Navigation Two', 'sub2', <AppstoreOutlined />, []),

      getItem('Navigation Three', 'sub4', <SettingOutlined />, [
        getItem('Option 9', '9', null, [
          getItem('Item 1', 'g14', null),
          getItem('Item 2', 'g25', null),
    ]),
        getItem('Option 10', '10'),
  ]),
];
   return <Menu items={items} ableToggle={true} curSelectKey={'sub2'} />;
};

菜单的基本 HTML 结构实现

tree.png

菜单的展开和收缩基本思路

image.png

通过使用 ref,获取到对应收缩子容器的节点。

实现树的展开和收缩

第一:收缩的时候,高度变为 0 + 过渡动画

第二:扩张的时候,高度变为本来容器的高度 + 过渡动画

这里有个细节大家要注意:扩张结束之后,高度应该设为 auto, 不设 auto,树展开的高度计算就会有偏差,导致出现 UI bug

  const transitionCallback = (callback: callback) => () => {
    callback(wrapperRef.current, heightRef);
  };

    
  const handleOnEnter = transitionCallback((node: HTMLElement) => {
    node.style['height'] = collapsedSize + 'px';
  });

  const handleEntering = transitionCallback((node: HTMLElement) => {
    node.style['height'] = heightRef.current;
  });

  const handelEntered = transitionCallback(
    (node: HTMLElement, heightRef: React.MutableRefObject<string>) => {
      node.style['height'] = 'auto';
      heightRef.current = getWrapperHeight();
    },
  );

  const handleOnExit = transitionCallback(
    (node: HTMLElement, heightRef: React.MutableRefObject<string>) => {
      const height = node.clientHeight + 'px';
      node.style['height'] = height;

      if (!heightRef.current) {
        heightRef.current = height;
      }
    },
  );

  const handleOnExiting = transitionCallback((node: HTMLElement) => {
    wrapperRef.current.style['height'] = 0;
  });

实现手风琴思路

image.png

比方说,点击了第一个菜单的三级导航 1-1-1,那么除了第一个菜单的三级导航及其父元素不改变状态(1,1-1,1-1-1),其余的导航 expand 属性都变为 false -- 收缩,即实现手风琴。

  //儿元素 和 其父元素 key的映射表。
  const map = getParentKey(props.items);

 // 当前点击元素 key 和 其所有父元素 key 的集合
  const memoParent = useMemo(() => {
    const parent = [];
    const getParent = (key: React.Key): React.Key | null => map.get(key);
    parent.push(MenuKeys.selectKey);
    let p = getParent(MenuKeys.selectKey);
    while (p) {
      parent.push(p);
      p = getParent(p);
    }
    return parent;
  }, [MenuKeys.selectKey]);

其余关键代码 -- HTML 结构

     <MenuNode
            memoParent={memoParent}
            key={c.key}
            refContainer={ref}
            level={level}
            menuItem={c}
            isToggle={props.ableToggle}
            selectKey={MenuKeys.selectKey}
            curToggleKey={MenuKeys.curExpandKey}
            clickSelectMenu={clickSelectMenu}
            recursive={recursive}
          >
          <MenuItemContainer
            onClick={(e) => clickSelectMenu(c.key, e)}
            curKey={c.key}
            selectKey={selectKey}
          >
            <FirstMenuItemContainer>
            </FirstMenuItemContainer>
          </MenuItemContainer>
          <ChildMenu ref={refContainer}>{c.children && recursive(c.children, level + 1)}</ChildMenu>
    <MenuNode/>

自此导航菜单实现完成