diff --git a/web/components/menu.css b/web/components/menu.css --- a/web/components/menu.css +++ b/web/components/menu.css @@ -2,33 +2,49 @@ background-color: transparent; border: none; cursor: pointer; - color: var(--thread-top-bar-menu-color); + color: inherit; } div.menuActionList { position: absolute; - right: 10px; - top: 55px; - z-index: 1; + z-index: 4; display: flex; flex-direction: column; background-color: var(--menu-bg); + color: var(--menu-color); border-radius: 4px; padding: 4px 0; + line-height: var(--line-height-text); + min-width: max-content; +} + +div.menuActionListThreadActions { + font-size: var(--m-font-16); + top: 40px; + right: -20px; +} + +div.menuActionListMemberActions { + font-size: var(--xs-font-12); + background-color: var(--menu-bg-light); + color: var(--menu-color-light); + top: 0; + right: 5px; } button.menuAction { + color: inherit; z-index: 1; background-color: transparent; padding: 12px 16px; - color: var(--menu-color); - background-color: var(--menu-bg); - font-size: var(--m-font-16); line-height: 1.5; + background-color: transparent; border: none; cursor: pointer; display: flex; align-items: center; + color: inherit; + font-size: inherit; } button.menuAction:hover { @@ -36,11 +52,10 @@ } div.menuActionIcon { - font-size: var(--l-font-18); + font-size: 1.125em; display: flex; justify-content: center; margin-right: 8px; - width: 20px; } button.menuActionDangerous { 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 @@ -1,54 +1,117 @@ // @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 [isOpen, setIsOpen] = React.useState(false); - const { icon, children } = props; + const buttonRef = React.useRef(); + const { renderMenu, setMenuPosition } = useRenderMenu(); + const { icon, children, variant = 'thread-actions', onChange } = props; + + const updatePosition = React.useCallback(() => { + if (buttonRef.current && isOpen) { + const { top, left } = buttonRef.current.getBoundingClientRect(); + setMenuPosition({ top, left }); + } + }, [isOpen, setMenuPosition]); + + React.useEffect(updatePosition, [updatePosition]); + + 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]); + + 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(children) === 0) { return null; } - let menuActionList = null; - if (isOpen) { - menuActionList =
{children}
; - } - return ( -
- - {menuActionList} -
+ ); } diff --git a/web/theme.css b/web/theme.css --- a/web/theme.css +++ b/web/theme.css @@ -103,8 +103,10 @@ --text-message-default-background: var(--shades-black-80); --message-action-tooltip-bg: var(--shades-black-90); --menu-bg: var(--shades-black-90); + --menu-bg-light: var(--shades-black-80); --menu-separator-color: var(--shades-black-80); --menu-color: var(--shades-black-60); + --menu-color-light: var(--shades-white-60); --menu-color-hover: var(--shades-white-100); --menu-color-dangerous: var(--error-primary); --menu-color-dangerous-hover: var(--error-light-50);