diff --git a/web/modals/threads/members/add-members-modal.react.js b/web/modals/threads/members/add-members-modal.react.js index 3a76f1550..a170ea06c 100644 --- a/web/modals/threads/members/add-members-modal.react.js +++ b/web/modals/threads/members/add-members-modal.react.js @@ -1,167 +1,195 @@ // @flow import * as React from 'react'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from 'lib/actions/thread-actions'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { userSearchIndexForPotentialMembers, userInfoSelectorForPotentialMembers, } from 'lib/selectors/user-selectors'; import { getPotentialMemberItems } from 'lib/shared/search-utils'; import { threadActualMembers } from 'lib/shared/thread-utils'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils'; import Button from '../../../components/button.react'; +import Label from '../../../components/label.react'; import { useSelector } from '../../../redux/redux-utils'; import SearchModal from '../../search-modal.react'; import AddMembersList from './add-members-list.react'; import css from './members-modal.css'; type ContentProps = { +searchText: string, +threadID: string, +onClose: () => void, }; function AddMembersModalContent(props: ContentProps): React.Node { const { searchText, threadID, onClose } = props; const [pendingUsersToAdd, setPendingUsersToAdd] = React.useState< $ReadOnlySet, >(new Set()); const threadInfo = useSelector(state => threadInfoSelector(state)[threadID]); const { parentThreadID, community } = threadInfo; const parentThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, ); const communityThreadInfo = useSelector(state => community ? threadInfoSelector(state)[community] : null, ); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const excludeUserIDs = React.useMemo( - () => threadActualMembers(threadInfo.members), - [threadInfo.members], + () => + threadActualMembers(threadInfo.members).concat( + Array.from(pendingUsersToAdd), + ), + [pendingUsersToAdd, threadInfo.members], ); const userSearchResults = React.useMemo( () => getPotentialMemberItems( searchText, otherUserInfos, userSearchIndex, excludeUserIDs, parentThreadInfo, communityThreadInfo, threadInfo.type, ), [ communityThreadInfo, excludeUserIDs, otherUserInfos, parentThreadInfo, searchText, threadInfo.type, userSearchIndex, ], ); const onSwitchUser = React.useCallback( userID => setPendingUsersToAdd(users => { const newUsers = new Set(users); if (newUsers.has(userID)) { newUsers.delete(userID); } else { newUsers.add(userID); } return newUsers; }), [], ); const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadSettings = useServerCall(changeThreadSettings); const addUsers = React.useCallback(() => { dispatchActionPromise( changeThreadSettingsActionTypes, callChangeThreadSettings({ threadID, changes: { newMemberIDs: Array.from(pendingUsersToAdd) }, }), ); onClose(); }, [ callChangeThreadSettings, dispatchActionPromise, onClose, pendingUsersToAdd, threadID, ]); + const pendingUsersWithNames = React.useMemo( + () => + Array.from(pendingUsersToAdd) + .map(userID => [userID, otherUserInfos[userID].username]) + .sort((a, b) => a[1].localeCompare(b[1])), + [otherUserInfos, pendingUsersToAdd], + ); + + const labelItems = React.useMemo(() => { + if (!pendingUsersWithNames.length) { + return null; + } + return ( +
+ {pendingUsersWithNames.map(([userID, username]) => ( + + ))} +
+ ); + }, [onSwitchUser, pendingUsersWithNames]); + return (
+ {labelItems}
); } type Props = { +threadID: string, +onClose: () => void, }; function AddMembersModal(props: Props): React.Node { const { threadID, onClose } = props; const addMembersModalContent = React.useCallback( (searchText: string) => ( ), [onClose, threadID], ); return ( {addMembersModalContent} ); } export default AddMembersModal; diff --git a/web/modals/threads/members/members-modal.css b/web/modals/threads/members/members-modal.css index 394566adc..19714be4a 100644 --- a/web/modals/threads/members/members-modal.css +++ b/web/modals/threads/members/members-modal.css @@ -1,127 +1,135 @@ div.modalContentContainer { width: 383px; height: 617px; overflow: hidden; display: flex; flex-direction: column; row-gap: 16px; } div.membersListTabs { flex: 1; overflow: hidden; } div.addNewMembers button { width: 100%; } div.membersList { overflow: auto; padding: 8px 0; color: var(--members-modal-member-text); } div.noScroll { overflow: hidden; } div.memberContainer { display: flex; flex-direction: row; justify-content: space-between; padding: 8px 16px; } div.memberContainer:hover { color: var(--members-modal-member-text-hover); } div.memberContainerWithMenuOpen { color: var(--members-modal-member-text-hover); } div.memberInfo { font-size: var(--l-font-18); display: flex; align-items: center; gap: 10px; } div.memberAction { position: relative; } h5.memberletterHeader { margin: 16px; color: var(--members-modal-member-text); font-size: var(--s-font-14); } div.noUsers { padding-top: 16px; text-align: center; } div.addMembersContent { display: flex; flex-direction: column; overflow: auto; color: var(--fg); - row-gap: 16px; width: 383px; height: 617px; } +div.addMembersPendingList { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 6px; + padding: 8px; +} + div.addMembersListContainer { overflow: auto; flex: 1; } div.addMembersFooter { display: flex; justify-content: end; column-gap: 16px; + margin-top: 16px; } div.addMemberItemsGroupHeader { font-size: var(--s-font-14); color: var(--add-members-group-header-color); margin: 16px; } button.addMemberItem { display: flex; flex-direction: row; justify-content: space-between; align-items: center; color: var(--add-members-item-color); font-size: var(--l-font-18); background-color: transparent; border: none; width: 100%; } button.addMemberItem:hover { color: var(--add-members-item-color-hover); } button.addMemberItem:disabled { color: var(--add-members-item-disabled-color); cursor: not-allowed; } button.addMemberItem:hover:disabled { color: var(--add-members-item-disabled-color-hover); } button.addMemberItem .label { padding: 8px 16px; } button.addMemberItem .danger { color: var(--add-members-remove-pending-color); } button.addMemberItem:hover .danger { color: var(--add-members-remove-pending-color-hover); }