diff --git a/web/components/menu.react.js b/web/components/menu.react.js
--- a/web/components/menu.react.js
+++ b/web/components/menu.react.js
@@ -3,6 +3,7 @@
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';
@@ -11,53 +12,97 @@
+icon: React.Node,
+children?: React.Node,
+variant?: MenuVariant,
+ +onChange?: boolean => void,
};
function Menu(props: MenuProps): React.Node {
- const [isOpen, setIsOpen] = React.useState(false);
+ const buttonRef = React.useRef();
+ const {
+ renderMenu,
+ setMenuPosition,
+ closeMenu,
+ currentMenu: currentlyRenderedMenu,
+ } = useRenderMenu();
+ const { icon, children, variant = 'thread-actions', onChange } = props;
- const { icon, children, variant = 'thread-actions' } = props;
+ const menuActionListClasses = React.useMemo(
+ () =>
+ classnames(css.menuActionList, {
+ [css.menuActionListThreadActions]: variant === 'thread-actions',
+ [css.menuActionListMemberActions]: variant === 'member-actions',
+ }),
+ [variant],
+ );
+
+ const menuActionList = React.useMemo(() => {
+ return
{children}
;
+ }, [children, menuActionListClasses]);
+
+ const updatePosition = React.useCallback(() => {
+ if (buttonRef.current && currentlyRenderedMenu === menuActionList) {
+ const { top, left } = buttonRef.current.getBoundingClientRect();
+ setMenuPosition({ top, left });
+ }
+ }, [currentlyRenderedMenu, menuActionList, setMenuPosition]);
+
+ // useLayoutEffect is necessary so that the menu position is immediately
+ // updated in the first render of component
+ React.useLayoutEffect(() => {
+ updatePosition();
+ }, [updatePosition]);
const closeMenuCallback = React.useCallback(() => {
- document.removeEventListener('click', closeMenuCallback);
- if (isOpen) {
- setIsOpen(false);
+ closeMenu(menuActionList);
+ onChange?.(false);
+ }, [closeMenu, menuActionList, onChange]);
+
+ React.useEffect(() => {
+ if (currentlyRenderedMenu === menuActionList) {
+ document.addEventListener('click', closeMenuCallback);
+ }
+ return () => {
+ document.removeEventListener('click', closeMenuCallback);
+ };
+ }, [closeMenuCallback, currentlyRenderedMenu, menuActionList]);
+
+ const prevActionListRef = React.useRef(null);
+ React.useEffect(() => {
+ if (prevActionListRef.current === currentlyRenderedMenu) {
+ renderMenu(menuActionList);
}
- }, [isOpen]);
+ prevActionListRef.current = menuActionList;
+ }, [currentlyRenderedMenu, menuActionList, renderMenu]);
React.useEffect(() => {
- if (!document || !isOpen) {
+ if (!window) {
return undefined;
}
- document.addEventListener('click', closeMenuCallback);
- return () => document.removeEventListener('click', closeMenuCallback);
- }, [closeMenuCallback, isOpen]);
- const switchMenuCallback = React.useCallback(() => {
- setIsOpen(isMenuOpen => !isMenuOpen);
- }, []);
+ window.addEventListener('resize', updatePosition);
+ return () => window.removeEventListener('resize', updatePosition);
+ }, [updatePosition]);
- if (React.Children.count(children) === 0) {
- return null;
- }
+ React.useEffect(() => {
+ return () => closeMenu(prevActionListRef.current);
+ }, [closeMenu]);
- let menuActionList = null;
- if (isOpen) {
- const menuActionListClasses = classnames(css.menuActionList, {
- [css.menuActionListThreadActions]: variant === 'thread-actions',
- [css.menuActionListMemberActions]: variant === 'member-actions',
- });
+ const onClickMenuCallback = React.useCallback(() => {
+ renderMenu(menuActionList);
+ onChange?.(true);
+ }, [menuActionList, onChange, renderMenu]);
- menuActionList = {children}
;
+ if (React.Children.count(children) === 0) {
+ return null;
}
return (
-
-
- {menuActionList}
-
+
);
}