diff --git a/web/chat/thread-menu.css b/web/chat/thread-menu.css index 846eabccf..31dd25cac 100644 --- a/web/chat/thread-menu.css +++ b/web/chat/thread-menu.css @@ -1,51 +1,59 @@ button.topBarMenuButton { background-color: transparent; border: none; cursor: pointer; color: var(--thread-top-bar-menu-color); } div.topBarMenuActionList { position: absolute; right: 10px; top: 55px; z-index: 1; display: flex; flex-direction: column; background-color: var(--thread-menu-bg); border-radius: 4px; padding: 4px 0; } button.topBarMenuAction { z-index: 1; background-color: transparent; padding: 12px 16px; color: var(--thread-menu-color); background-color: var(--thread-menu-bg); font-size: var(--m-font-16); line-height: 1.5; border: none; cursor: pointer; display: flex; align-items: center; } button.topBarMenuAction:hover { color: var(--thread-menu-color-hover); } div.topBarMenuActionIcon { font-size: var(--l-font-18); display: flex; justify-content: center; margin-right: 8px; width: 20px; } button.topBarMenuActionDangerous { color: var(--thread-menu-color-dangerous); } button.topBarMenuActionDangerous:hover { color: var(--thread-menu-color-dangerous-hover); } + +hr.separator { + height: 1px; + background: var(--thread-menu-separator-color); + margin: 10px 16px; + max-width: 130px; + border: none; +} diff --git a/web/chat/thread-menu.react.js b/web/chat/thread-menu.react.js index 64c815b8e..b9021819b 100644 --- a/web/chat/thread-menu.react.js +++ b/web/chat/thread-menu.react.js @@ -1,156 +1,185 @@ // @flow import { faArrowRight, faBell, faCog, faCommentAlt, + faSignOutAlt, faPlusCircle, faUserFriends, } from '@fortawesome/free-solid-svg-icons'; import * as React from 'react'; import { childThreadInfos } from 'lib/selectors/thread-selectors'; -import { threadHasPermission } from 'lib/shared/thread-utils'; +import { threadHasPermission, viewerIsMember } from 'lib/shared/thread-utils'; import { type ThreadInfo, threadTypes, threadPermissions, } from 'lib/types/thread-types'; import { useSelector } from '../redux/redux-utils'; import SWMansionIcon from '../SWMansionIcon.react'; import ThreadMenuItem from './thread-menu-item.react'; import css from './thread-menu.css'; type ThreadMenuProps = { +threadInfo: ThreadInfo, }; function ThreadMenu(props: ThreadMenuProps): React.Node { const [isOpen, setIsOpen] = React.useState(false); const { threadInfo } = props; const membersItem = React.useMemo(() => { if (threadInfo.type === threadTypes.PERSONAL) { return null; } return ; }, [threadInfo.type]); const childThreads = useSelector( state => childThreadInfos(state)[threadInfo.id], ); const hasSidebars = React.useMemo(() => { return childThreads?.some( childThreadInfo => childThreadInfo.type === threadTypes.SIDEBAR, ); }, [childThreads]); const sidebarItem = React.useMemo(() => { if (!hasSidebars) { return null; } return ( ); }, [hasSidebars]); const canCreateSubchannels = React.useMemo( () => threadHasPermission(threadInfo, threadPermissions.CREATE_SUBCHANNELS), [threadInfo], ); const hasSubchannels = React.useMemo(() => { return childThreads?.some( childThreadInfo => childThreadInfo.type !== threadTypes.SIDEBAR, ); }, [childThreads]); const viewSubchannelsItem = React.useMemo(() => { if (!hasSubchannels && !canCreateSubchannels) { return null; } return ( ); }, [canCreateSubchannels, hasSubchannels]); const createSubchannelsItem = React.useMemo(() => { if (!canCreateSubchannels) { return null; } return ( ); }, [canCreateSubchannels]); + const leaveThreadItem = React.useMemo(() => { + const canLeaveThread = threadHasPermission( + threadInfo, + threadPermissions.LEAVE_THREAD, + ); + if (!viewerIsMember(threadInfo) || !canLeaveThread) { + return null; + } + return ( + + ); + }, [threadInfo]); + const menuItems = React.useMemo(() => { const settingsItem = ( ); const notificationsItem = ( ); + const separator =
; + const items = [ settingsItem, notificationsItem, membersItem, sidebarItem, viewSubchannelsItem, createSubchannelsItem, + leaveThreadItem && separator, + leaveThreadItem, ]; return items.filter(Boolean); - }, [membersItem, sidebarItem, viewSubchannelsItem, createSubchannelsItem]); + }, [ + membersItem, + sidebarItem, + viewSubchannelsItem, + createSubchannelsItem, + leaveThreadItem, + ]); const closeMenuCallback = React.useCallback(() => { document.removeEventListener('click', closeMenuCallback); if (isOpen) { setIsOpen(false); } }, [isOpen]); React.useEffect(() => { if (!document || !isOpen) { return undefined; } document.addEventListener('click', closeMenuCallback); return () => document.removeEventListener('click', closeMenuCallback); }, [closeMenuCallback, isOpen]); const switchMenuCallback = React.useCallback(() => { setIsOpen(isMenuOpen => !isMenuOpen); }, []); if (menuItems.length === 0) { return null; } let menuActionList = null; if (isOpen) { menuActionList = (
{menuItems}
); } return (
{menuActionList}
); } export default ThreadMenu;