Bluesky app fork with some witchin' additions 💫

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).

authored by

Alex Benzer and committed by
GitHub
b38edc52 d68fc78e

+80 -18
+4 -4
src/lib/routes/types.ts
··· 67 InterestsSettings: undefined 68 AboutSettings: undefined 69 AppIconSettings: undefined 70 - Search: {q?: string} 71 Hashtag: {tag: string; author?: string} 72 Topic: {topic: string} 73 MessagesConversation: {conversation: string; embed?: string; accept?: true} ··· 102 } 103 104 export type SearchTabNavigatorParams = CommonNavigatorParams & { 105 - Search: {q?: string} 106 } 107 108 export type NotificationsTabNavigatorParams = CommonNavigatorParams & { ··· 119 120 export type FlatNavigatorParams = CommonNavigatorParams & { 121 Home: undefined 122 - Search: {q?: string} 123 Feeds: undefined 124 Notifications: undefined 125 Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} ··· 129 HomeTab: undefined 130 Home: undefined 131 SearchTab: undefined 132 - Search: {q?: string} 133 Feeds: undefined 134 NotificationsTab: undefined 135 Notifications: undefined
··· 67 InterestsSettings: undefined 68 AboutSettings: undefined 69 AppIconSettings: undefined 70 + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} 71 Hashtag: {tag: string; author?: string} 72 Topic: {topic: string} 73 MessagesConversation: {conversation: string; embed?: string; accept?: true} ··· 102 } 103 104 export type SearchTabNavigatorParams = CommonNavigatorParams & { 105 + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} 106 } 107 108 export type NotificationsTabNavigatorParams = CommonNavigatorParams & { ··· 119 120 export type FlatNavigatorParams = CommonNavigatorParams & { 121 Home: undefined 122 + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} 123 Feeds: undefined 124 Notifications: undefined 125 Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} ··· 129 HomeTab: undefined 130 Home: undefined 131 SearchTab: undefined 132 + Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} 133 Feeds: undefined 134 NotificationsTab: undefined 135 Notifications: undefined
+12 -2
src/screens/Search/Explore.tsx
··· 726 <ModuleHeader.SearchButton 727 {...item.searchButton} 728 onPress={() => 729 - focusSearchInput(item.searchButton?.tab || 'user') 730 } 731 /> 732 )} ··· 743 <ModuleHeader.SearchButton 744 {...item.searchButton} 745 onPress={() => 746 - focusSearchInput(item.searchButton?.tab || 'user') 747 } 748 /> 749 )}
··· 726 <ModuleHeader.SearchButton 727 {...item.searchButton} 728 onPress={() => 729 + focusSearchInput( 730 + (item.searchButton?.tab || 'user') as 731 + | 'user' 732 + | 'profile' 733 + | 'feed', 734 + ) 735 } 736 /> 737 )} ··· 748 <ModuleHeader.SearchButton 749 {...item.searchButton} 750 onPress={() => 751 + focusSearchInput( 752 + (item.searchButton?.tab || 'user') as 753 + | 'user' 754 + | 'profile' 755 + | 'feed', 756 + ) 757 } 758 /> 759 )}
+3 -1
src/screens/Search/SearchResults.tsx
··· 30 activeTab, 31 onPageSelected, 32 headerHeight, 33 }: { 34 query: string 35 queryWithParams: string 36 activeTab: number 37 onPageSelected: (page: number) => void 38 headerHeight: number 39 }): React.ReactNode => { 40 const {_} = useLingui() 41 ··· 89 <TabBar items={sections.map(section => section.title)} {...props} /> 90 </Layout.Center> 91 )} 92 - initialPage={0}> 93 {sections.map((section, i) => ( 94 <View key={i}>{section.component}</View> 95 ))}
··· 30 activeTab, 31 onPageSelected, 32 headerHeight, 33 + initialPage = 0, 34 }: { 35 query: string 36 queryWithParams: string 37 activeTab: number 38 onPageSelected: (page: number) => void 39 headerHeight: number 40 + initialPage?: number 41 }): React.ReactNode => { 42 const {_} = useLingui() 43 ··· 91 <TabBar items={sections.map(section => section.title)} {...props} /> 92 </Layout.Center> 93 )} 94 + initialPage={initialPage}> 95 {sections.map((section, i) => ( 96 <View key={i}>{section.component}</View> 97 ))}
+61 -11
src/screens/Search/Shell.tsx
··· 190 setShowAutocomplete(false) 191 if (isWeb) { 192 // Empty params resets the URL to be /search rather than /search?q= 193 - 194 - const {q: _q, ...parameters} = (route.params ?? {}) as { 195 [key: string]: string 196 } 197 // @ts-expect-error route is not typesafe 198 navigation.replace(route.name, parameters) 199 } else { 200 setSearchText('') 201 - navigation.setParams({q: ''}) 202 } 203 }, [setShowAutocomplete, setSearchText, navigation, route.params, route.name]) 204 ··· 236 const onSoftReset = useCallback(() => { 237 if (isWeb) { 238 // Empty params resets the URL to be /search rather than /search?q= 239 - 240 - const {q: _q, ...parameters} = (route.params ?? {}) as { 241 [key: string]: string 242 } 243 // @ts-expect-error route is not typesafe 244 navigation.replace(route.name, parameters) 245 } else { 246 setSearchText('') 247 - navigation.setParams({q: ''}) 248 textInput.current?.focus() 249 } 250 }, [navigation, route]) ··· 268 } 269 }, [setShowAutocomplete]) 270 271 - const focusSearchInput = useCallback(() => { 272 - textInput.current?.focus() 273 - }, []) 274 275 const showHeader = !gtMobile || navButton !== 'menu' 276 ··· 421 query: string 422 queryWithParams: string 423 headerHeight: number 424 - focusSearchInput: () => void 425 }): React.ReactNode => { 426 const t = useTheme() 427 const setMinimalShellMode = useSetMinimalShellMode() 428 const {hasSession} = useSession() 429 const {gtTablet} = useBreakpoints() 430 - const [activeTab, setActiveTab] = useState(0) 431 432 const onPageSelected = useCallback( 433 (index: number) => { ··· 444 activeTab={activeTab} 445 headerHeight={headerHeight} 446 onPageSelected={onPageSelected} 447 /> 448 ) : hasSession ? ( 449 <Explore focusSearchInput={focusSearchInput} headerHeight={headerHeight} />
··· 190 setShowAutocomplete(false) 191 if (isWeb) { 192 // Empty params resets the URL to be /search rather than /search?q= 193 + // Also clear the tab parameter 194 + const { 195 + q: _q, 196 + tab: _tab, 197 + ...parameters 198 + } = (route.params ?? {}) as { 199 [key: string]: string 200 } 201 // @ts-expect-error route is not typesafe 202 navigation.replace(route.name, parameters) 203 } else { 204 setSearchText('') 205 + navigation.setParams({q: '', tab: undefined}) 206 } 207 }, [setShowAutocomplete, setSearchText, navigation, route.params, route.name]) 208 ··· 240 const onSoftReset = useCallback(() => { 241 if (isWeb) { 242 // Empty params resets the URL to be /search rather than /search?q= 243 + // Also clear the tab parameter when soft resetting 244 + const { 245 + q: _q, 246 + tab: _tab, 247 + ...parameters 248 + } = (route.params ?? {}) as { 249 [key: string]: string 250 } 251 // @ts-expect-error route is not typesafe 252 navigation.replace(route.name, parameters) 253 } else { 254 setSearchText('') 255 + navigation.setParams({q: '', tab: undefined}) 256 textInput.current?.focus() 257 } 258 }, [navigation, route]) ··· 276 } 277 }, [setShowAutocomplete]) 278 279 + const focusSearchInput = useCallback( 280 + (tab?: 'user' | 'profile' | 'feed') => { 281 + textInput.current?.focus() 282 + 283 + // If a tab is specified, set the tab parameter 284 + if (tab) { 285 + if (isWeb) { 286 + navigation.setParams({...route.params, tab}) 287 + } else { 288 + navigation.setParams({tab}) 289 + } 290 + } 291 + }, 292 + [navigation, route], 293 + ) 294 295 const showHeader = !gtMobile || navButton !== 'menu' 296 ··· 441 query: string 442 queryWithParams: string 443 headerHeight: number 444 + focusSearchInput: (tab?: 'user' | 'profile' | 'feed') => void 445 }): React.ReactNode => { 446 const t = useTheme() 447 const setMinimalShellMode = useSetMinimalShellMode() 448 const {hasSession} = useSession() 449 const {gtTablet} = useBreakpoints() 450 + const route = useRoute() 451 + 452 + // Get tab parameter from route params 453 + const tabParam = ( 454 + route.params as {q?: string; tab?: 'user' | 'profile' | 'feed'} 455 + )?.tab 456 + 457 + // Map tab parameter to tab index 458 + const getInitialTabIndex = useCallback(() => { 459 + if (!tabParam) return 0 460 + switch (tabParam) { 461 + case 'user': 462 + case 'profile': 463 + return 2 // People tab 464 + case 'feed': 465 + return 3 // Feeds tab 466 + default: 467 + return 0 468 + } 469 + }, [tabParam]) 470 + 471 + const [activeTab, setActiveTab] = useState(getInitialTabIndex()) 472 + 473 + // Update activeTab when tabParam changes 474 + useLayoutEffect(() => { 475 + const newTabIndex = getInitialTabIndex() 476 + if (newTabIndex !== activeTab) { 477 + setActiveTab(newTabIndex) 478 + } 479 + }, [tabParam, activeTab, getInitialTabIndex]) 480 481 const onPageSelected = useCallback( 482 (index: number) => { ··· 493 activeTab={activeTab} 494 headerHeight={headerHeight} 495 onPageSelected={onPageSelected} 496 + initialPage={activeTab} 497 /> 498 ) : hasSession ? ( 499 <Explore focusSearchInput={focusSearchInput} headerHeight={headerHeight} />