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 (
{threadPickerContent}
); } export default ThreadPickerModal;