Skip to content

Commit

Permalink
feat: change the interaction of MenuBar in horizontal mode (#636)
Browse files Browse the repository at this point in the history
  • Loading branch information
kiwiwong authored Jan 20, 2022
1 parent ecdb312 commit b352afd
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 37 deletions.
60 changes: 59 additions & 1 deletion src/workbench/menuBar/__tests__/menubar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { cleanup, fireEvent, render } from '@testing-library/react';
import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';

import MenuBar, { actionClassName } from '../menuBar';
Expand Down Expand Up @@ -123,4 +123,62 @@ describe('Test MenuBar Component', () => {
fireEvent.click(elem);
expect(mockFn).toBeCalled();
});

test('Should support to execute the handleClickMenuBar method in HorizontalView', async () => {
const { getByText } = render(
<MenuBar
data={menuData}
onClick={TEST_FN}
mode={MenuBarMode.horizontal}
/>
);
const elem = getByText(TEST_ID);
const liElem = elem.closest('li');
const elemArr = liElem ? [liElem] : [];
const spanElem = getByText(TEST_DATA);
const ulElem = spanElem.closest('ul');
const originalFunc = document.elementsFromPoint;
document.elementsFromPoint = jest.fn(() => elemArr);

fireEvent.click(elem);
await waitFor(() => {
expect(ulElem?.style.opacity).toBe('1');
});

fireEvent.click(elem);
await waitFor(() => {
expect(ulElem?.style.opacity).toBe('0');
});

document.elementsFromPoint = originalFunc;
});

test('Should support to execute the clearAutoDisplay method in HorizontalView', async () => {
const { getByText } = render(
<MenuBar
data={menuData}
onClick={TEST_FN}
mode={MenuBarMode.horizontal}
/>
);
const elem = getByText(TEST_ID);
const liElem = elem.closest('li');
const elemArr = liElem ? [liElem] : [];
const spanElem = getByText(TEST_DATA);
const ulElem = spanElem.closest('ul');
const originalFunc = document.elementsFromPoint;
document.elementsFromPoint = jest.fn(() => elemArr);

fireEvent.click(elem);
await waitFor(() => {
expect(ulElem?.style.opacity).toBe('1');
});

fireEvent.click(document.body);
await waitFor(() => {
expect(ulElem?.style.opacity).toBe('0');
});

document.elementsFromPoint = originalFunc;
});
});
100 changes: 100 additions & 0 deletions src/workbench/menuBar/horizontalView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useState, useRef, useEffect } from 'react';
import {
getBEMElement,
prefixClaName,
getBEMModifier,
} from 'mo/common/className';
import { IMenuBarItem } from 'mo/model/workbench/menuBar';
import { Menu, MenuMode, MenuRef, IMenuProps } from 'mo/components/menu';
import Logo from './logo';

export const defaultClassName = prefixClaName('menuBar');
export const actionClassName = getBEMElement(defaultClassName, 'action');
export const horizontalClassName = getBEMModifier(
defaultClassName,
'horizontal'
);
export const logoClassName = getBEMElement(horizontalClassName, 'logo');
export const logoContentClassName = getBEMElement(logoClassName, 'content');

export interface IHorizontalViewProps {
data?: IMenuProps[];
onClick?: (event: React.MouseEvent<any, any>, item: IMenuBarItem) => void;
logo?: React.ReactNode;
}

