diff --git a/app/soapbox/components/status_list.tsx b/app/soapbox/components/status_list.tsx index 6eac9c7b2..7a86778eb 100644 --- a/app/soapbox/components/status_list.tsx +++ b/app/soapbox/components/status_list.tsx @@ -86,7 +86,7 @@ const StatusList: React.FC = ({ index, behavior: 'smooth', done: () => { - const element: HTMLElement | null = document.querySelector(`#status-list [data-index="${index}"] .focusable`); + const element = document.querySelector(`#status-list [data-index="${index}"] .focusable`); element?.focus(); }, }); diff --git a/app/soapbox/features/compose/components/search_results.tsx b/app/soapbox/features/compose/components/search_results.tsx index 15f607732..45c92c472 100644 --- a/app/soapbox/features/compose/components/search_results.tsx +++ b/app/soapbox/features/compose/components/search_results.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { expandSearch, setFilter } from 'soapbox/actions/search'; @@ -14,6 +14,8 @@ import PlaceholderHashtag from 'soapbox/features/placeholder/components/placehol import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; +import type { VirtuosoHandle } from 'react-virtuoso'; import type { SearchFilter } from 'soapbox/reducers/search'; const messages = defineMessages({ @@ -23,6 +25,8 @@ const messages = defineMessages({ }); const SearchResults = () => { + const node = useRef(null); + const intl = useIntl(); const dispatch = useAppDispatch(); @@ -60,6 +64,35 @@ const SearchResults = () => { return ; }; + const getCurrentIndex = (id: string): number => { + return resultsIds?.keySeq().findIndex(key => key === id); + }; + + const handleMoveUp = (id: string) => { + if (!resultsIds) return; + + const elementIndex = getCurrentIndex(id) - 1; + selectChild(elementIndex); + }; + + const handleMoveDown = (id: string) => { + if (!resultsIds) return; + + const elementIndex = getCurrentIndex(id) + 1; + selectChild(elementIndex); + }; + + const selectChild = (index: number) => { + node.current?.scrollIntoView({ + index, + behavior: 'smooth', + done: () => { + const element = document.querySelector(`#search-results [data-index="${index}"] .focusable`); + element?.focus(); + }, + }); + }; + useEffect(() => { dispatch(fetchTrendingStatuses()); }, []); @@ -69,6 +102,7 @@ const SearchResults = () => { let loaded; let noResultsMessage; let placeholderComponent = PlaceholderStatus as React.ComponentType; + let resultsIds: ImmutableOrderedSet; if (selectedFilter === 'accounts') { hasMore = results.accountsHasMore; @@ -99,13 +133,25 @@ const SearchResults = () => { if (results.statuses && results.statuses.size > 0) { searchResults = results.statuses.map((statusId: string) => ( // @ts-ignore - + )); + resultsIds = results.statuses; } else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) { searchResults = trendingStatuses.map((statusId: string) => ( // @ts-ignore - + )); + resultsIds = trendingStatuses; } else if (loaded) { noResultsMessage = (
@@ -147,6 +193,8 @@ const SearchResults = () => { {noResultsMessage || (