Page MenuHomePhabricator

No OneTemporary

diff --git a/web/components/menu.react.js b/web/components/menu.react.js
index fedd647c7..3d968ba44 100644
--- a/web/components/menu.react.js
+++ b/web/components/menu.react.js
@@ -1,118 +1,119 @@
// @flow
import classnames from 'classnames';
import * as React from 'react';
import { useRenderMenu } from '../menu-provider.react';
import css from './menu.css';
type MenuVariant = 'thread-actions' | 'member-actions';
type MenuProps = {
+icon: React.Node,
+children?: React.Node,
+variant?: MenuVariant,
+onChange?: boolean => void,
};
function Menu(props: MenuProps): React.Node {
const buttonRef = React.useRef();
const {
renderMenu,
setMenuPosition,
closeMenu,
setCurrentOpenMenu,
currentOpenMenu,
} = useRenderMenu();
const { icon, children, variant = 'thread-actions', onChange } = props;
const ourSymbol = React.useRef(Symbol());
const menuActionListClasses = React.useMemo(
() =>
classnames(css.menuActionList, {
[css.menuActionListThreadActions]: variant === 'thread-actions',
[css.menuActionListMemberActions]: variant === 'member-actions',
}),
[variant],
);
const menuActionList = React.useMemo(
() => <div className={menuActionListClasses}>{children}</div>,
[children, menuActionListClasses],
);
const isOurMenuOpen = currentOpenMenu === ourSymbol.current;
const updatePosition = React.useCallback(() => {
if (buttonRef.current && isOurMenuOpen) {
const { top, left } = buttonRef.current.getBoundingClientRect();
setMenuPosition({ top, left });
}
}, [isOurMenuOpen, setMenuPosition]);
React.useEffect(() => {
if (!window) {
return undefined;
}
window.addEventListener('resize', updatePosition);
return () => window.removeEventListener('resize', updatePosition);
}, [updatePosition]);
React.useEffect(updatePosition, [updatePosition]);
const closeMenuCallback = React.useCallback(() => {
- closeMenu(menuActionList);
- }, [closeMenu, menuActionList]);
+ closeMenu(ourSymbol.current);
+ }, [closeMenu]);
React.useEffect(() => {
onChange?.(isOurMenuOpen);
}, [isOurMenuOpen, onChange]);
React.useEffect(() => {
if (!isOurMenuOpen) {
return undefined;
}
document.addEventListener('click', closeMenuCallback);
return () => {
document.removeEventListener('click', closeMenuCallback);
};
}, [closeMenuCallback, isOurMenuOpen]);
const prevActionListRef = React.useRef<React.Node>(null);
React.useEffect(() => {
if (!isOurMenuOpen) {
prevActionListRef.current = null;
return;
}
if (prevActionListRef.current === menuActionList) {
return;
}
renderMenu(menuActionList);
prevActionListRef.current = menuActionList;
}, [isOurMenuOpen, menuActionList, renderMenu]);
React.useEffect(() => {
- return () => closeMenu(prevActionListRef.current);
+ const ourSymbolValue = ourSymbol.current;
+ return () => closeMenu(ourSymbolValue);
}, [closeMenu]);
const onClickMenuCallback = React.useCallback(() => {
setCurrentOpenMenu(ourSymbol.current);
}, [setCurrentOpenMenu]);
if (React.Children.count(children) === 0) {
return null;
}
return (
<button
ref={buttonRef}
className={css.menuButton}
onClick={onClickMenuCallback}
>
{icon}
</button>
);
}
export default Menu;
diff --git a/web/menu-provider.react.js b/web/menu-provider.react.js
index 55265467f..265223229 100644
--- a/web/menu-provider.react.js
+++ b/web/menu-provider.react.js
@@ -1,84 +1,106 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import type { SetState } from 'lib/types/hook-types';
import css from './menu.css';
type MenuPosition = {
+top: number,
+left: number,
};
type Props = {
+children: React.Node,
};
type MenuContextType = {
- +renderMenu: SetState<React.Node>,
+ +renderMenu: React.Node => void,
+setMenuPosition: SetState<MenuPosition>,
- +closeMenu: React.Node => void,
- +currentOpenMenu: symbol,
- +setCurrentOpenMenu: SetState<symbol>,
+ +closeMenu: (symbol) => void,
+ +currentOpenMenu: ?symbol,
+ +setCurrentOpenMenu: (symbol) => void,
};
const MenuContext: React.Context<MenuContextType> = React.createContext<MenuContextType>(
{
renderMenu: () => {},
setMenuPosition: () => {},
closeMenu: () => {},
- currentOpenMenu: Symbol(),
+ currentOpenMenu: null,
setCurrentOpenMenu: () => {},
},
);
+type Menu = {
+ +node: ?React.Node,
+ +symbol: ?symbol,
+};
+
function MenuProvider(props: Props): React.Node {
const { children } = props;
- const [menu, setMenu] = React.useState(null);
- const [currentOpenMenu, setCurrentOpenMenu] = React.useState<symbol>(
- Symbol(),
- );
+ const [menu, setMenu] = React.useState<Menu>({ node: null, symbol: null });
const [position, setPosition] = React.useState<MenuPosition>({
top: 0,
left: 0,
});
- const closeMenu = React.useCallback((menuToClose: React.Node) => {
- setCurrentOpenMenu(Symbol());
- setMenu(oldMenu => {
- if (oldMenu === menuToClose) {
- return null;
- } else {
- return oldMenu;
+ const setMenuSymbol = React.useCallback(
+ (newSymbol: symbol) =>
+ setMenu(prevMenu => {
+ if (prevMenu.symbol === newSymbol) {
+ return prevMenu;
+ }
+ return { node: null, symbol: newSymbol };
+ }),
+ [],
+ );
+
+ const setMenuNode = React.useCallback(
+ (newMenuNode: React.Node) =>
+ setMenu(prevMenu => {
+ if (prevMenu.node === newMenuNode) {
+ return prevMenu;
+ }
+ return { ...prevMenu, node: newMenuNode };
+ }),
+ [],
+ );
+
+ const closeMenu = React.useCallback((menuToCloseSymbol: symbol) => {
+ setMenu(currentMenu => {
+ if (currentMenu.symbol === menuToCloseSymbol) {
+ return { node: null, symbol: null };
}
+ return currentMenu;
});
}, []);
const value = React.useMemo(
() => ({
- renderMenu: setMenu,
+ renderMenu: setMenuNode,
setMenuPosition: setPosition,
closeMenu,
- setCurrentOpenMenu,
- currentOpenMenu,
+ setCurrentOpenMenu: setMenuSymbol,
+ currentOpenMenu: menu.symbol,
}),
- [closeMenu, currentOpenMenu],
+ [closeMenu, menu.symbol, setMenuNode, setMenuSymbol],
);
return (
<>
<MenuContext.Provider value={value}>{children}</MenuContext.Provider>
<div style={position} className={css.container}>
- {menu}
+ {menu.node}
</div>
</>
);
}
function useRenderMenu(): MenuContextType {
const context = React.useContext(MenuContext);
invariant(context, 'MenuContext not found');
return context;
}
export { MenuProvider, useRenderMenu };

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 5:54 AM (23 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2690431
Default Alt Text
(6 KB)

Event Timeline