export function HorizontalView(props: IHorizontalViewProps) {
const { data, onClick, logo } = props;
const menuRef = useRef<MenuRef>(null);
const [autoDisplayMenu, setAutoDisplayMenu] = useState(false);

const checkIsRootLiElem = (e: MouseEvent) => {
const target = e.target as HTMLElement;
const liElem = target.closest('li');
const menuBarElem = liElem?.parentElement?.parentElement;
const isRootLiElem =
!!menuBarElem &&
menuBarElem.classList.contains(horizontalClassName);
return isRootLiElem;
};

useEffect(() => {
const menuBarElem = document.getElementsByClassName(
horizontalClassName
)[0] as HTMLElement;

const handleClickMenuBar = (e: MouseEvent) => {
const isRootLiElem = checkIsRootLiElem(e);
if (!isRootLiElem) return;
if (autoDisplayMenu) {
e.preventDefault();
e.stopPropagation();
menuRef.current?.dispose?.();
}
// Delay the execution of setAutoDisplayMenu to ensure that the menu can be displayed.
setTimeout(() => setAutoDisplayMenu(!autoDisplayMenu));
};

const clearAutoDisplay = (e: MouseEvent) => {
if (!autoDisplayMenu) return;
const isRootLiElem = checkIsRootLiElem(e);
const target = e.target as HTMLElement;
const liElem = target.closest('li');
if (!isRootLiElem && !liElem?.dataset.submenu) {
setAutoDisplayMenu(false);
}
};

document.addEventListener('click', clearAutoDisplay);
menuBarElem?.addEventListener('click', handleClickMenuBar);

return () => {
document.removeEventListener('click', clearAutoDisplay);
menuBarElem?.removeEventListener('click', handleClickMenuBar);
};
}, [autoDisplayMenu]);

const trigger = autoDisplayMenu ? 'hover' : 'click';

const handleClickMenu = (e: React.MouseEvent, item: IMenuBarItem) => {
onClick?.(e, item);
menuRef.current!.dispose();
};

return (
<div className={horizontalClassName}>
<div className={logoClassName}>
{logo || <Logo className={logoContentClassName} />}
</div>
<Menu
ref={menuRef}
role="menu"
mode={MenuMode.Horizontal}
trigger={trigger}
onClick={handleClickMenu}
style={{ width: '100%' }}
data={data}
/>
</div>
);
}
44 changes: 8 additions & 36 deletions src/workbench/menuBar/menuBar.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import React, { useCallback, useEffect, useRef } from 'react';
import {
getBEMElement,
prefixClaName,
getBEMModifier,
} from 'mo/common/className';
import { getBEMElement, prefixClaName } from 'mo/common/className';
import { IMenuBar, IMenuBarItem } from 'mo/model/workbench/menuBar';
import { IMenuBarController } from 'mo/controller/menuBar';
import { DropDown, DropDownRef } from 'mo/components/dropdown';
import { IMenuProps, Menu, MenuMode, MenuRef } from 'mo/components/menu';
import { IMenuProps, Menu } from 'mo/components/menu';
import { Icon } from 'mo/components/icon';
import { KeybindingHelper } from 'mo/services/keybinding';
import { MenuBarMode } from 'mo/model/workbench/layout';
import Logo from './logo';
import { HorizontalView } from './horizontalView';

export const defaultClassName = prefixClaName('menuBar');
export const actionClassName = getBEMElement(defaultClassName, 'action');
export const horizontalClassName = getBEMModifier(
defaultClassName,
'horizontal'
);
export const logoClassName = getBEMElement(horizontalClassName, 'logo');
export const logoContentClassName = getBEMElement(logoClassName, 'content');

export function MenuBar(props: IMenuBar & IMenuBarController) {
const {
Expand All @@ -31,7 +21,6 @@ export function MenuBar(props: IMenuBar & IMenuBarController) {
logo,
} = props;
const childRef = useRef<DropDownRef>(null);
const menuRef = useRef<MenuRef>(null);

const addKeybindingForData = (
rawData: IMenuBarItem[] = []
Expand Down Expand Up @@ -62,14 +51,6 @@ export function MenuBar(props: IMenuBar & IMenuBarController) {
childRef.current!.dispose();
};

const handleClickHorizontalMenu = (
e: React.MouseEvent,
item: IMenuBarItem
) => {
onClick?.(e, item);
menuRef.current!.dispose();
};

const overlay = (
<Menu
role="menu"
Expand All @@ -92,20 +73,11 @@ export function MenuBar(props: IMenuBar & IMenuBarController) {

if (mode === MenuBarMode.horizontal) {
return (
<div className={horizontalClassName}>
<div className={logoClassName}>
{logo || <Logo className={logoContentClassName} />}
</div>
<Menu
ref={menuRef}
role="menu"
mode={MenuMode.Horizontal}
trigger="click"
onClick={handleClickHorizontalMenu}
style={{ width: '100%' }}
data={addKeybindingForData(data)}
/>
</div>
<HorizontalView
data={addKeybindingForData(data)}
onClick={onClick}
logo={logo}
/>
);
}

Expand Down

0 comments on commit b352afd

Please sign in to comment.