diff --git a/web/components/search.react.js b/web/components/search.react.js
index 697a3426c..220f9a8b7 100644
--- a/web/components/search.react.js
+++ b/web/components/search.react.js
@@ -1,48 +1,54 @@
// @flow
import * as React from 'react';
import SWMansionIcon from '../SWMansionIcon.react';
import ClearSearchButton from './clear-search-button.react';
import css from './search.css';
type Props = {
+searchText: string,
+onChangeText: (searchText: string) => mixed,
+placeholder?: string,
};
-function Search(props: Props): React.Node {
+function Search(props: Props, ref): React.Node {
const { searchText, onChangeText, placeholder } = props;
const showClearButton = !!searchText;
const onClear = React.useCallback(() => {
onChangeText('');
}, [onChangeText]);
const onChange = React.useCallback(
event => {
onChangeText(event.target.value);
},
[onChangeText],
);
return (
);
}
-export default Search;
+const ForwardedSearch: React.AbstractComponent<
+ Props,
+ HTMLInputElement,
+> = React.forwardRef(Search);
+
+export default ForwardedSearch;
diff --git a/web/modals/threads/thread-picker-modal.react.js b/web/modals/threads/thread-picker-modal.react.js
index 36a8b67de..9201c8cda 100644
--- a/web/modals/threads/thread-picker-modal.react.js
+++ b/web/modals/threads/thread-picker-modal.react.js
@@ -1,136 +1,143 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { createSelector } from 'reselect';
import { threadSearchIndex } from 'lib/selectors/nav-selectors';
import { onScreenEntryEditableThreadInfos } from 'lib/selectors/thread-selectors';
import type { ThreadInfo } from 'lib/types/thread-types';
import Button from '../../components/button.react';
import Search from '../../components/search.react';
import { useSelector } from '../../redux/redux-utils';
import Modal, { type ModalOverridableProps } from '../modal.react';
import css from './thread-picker-modal.css';
type OptionProps = {
+threadInfo: ThreadInfo,
+createNewEntry: (threadID: string) => void,
+onCloseModal: () => void,
};
function ThreadPickerOption(props: OptionProps) {
const { threadInfo, createNewEntry, onCloseModal } = props;
const onClickThreadOption = React.useCallback(() => {
createNewEntry(threadInfo.id);
onCloseModal();
}, [threadInfo.id, createNewEntry, onCloseModal]);
const splotchColorStyle = React.useMemo(
() => ({
backgroundColor: `#${threadInfo.color}`,
}),
[threadInfo.color],
);
return (
);
}
type Props = {
...ModalOverridableProps,
+createNewEntry: (threadID: string) => void,
};
function ThreadPickerModal(props: Props): React.Node {
const { createNewEntry, ...modalProps } = props;
const onScreenThreadInfos = useSelector(onScreenEntryEditableThreadInfos);
const searchIndex = useSelector(state => threadSearchIndex(state));
invariant(
onScreenThreadInfos.length > 0,
"ThreadPicker can't be open when onScreenThreadInfos is empty",
);
const [searchText, setSearchText] = React.useState('');
const [searchResults, setSearchResults] = React.useState>(
new Set(),
);
+ const searchRef = React.useRef();
+
+ React.useEffect(() => {
+ searchRef.current?.focus();
+ }, []);
+
const onChangeSearchText = React.useCallback(
(text: string) => {
const results = searchIndex.getSearchResults(text);
setSearchText(text);
setSearchResults(new Set(results));
},
[searchIndex],
);
const listDataSelector = createSelector(
state => state.onScreenThreadInfos,
state => state.searchText,
state => state.searchResults,
(
threadInfos: $ReadOnlyArray,
text: string,
results: Set,
) =>
text
? threadInfos.filter(threadInfo => results.has(threadInfo.id))
: [...threadInfos],
);
const threads = useSelector(() =>
listDataSelector({
onScreenThreadInfos,
searchText,
searchResults,
}),
);
const threadPickerContent = React.useMemo(() => {
const options = threads.map(threadInfo => (
));
if (options.length === 0 && searchText.length > 0) {
return (
No results for {searchText}
);
} else {
return options;
}
}, [threads, createNewEntry, modalProps.onClose, searchText]);
return (
);
}
export default ThreadPickerModal;