Page MenuHomePhabricator

D4461.id14716.diff
No OneTemporary

D4461.id14716.diff

diff --git a/web/chat/chat-thread-composer.css b/web/chat/chat-thread-composer.css
new file mode 100644
--- /dev/null
+++ b/web/chat/chat-thread-composer.css
@@ -0,0 +1,72 @@
+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;
+}
+
+div.closeSearch {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ color: var(--thread-creation-close-search-color);
+ margin: 0 8px;
+}
+
+ul.searchResultsContainer {
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ padding: 0 12px 8px;
+}
+
+li.searchResultsItem {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ cursor: pointer;
+}
+
+li.searchResultsItem:hover {
+ background-color: var(--thread-creation-search-item-bg-hover);
+}
+
+div.userName {
+ color: var(--fg);
+}
+
+div.userInfo {
+ font-style: italic;
+ color: var(--thread-creation-search-item-info-color);
+}
diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js
new file mode 100644
--- /dev/null
+++ b/web/chat/chat-thread-composer.react.js
@@ -0,0 +1,124 @@
+// @flow
+import classNames from 'classnames';
+import * as React from 'react';
+
+import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors';
+import { getPotentialMemberItems } from 'lib/shared/search-utils';
+import type { AccountUserInfo, UserListItem } from 'lib/types/user-types';
+
+import Label from '../components/label.react';
+import Search from '../components/search.react';
+import type { InputState } from '../input/input-state';
+import { useSelector } from '../redux/redux-utils';
+import SWMansionIcon from '../SWMansionIcon.react';
+import css from './chat-thread-composer.css';
+
+type Props = {
+ +userInfoInputArray: $ReadOnlyArray<AccountUserInfo>,
+ +otherUserInfos: { [id: string]: AccountUserInfo },
+ +threadID: string,
+ +inputState: InputState,
+};
+
+function ChatThreadComposer(props: Props): React.Node {
+ const { userInfoInputArray, otherUserInfos } = props;
+
+ const [usernameInputText, setUsernameInputText] = React.useState('');
+
+ 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],
+ );
+
+ // eslint-disable-next-line no-unused-vars
+ const onSelectUserFromSearch = React.useCallback((id: string) => {}, []);
+ // eslint-disable-next-line no-unused-vars
+ const onRemoveUserFromSelected = React.useCallback((id: string) => {}, []);
+
+ const userSearchResultList = React.useMemo(() => {
+ if (
+ !userListItems.length ||
+ (!usernameInputText && userInfoInputArray.length)
+ ) {
+ return null;
+ }
+
+ return (
+ <ul className={css.searchResultsContainer}>
+ {userListItems.map((userSearchResult: UserListItem) => (
+ <li
+ className={css.searchResultsItem}
+ key={userSearchResult.id}
+ onClick={() => onSelectUserFromSearch(userSearchResult.id)}
+ >
+ <div className={css.userName}>{userSearchResult.username}</div>
+ <div className={css.userInfo}>{userSearchResult.alertTitle}</div>
+ </li>
+ ))}
+ </ul>
+ );
+ }, [
+ onSelectUserFromSearch,
+ userInfoInputArray.length,
+ userListItems,
+ usernameInputText,
+ ]);
+
+ const hideSearch = React.useCallback(() => {}, []);
+
+ const tagsList = React.useMemo(() => {
+ if (!userInfoInputArray?.length) {
+ return null;
+ }
+ const labels = userInfoInputArray.map(user => {
+ return (
+ <Label key={user.id} onClose={() => onRemoveUserFromSelected(user.id)}>
+ {user.username}
+ </Label>
+ );
+ });
+ return <div className={css.userSelectedTags}>{labels}</div>;
+ }, [userInfoInputArray, onRemoveUserFromSelected]);
+
+ const threadSearchContainerStyles = React.useMemo(
+ () =>
+ classNames(css.threadSearchContainer, {
+ [css.fullHeight]: !userInfoInputArray.length,
+ }),
+ [userInfoInputArray.length],
+ );
+
+ return (
+ <div className={threadSearchContainerStyles}>
+ <div className={css.searchRow}>
+ <div className={css.searchField}>
+ <Search
+ onChangeText={setUsernameInputText}
+ searchText={usernameInputText}
+ placeholder="Select users for chat"
+ />
+ </div>
+ <div className={css.closeSearch} onClick={hideSearch}>
+ <SWMansionIcon size={25} icon="cross" />
+ </div>
+ </div>
+ {tagsList}
+ {userSearchResultList}
+ </div>
+ );
+}
+
+export default ChatThreadComposer;
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -169,4 +169,8 @@
--notification-settings-option-invalid-selected-color: var(--shades-black-60);
--danger-zone-subheading-color: var(--shades-white-60);
--danger-zone-explanation-color: var(--shades-black-60);
+ --thread-creation-search-container-bg: var(--shades-black-90);
+ --thread-creation-close-search-color: var(--shades-black-60);
+ --thread-creation-search-item-bg-hover: var(--shades-black-80);
+ --thread-creation-search-item-info-color: var(--shades-black-60);
}

File Metadata

Mime Type
text/plain
Expires
Tue, Nov 26, 3:23 PM (21 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2585344
Default Alt Text
D4461.id14716.diff (5 KB)

Event Timeline