Bluesky app fork with some witchin' additions 馃挮
at main 145 lines 4.2 kB view raw
1import {View} from 'react-native' 2import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' 3import {type AppBskyActorDefs} from '@atproto/api' 4import {Trans} from '@lingui/react/macro' 5 6import {PressableScale} from '#/lib/custom-animations/PressableScale' 7import {sanitizeDisplayName} from '#/lib/strings/display-names' 8import {sanitizeHandle} from '#/lib/strings/handles' 9import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 10import {UserAvatar} from '#/view/com/util/UserAvatar' 11import {atoms as a, platform, useTheme} from '#/alf' 12import {PdsBadge} from '#/components/PdsBadge' 13import {Text} from '#/components/Typography' 14import {useSimpleVerificationState} from '#/components/verification' 15import {VerificationCheck} from '#/components/verification/VerificationCheck' 16 17export function Autocomplete({ 18 prefix, 19 onSelect, 20}: { 21 prefix: string 22 onSelect: (item: string) => void 23}) { 24 const t = useTheme() 25 26 const isActive = !!prefix 27 const {data: suggestions, isFetching} = useActorAutocompleteQuery( 28 prefix, 29 true, 30 ) 31 32 if (!isActive) return null 33 34 return ( 35 <Animated.View 36 entering={FadeInDown.duration(200)} 37 exiting={FadeOut.duration(100)} 38 style={[ 39 t.atoms.bg, 40 a.mt_sm, 41 a.border, 42 a.rounded_sm, 43 t.atoms.border_contrast_high, 44 {marginLeft: -62}, 45 ]}> 46 {suggestions?.length ? ( 47 suggestions.slice(0, 5).map((item, index, arr) => { 48 return ( 49 <AutocompleteProfileCard 50 key={item.did} 51 profile={item} 52 itemIndex={index} 53 totalItems={arr.length} 54 onPress={() => { 55 onSelect(item.handle) 56 }} 57 /> 58 ) 59 }) 60 ) : ( 61 <Text style={[a.text_md, a.px_sm, a.py_md]}> 62 {isFetching ? <Trans>Loading...</Trans> : <Trans>No result</Trans>} 63 </Text> 64 )} 65 </Animated.View> 66 ) 67} 68 69function AutocompleteProfileCard({ 70 profile, 71 itemIndex, 72 totalItems, 73 onPress, 74}: { 75 profile: AppBskyActorDefs.ProfileViewBasic 76 itemIndex: number 77 totalItems: number 78 onPress: () => void 79}) { 80 const t = useTheme() 81 const state = useSimpleVerificationState({profile}) 82 const displayName = sanitizeDisplayName( 83 profile.displayName || sanitizeHandle(profile.handle), 84 ) 85 return ( 86 <View 87 style={[ 88 itemIndex !== totalItems - 1 && a.border_b, 89 t.atoms.border_contrast_high, 90 a.px_sm, 91 a.py_md, 92 ]} 93 key={profile.did}> 94 <PressableScale 95 testID="autocompleteButton" 96 style={[a.flex_row, a.gap_lg, a.justify_between, a.align_center]} 97 onPress={onPress} 98 accessibilityLabel={`Select ${profile.handle}`} 99 accessibilityHint=""> 100 <View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_1]}> 101 <UserAvatar 102 avatar={profile.avatar ?? null} 103 size={24} 104 type={profile.associated?.labeler ? 'labeler' : 'user'} 105 /> 106 <View 107 style={[ 108 a.flex_row, 109 a.align_center, 110 a.gap_xs, 111 platform({ios: a.flex_1}), 112 ]}> 113 <Text 114 style={[a.text_md, a.font_semi_bold, a.leading_snug]} 115 emoji 116 numberOfLines={1}> 117 {displayName} 118 </Text> 119 <View style={[{marginTop: platform({android: -2})}]}> 120 <PdsBadge did={profile.did} size="sm" /> 121 </View> 122 {state.isVerified && ( 123 <View 124 style={[ 125 { 126 marginTop: platform({android: -2}), 127 }, 128 ]}> 129 <VerificationCheck 130 width={12} 131 verifier={state.role === 'verifier'} 132 /> 133 </View> 134 )} 135 </View> 136 </View> 137 <Text 138 style={[t.atoms.text_contrast_medium, a.text_right, a.leading_snug]} 139 numberOfLines={1}> 140 {sanitizeHandle(profile.handle, '@')} 141 </Text> 142 </PressableScale> 143 </View> 144 ) 145}