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,57 +12,121 @@
+icon: React.Node,
+children?: React.Node,
+variant?: MenuVariant,
+ +onChange?: boolean => void,
};
function Menu(props: MenuProps): React.Node {
const [isOpen, setIsOpen] = React.useState(false);
+ const [buttonPosition, setButtonPosition] = React.useState(null);
- const { icon, children, variant = 'thread-actions' } = props;
+ const buttonRef = React.useRef();
+ const renderMenu = useRenderMenu();
+ const { icon, children, variant = 'thread-actions', onChange } = props;
+
+ const updatePosition = React.useCallback(() => {
+ if (buttonRef.current) {
+ setButtonPosition(buttonRef.current.getBoundingClientRect());
+ }
+ }, []);
+
+ React.useEffect(updatePosition, [updatePosition]);
+
+ const menuActionListClasses = React.useMemo(
+ () =>
+ classnames(css.menuActionList, {
+ [css.menuActionListThreadActions]: variant === 'thread-actions',
+ [css.menuActionListMemberActions]: variant === 'member-actions',
+ }),
+ [variant],
+ );
+
+ const actionListStyle = React.useMemo(
+ () =>
+ buttonPosition
+ ? {
+ top: buttonPosition.top,
+ left: buttonPosition.left,
+ }
+ : {},
+ [buttonPosition],
+ );
+
+ const menuActionList = React.useMemo(() => {
+ return (
+
+ );
+ }, [actionListStyle, children, menuActionListClasses]);
+
+ React.useEffect(
+ () =>
+ renderMenu(previous => {
+ if (isOpen) {
+ return menuActionList;
+ } else if (previous === menuActionList) {
+ return null;
+ } else {
+ return previous;
+ }
+ }),
+ [isOpen, menuActionList, renderMenu],
+ );
const closeMenuCallback = React.useCallback(() => {
document.removeEventListener('click', closeMenuCallback);
if (isOpen) {
setIsOpen(false);
+ renderMenu(previous => {
+ if (previous === menuActionList) {
+ return null;
+ } else {
+ return previous;
+ }
+ });
}
- }, [isOpen]);
+ }, [isOpen, menuActionList, renderMenu]);
+
+ React.useEffect(() => {
+ onChange?.(isOpen);
+ }, [isOpen, onChange]);
+
+ React.useEffect(() => {
+ if (!window) {
+ return undefined;
+ }
+
+ window.addEventListener('resize', updatePosition);
+ return () => window.removeEventListener('resize', updatePosition);
+ }, [updatePosition]);
React.useEffect(() => {
if (!document || !isOpen) {
return undefined;
}
document.addEventListener('click', closeMenuCallback);
- return () => document.removeEventListener('click', closeMenuCallback);
+ return closeMenuCallback;
}, [closeMenuCallback, isOpen]);
const switchMenuCallback = React.useCallback(() => {
+ updatePosition();
setIsOpen(isMenuOpen => !isMenuOpen);
- }, []);
+ }, [updatePosition]);
if (React.Children.count === 0) {
return null;
}
- let menuActionList = null;
- if (isOpen) {
- const menuActionListClasses = classnames(css.menuActionList, {
- [css.menuActionListThreadActions]: variant === 'thread-actions',
- [css.menuActionListMemberActions]: variant === 'member-actions',
- });
-
- menuActionList = (
-
- );
- }
-
return (
-
-
+ >
);
}