diff --git a/web/chat/chat-thread-composer.css b/web/chat/chat-thread-composer.css index 0c38c9021..0c542b5f2 100644 --- a/web/chat/chat-thread-composer.css +++ b/web/chat/chat-thread-composer.css @@ -1,71 +1,78 @@ div.threadSearchContainer { background-color: var(--thread-creation-search-container-bg); color: var(--fg); display: flex; flex-direction: column; max-height: 50%; overflow: auto; flex-shrink: 0; } div.fullHeight { flex-grow: 1; max-height: 100%; } div.userSelectedTags { display: flex; flex-wrap: wrap; flex-direction: row; align-items: center; gap: 4px; padding: 4px 12px; margin-bottom: 8px; } div.searchRow { display: flex; flex-direction: row; align-items: center; margin-right: 8px; } div.searchField { flex-grow: 1; } .closeSearch { color: var(--thread-creation-close-search-color); margin: 0 8px; } ul.searchResultsContainer { display: flex; flex-direction: column; overflow: auto; padding: 0 12px 8px; list-style-type: none; } .searchResultsItem { display: flex; } .searchResultsItem:hover { background-color: var(--thread-creation-search-item-bg-hover); } .searchResultsButton { justify-content: space-between; flex: 1; padding: 8px 12px; } div.userName { color: var(--fg); + margin-left: 8px; } div.userInfo { font-style: italic; color: var(--thread-creation-search-item-info-color); } + +div.userContainer { + display: flex; + flex-direction: row; + align-items: center; +} diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js index bc0c2339d..2336f877d 100644 --- a/web/chat/chat-thread-composer.react.js +++ b/web/chat/chat-thread-composer.react.js @@ -1,186 +1,190 @@ // @flow import classNames from 'classnames'; import * as React from 'react'; import { useDispatch } from 'react-redux'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import { useENSNames } from 'lib/hooks/ens-cache.js'; import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { getPotentialMemberItems } from 'lib/shared/search-utils.js'; import { threadIsPending } from 'lib/shared/thread-utils.js'; import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js'; import css from './chat-thread-composer.css'; import Button from '../components/button.react.js'; import Label from '../components/label.react.js'; import Search from '../components/search.react.js'; +import UserAvatar from '../components/user-avatar.react.js'; import type { InputState } from '../input/input-state.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = { +userInfoInputArray: $ReadOnlyArray, +otherUserInfos: { [id: string]: AccountUserInfo }, +threadID: string, +inputState: InputState, }; type ActiveThreadBehavior = | 'reset-active-thread-if-pending' | 'keep-active-thread'; function ChatThreadComposer(props: Props): React.Node { const { userInfoInputArray, otherUserInfos, threadID, inputState } = props; const [usernameInputText, setUsernameInputText] = React.useState(''); const dispatch = useDispatch(); const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const userInfoInputIDs = React.useMemo( () => userInfoInputArray.map(userInfo => userInfo.id), [userInfoInputArray], ); const userListItems = React.useMemo( () => getPotentialMemberItems( usernameInputText, otherUserInfos, userSearchIndex, userInfoInputIDs, ), [usernameInputText, otherUserInfos, userSearchIndex, userInfoInputIDs], ); const userListItemsWithENSNames = useENSNames(userListItems); const onSelectUserFromSearch = React.useCallback( (id: string) => { const selectedUserIDs = userInfoInputArray.map(user => user.id); dispatch({ type: updateNavInfoActionType, payload: { selectedUserList: [...selectedUserIDs, id], }, }); setUsernameInputText(''); }, [dispatch, userInfoInputArray], ); const onRemoveUserFromSelected = React.useCallback( (id: string) => { const selectedUserIDs = userInfoInputArray.map(user => user.id); if (!selectedUserIDs.includes(id)) { return; } dispatch({ type: updateNavInfoActionType, payload: { selectedUserList: selectedUserIDs.filter(userID => userID !== id), }, }); }, [dispatch, userInfoInputArray], ); const userSearchResultList = React.useMemo(() => { if ( !userListItemsWithENSNames.length || (!usernameInputText && userInfoInputArray.length) ) { return null; } - return ( - + +
{userSearchResult.alertTitle}
+ + + ), ); + + return ; }, [ onSelectUserFromSearch, userInfoInputArray.length, userListItemsWithENSNames, usernameInputText, ]); const hideSearch = React.useCallback( (threadBehavior: ActiveThreadBehavior = 'keep-active-thread') => { dispatch({ type: updateNavInfoActionType, payload: { chatMode: 'view', activeChatThreadID: threadBehavior === 'keep-active-thread' || !threadIsPending(threadID) ? threadID : null, }, }); }, [dispatch, threadID], ); const onCloseSearch = React.useCallback(() => { hideSearch('reset-active-thread-if-pending'); }, [hideSearch]); const userInfoInputArrayWithENSNames = useENSNames(userInfoInputArray); const tagsList = React.useMemo(() => { if (!userInfoInputArrayWithENSNames?.length) { return null; } const labels = userInfoInputArrayWithENSNames.map(user => { return ( ); }); return
{labels}
; }, [userInfoInputArrayWithENSNames, onRemoveUserFromSelected]); React.useEffect(() => { if (!inputState) { return; } inputState.registerSendCallback(hideSearch); return () => inputState.unregisterSendCallback(hideSearch); }, [hideSearch, inputState]); const threadSearchContainerStyles = classNames(css.threadSearchContainer, { [css.fullHeight]: !userInfoInputArray.length, }); return (
{tagsList} {userSearchResultList}
); } export default ChatThreadComposer;