Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3509522
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
6 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment