Bluesky app fork with some witchin' additions 💫

Modernize search page (take 2) (#7642)

authored by samuel.fm and committed by

GitHub 5c14f695 66c09d99

+55 -138
+55 -138
src/view/screens/Search/Search.tsx
··· 1 1 import React, {useCallback, useLayoutEffect, useMemo} from 'react' 2 2 import { 3 3 ActivityIndicator, 4 - Image, 5 - ImageStyle, 6 4 Pressable, 7 5 StyleProp, 8 6 StyleSheet, 9 7 TextInput, 10 8 View, 9 + ViewStyle, 11 10 } from 'react-native' 12 11 import {ScrollView as RNGHScrollView} from 'react-native-gesture-handler' 13 12 import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api' 14 - import { 15 - FontAwesomeIcon, 16 - FontAwesomeIconStyle, 17 - } from '@fortawesome/react-native-fontawesome' 18 13 import {msg, Trans} from '@lingui/macro' 19 14 import {useLingui} from '@lingui/react' 20 15 import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native' ··· 24 19 import {createHitslop, HITSLOP_20} from '#/lib/constants' 25 20 import {HITSLOP_10} from '#/lib/constants' 26 21 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 27 - import {usePalette} from '#/lib/hooks/usePalette' 28 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 29 22 import {MagnifyingGlassIcon} from '#/lib/icons' 30 23 import {makeProfileLink} from '#/lib/routes/links' 31 24 import {NavigationProp} from '#/lib/routes/types' ··· 56 49 import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' 57 50 import {Link} from '#/view/com/util/Link' 58 51 import {List} from '#/view/com/util/List' 59 - import {Text} from '#/view/com/util/text/Text' 52 + import {UserAvatar} from '#/view/com/util/UserAvatar' 60 53 import {Explore} from '#/view/screens/Search/Explore' 61 54 import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search' 62 55 import {makeSearchQuery, Params, parseSearchQuery} from '#/screens/Search/utils' ··· 77 70 ChevronTopBottom_Stroke2_Corner0_Rounded as ChevronUpDownIcon, 78 71 } from '#/components/icons/Chevron' 79 72 import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Globe' 73 + import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 80 74 import * as Layout from '#/components/Layout' 81 75 import * as Menu from '#/components/Menu' 76 + import {Text} from '#/components/Typography' 82 77 import {account, useStorage} from '#/storage' 83 78 import * as bsky from '#/types/bsky' 84 79 ··· 93 88 } 94 89 95 90 function EmptyState({message, error}: {message: string; error?: string}) { 96 - const pal = usePalette('default') 91 + const t = useTheme() 97 92 98 93 return ( 99 94 <Layout.Content> 100 95 <View style={[a.p_xl]}> 101 - <View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}> 102 - <Text style={[pal.text]}>{message}</Text> 96 + <View style={[t.atoms.bg_contrast_25, a.rounded_sm, a.p_lg]}> 97 + <Text style={[a.text_md]}>{message}</Text> 103 98 104 99 {error && ( 105 100 <> ··· 109 104 marginVertical: 12, 110 105 height: 1, 111 106 width: '100%', 112 - backgroundColor: pal.text.color, 107 + backgroundColor: t.atoms.text.color, 113 108 opacity: 0.2, 114 109 }, 115 110 ]} 116 111 /> 117 112 118 - <Text style={[pal.textLight]}> 113 + <Text style={[t.atoms.text_contrast_medium]}> 119 114 <Trans>Error:</Trans> {error} 120 115 </Text> 121 116 </> ··· 478 473 queryWithParams: string 479 474 headerHeight: number 480 475 }): React.ReactNode => { 481 - const pal = usePalette('default') 476 + const t = useTheme() 482 477 const setMinimalShellMode = useSetMinimalShellMode() 483 478 const {hasSession} = useSession() 484 - const {isDesktop} = useWebMediaQueries() 479 + const {gtTablet} = useBreakpoints() 485 480 const [activeTab, setActiveTab] = React.useState(0) 486 481 const {_} = useLingui() 487 482 ··· 552 547 <Explore /> 553 548 ) : ( 554 549 <Layout.Center> 555 - <View style={web({height: '100vh'})}> 556 - {isDesktop && ( 557 - <Text 558 - type="title" 550 + <View style={a.flex_1}> 551 + {gtTablet && ( 552 + <View 559 553 style={[ 560 - pal.text, 561 - pal.border, 562 - { 563 - display: 'flex', 564 - paddingVertical: 12, 565 - paddingHorizontal: 18, 566 - fontWeight: '600', 567 - borderBottomWidth: 1, 568 - }, 554 + a.border_b, 555 + t.atoms.border_contrast_low, 556 + a.px_lg, 557 + a.pt_sm, 558 + a.pb_lg, 569 559 ]}> 570 - <Trans>Search</Trans> 571 - </Text> 560 + <Text style={[a.text_2xl, a.font_heavy]}> 561 + <Trans>Search</Trans> 562 + </Text> 563 + </View> 572 564 )} 573 565 574 - <View 575 - style={{ 576 - flexDirection: 'column', 577 - alignItems: 'center', 578 - justifyContent: 'center', 579 - paddingVertical: 30, 580 - gap: 15, 581 - }}> 566 + <View style={[a.align_center, a.justify_center, a.py_4xl, a.gap_lg]}> 582 567 <MagnifyingGlassIcon 583 568 strokeWidth={3} 584 - size={isDesktop ? 60 : 60} 585 - style={pal.textLight} 569 + size={60} 570 + style={t.atoms.text_contrast_medium as StyleProp<ViewStyle>} 586 571 /> 587 - <Text type="xl" style={[pal.textLight, {paddingHorizontal: 18}]}> 588 - <Trans>Find posts and users on Bluesky</Trans> 572 + <Text style={[t.atoms.text_contrast_medium, a.text_md]}> 573 + <Trans>Find posts, users, and feeds on Bluesky</Trans> 589 574 </Text> 590 575 </View> 591 576 </View> ··· 1023 1008 onRemoveItemClick: (item: string) => void 1024 1009 onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewDetailed) => void 1025 1010 }) { 1026 - const {isMobile} = useWebMediaQueries() 1027 - const pal = usePalette('default') 1011 + const {gtMobile} = useBreakpoints() 1012 + const t = useTheme() 1028 1013 const {_} = useLingui() 1029 1014 1030 1015 return ( 1031 1016 <Layout.Content 1032 1017 keyboardDismissMode="interactive" 1033 1018 keyboardShouldPersistTaps="handled"> 1034 - <View style={styles.searchHistoryContainer}> 1019 + <View style={[a.w_full, a.px_md]}> 1035 1020 {(searchHistory.length > 0 || selectedProfiles.length > 0) && ( 1036 - <Text style={[pal.text, styles.searchHistoryTitle]}> 1021 + <Text style={[a.text_md, a.font_bold, a.p_md]}> 1037 1022 <Trans>Recent Searches</Trans> 1038 1023 </Text> 1039 1024 )} ··· 1041 1026 <View 1042 1027 style={[ 1043 1028 styles.selectedProfilesContainer, 1044 - isMobile && styles.selectedProfilesContainerMobile, 1029 + !gtMobile && styles.selectedProfilesContainerMobile, 1045 1030 ]}> 1046 1031 <RNGHScrollView 1047 1032 keyboardShouldPersistTaps="handled" ··· 1049 1034 style={[ 1050 1035 a.flex_row, 1051 1036 a.flex_nowrap, 1052 - {marginHorizontal: -tokens.space._2xl}, 1037 + {marginHorizontal: tokens.space._2xl * -1}, 1053 1038 ]} 1054 1039 contentContainerStyle={[a.px_2xl, a.border_0]}> 1055 1040 {selectedProfiles.slice(0, 5).map((profile, index) => ( ··· 1057 1042 key={index} 1058 1043 style={[ 1059 1044 styles.profileItem, 1060 - isMobile && styles.profileItemMobile, 1045 + !gtMobile && styles.profileItemMobile, 1061 1046 ]}> 1062 1047 <Link 1063 1048 href={makeProfileLink(profile)} ··· 1065 1050 asAnchor 1066 1051 anchorNoUnderline 1067 1052 onBeforePress={() => onProfileClick(profile)} 1068 - style={styles.profilePressable}> 1069 - <Image 1070 - source={{uri: profile.avatar}} 1071 - style={styles.profileAvatar as StyleProp<ImageStyle>} 1072 - accessibilityIgnoresInvertColors 1053 + style={[a.align_center, a.w_full]}> 1054 + <UserAvatar 1055 + avatar={profile.avatar} 1056 + type={profile.associated?.labeler ? 'labeler' : 'user'} 1057 + size={60} 1073 1058 /> 1074 1059 <Text 1075 1060 emoji 1076 - style={[pal.text, styles.profileName]} 1061 + style={[a.text_xs, a.text_center, styles.profileName]} 1077 1062 numberOfLines={1}> 1078 1063 {sanitizeDisplayName( 1079 1064 profile.displayName || profile.handle, ··· 1089 1074 onPress={() => onRemoveProfileClick(profile)} 1090 1075 hitSlop={createHitslop(6)} 1091 1076 style={styles.profileRemoveBtn}> 1092 - <FontAwesomeIcon 1093 - icon="xmark" 1094 - size={14} 1095 - style={pal.textLight as FontAwesomeIconStyle} 1096 - /> 1077 + <XIcon size="xs" style={t.atoms.text_contrast_low} /> 1097 1078 </Pressable> 1098 1079 </View> 1099 1080 ))} ··· 1101 1082 </View> 1102 1083 )} 1103 1084 {searchHistory.length > 0 && ( 1104 - <View style={styles.searchHistoryContent}> 1085 + <View style={[a.pl_md, a.pr_xs, a.mt_md]}> 1105 1086 {searchHistory.slice(0, 5).map((historyItem, index) => ( 1106 - <View 1107 - key={index} 1108 - style={[ 1109 - a.flex_row, 1110 - a.mt_md, 1111 - a.justify_center, 1112 - a.justify_between, 1113 - ]}> 1087 + <View key={index} style={[a.flex_row, a.align_center, a.mt_xs]}> 1114 1088 <Pressable 1115 1089 accessibilityRole="button" 1116 1090 onPress={() => onItemClick(historyItem)} 1117 1091 hitSlop={HITSLOP_10} 1118 - style={[a.flex_1, a.py_sm]}> 1119 - <Text style={pal.text}>{historyItem}</Text> 1092 + style={[a.flex_1, a.py_md]}> 1093 + <Text style={[a.text_md]}>{historyItem}</Text> 1120 1094 </Pressable> 1121 - <Pressable 1122 - accessibilityRole="button" 1095 + <Button 1096 + label={_(msg`Remove ${historyItem}`)} 1123 1097 onPress={() => onRemoveItemClick(historyItem)} 1124 - hitSlop={HITSLOP_10} 1125 - style={[a.px_md, a.py_xs, a.justify_center]}> 1126 - <FontAwesomeIcon 1127 - icon="xmark" 1128 - size={16} 1129 - style={pal.textLight as FontAwesomeIconStyle} 1130 - /> 1131 - </Pressable> 1098 + size="small" 1099 + variant="ghost" 1100 + color="secondary" 1101 + shape="round"> 1102 + <ButtonIcon icon={XIcon} /> 1103 + </Button> 1132 1104 </View> 1133 1105 ))} 1134 1106 </View> ··· 1145 1117 } 1146 1118 1147 1119 const styles = StyleSheet.create({ 1148 - headerMenuBtn: { 1149 - width: 30, 1150 - height: 30, 1151 - borderRadius: 30, 1152 - marginRight: 6, 1153 - alignItems: 'center', 1154 - justifyContent: 'center', 1155 - }, 1156 - headerSearchContainer: { 1157 - flex: 1, 1158 - flexDirection: 'row', 1159 - alignItems: 'center', 1160 - borderRadius: 30, 1161 - paddingHorizontal: 12, 1162 - paddingVertical: 8, 1163 - }, 1164 - headerSearchIcon: { 1165 - marginRight: 6, 1166 - alignSelf: 'center', 1167 - }, 1168 - headerSearchInput: { 1169 - flex: 1, 1170 - fontSize: 17, 1171 - minWidth: 0, 1172 - }, 1173 - headerCancelBtn: { 1174 - paddingLeft: 10, 1175 - alignSelf: 'center', 1176 - zIndex: -1, 1177 - elevation: -1, // For Android 1178 - }, 1179 - searchHistoryContainer: { 1180 - width: '100%', 1181 - paddingHorizontal: 12, 1182 - }, 1183 1120 selectedProfilesContainer: { 1184 1121 marginTop: 10, 1185 1122 paddingHorizontal: 12, ··· 1196 1133 profileItemMobile: { 1197 1134 width: 70, 1198 1135 }, 1199 - profilePressable: { 1200 - alignItems: 'center', 1201 - width: '100%', 1202 - }, 1203 - profileAvatar: { 1204 - width: 60, 1205 - height: 60, 1206 - borderRadius: 45, 1207 - }, 1208 1136 profileName: { 1209 1137 width: 78, 1210 - fontSize: 12, 1211 - textAlign: 'center', 1212 - marginTop: 5, 1138 + marginTop: 6, 1213 1139 }, 1214 1140 profileRemoveBtn: { 1215 1141 position: 'absolute', ··· 1221 1147 height: 18, 1222 1148 alignItems: 'center', 1223 1149 justifyContent: 'center', 1224 - }, 1225 - searchHistoryContent: { 1226 - paddingHorizontal: 10, 1227 - borderRadius: 8, 1228 - }, 1229 - searchHistoryTitle: { 1230 - fontWeight: '600', 1231 - paddingVertical: 12, 1232 - paddingHorizontal: 10, 1233 1150 }, 1234 1151 })