From b38edc52b8ea7a48eaa8cd66ac613c15f9c6d6c4 Mon Sep 17 00:00:00 2001 From: Alex Benzer Date: Thu, 9 Oct 2025 05:02:38 -0700 Subject: [PATCH] Auto-select search results tab (#9159) When the user clicks the search button next to "Discover New Feeds" or "Suggested Accounts" on the Explore page, we'll auto-select the respective search results tab (feeds or users). --- src/lib/routes/types.ts | 8 ++-- src/screens/Search/Explore.tsx | 14 +++++- src/screens/Search/SearchResults.tsx | 4 +- src/screens/Search/Shell.tsx | 72 +++++++++++++++++++++++----- 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 4f7054cb3f..c315a83418 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -67,7 +67,7 @@ export type CommonNavigatorParams = { InterestsSettings: undefined AboutSettings: undefined AppIconSettings: undefined - Search: {q?: string} + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} Hashtag: {tag: string; author?: string} Topic: {topic: string} MessagesConversation: {conversation: string; embed?: string; accept?: true} @@ -102,7 +102,7 @@ export type HomeTabNavigatorParams = CommonNavigatorParams & { } export type SearchTabNavigatorParams = CommonNavigatorParams & { - Search: {q?: string} + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} } export type NotificationsTabNavigatorParams = CommonNavigatorParams & { @@ -119,7 +119,7 @@ export type MessagesTabNavigatorParams = CommonNavigatorParams & { export type FlatNavigatorParams = CommonNavigatorParams & { Home: undefined - Search: {q?: string} + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} Feeds: undefined Notifications: undefined Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} @@ -129,7 +129,7 @@ export type AllNavigatorParams = CommonNavigatorParams & { HomeTab: undefined Home: undefined SearchTab: undefined - Search: {q?: string} + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} Feeds: undefined NotificationsTab: undefined Notifications: undefined diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index c670a32dce..e327254b19 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -726,7 +726,12 @@ export function Explore({ - focusSearchInput(item.searchButton?.tab || 'user') + focusSearchInput( + (item.searchButton?.tab || 'user') as + | 'user' + | 'profile' + | 'feed', + ) } /> )} @@ -743,7 +748,12 @@ export function Explore({ - focusSearchInput(item.searchButton?.tab || 'user') + focusSearchInput( + (item.searchButton?.tab || 'user') as + | 'user' + | 'profile' + | 'feed', + ) } /> )} diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx index 4c40684176..fcee3ab863 100644 --- a/src/screens/Search/SearchResults.tsx +++ b/src/screens/Search/SearchResults.tsx @@ -30,12 +30,14 @@ let SearchResults = ({ activeTab, onPageSelected, headerHeight, + initialPage = 0, }: { query: string queryWithParams: string activeTab: number onPageSelected: (page: number) => void headerHeight: number + initialPage?: number }): React.ReactNode => { const {_} = useLingui() @@ -89,7 +91,7 @@ let SearchResults = ({ section.title)} {...props} /> )} - initialPage={0}> + initialPage={initialPage}> {sections.map((section, i) => ( {section.component} ))} diff --git a/src/screens/Search/Shell.tsx b/src/screens/Search/Shell.tsx index 12b0226e13..1293b58884 100644 --- a/src/screens/Search/Shell.tsx +++ b/src/screens/Search/Shell.tsx @@ -190,15 +190,19 @@ export function SearchScreenShell({ setShowAutocomplete(false) if (isWeb) { // Empty params resets the URL to be /search rather than /search?q= - - const {q: _q, ...parameters} = (route.params ?? {}) as { + // Also clear the tab parameter + const { + q: _q, + tab: _tab, + ...parameters + } = (route.params ?? {}) as { [key: string]: string } // @ts-expect-error route is not typesafe navigation.replace(route.name, parameters) } else { setSearchText('') - navigation.setParams({q: ''}) + navigation.setParams({q: '', tab: undefined}) } }, [setShowAutocomplete, setSearchText, navigation, route.params, route.name]) @@ -236,15 +240,19 @@ export function SearchScreenShell({ const onSoftReset = useCallback(() => { if (isWeb) { // Empty params resets the URL to be /search rather than /search?q= - - const {q: _q, ...parameters} = (route.params ?? {}) as { + // Also clear the tab parameter when soft resetting + const { + q: _q, + tab: _tab, + ...parameters + } = (route.params ?? {}) as { [key: string]: string } // @ts-expect-error route is not typesafe navigation.replace(route.name, parameters) } else { setSearchText('') - navigation.setParams({q: ''}) + navigation.setParams({q: '', tab: undefined}) textInput.current?.focus() } }, [navigation, route]) @@ -268,9 +276,21 @@ export function SearchScreenShell({ } }, [setShowAutocomplete]) - const focusSearchInput = useCallback(() => { - textInput.current?.focus() - }, []) + const focusSearchInput = useCallback( + (tab?: 'user' | 'profile' | 'feed') => { + textInput.current?.focus() + + // If a tab is specified, set the tab parameter + if (tab) { + if (isWeb) { + navigation.setParams({...route.params, tab}) + } else { + navigation.setParams({tab}) + } + } + }, + [navigation, route], + ) const showHeader = !gtMobile || navButton !== 'menu' @@ -421,13 +441,42 @@ let SearchScreenInner = ({ query: string queryWithParams: string headerHeight: number - focusSearchInput: () => void + focusSearchInput: (tab?: 'user' | 'profile' | 'feed') => void }): React.ReactNode => { const t = useTheme() const setMinimalShellMode = useSetMinimalShellMode() const {hasSession} = useSession() const {gtTablet} = useBreakpoints() - const [activeTab, setActiveTab] = useState(0) + const route = useRoute() + + // Get tab parameter from route params + const tabParam = ( + route.params as {q?: string; tab?: 'user' | 'profile' | 'feed'} + )?.tab + + // Map tab parameter to tab index + const getInitialTabIndex = useCallback(() => { + if (!tabParam) return 0 + switch (tabParam) { + case 'user': + case 'profile': + return 2 // People tab + case 'feed': + return 3 // Feeds tab + default: + return 0 + } + }, [tabParam]) + + const [activeTab, setActiveTab] = useState(getInitialTabIndex()) + + // Update activeTab when tabParam changes + useLayoutEffect(() => { + const newTabIndex = getInitialTabIndex() + if (newTabIndex !== activeTab) { + setActiveTab(newTabIndex) + } + }, [tabParam, activeTab, getInitialTabIndex]) const onPageSelected = useCallback( (index: number) => { @@ -444,6 +493,7 @@ let SearchScreenInner = ({ activeTab={activeTab} headerHeight={headerHeight} onPageSelected={onPageSelected} + initialPage={activeTab} /> ) : hasSession ? (