Bluesky app fork with some witchin' additions 馃挮
at main 5.6 kB view raw
1import {memo} from 'react' 2import {type StyleProp, View, type ViewStyle} from 'react-native' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5 6import {cleanError} from '#/lib/strings/errors' 7import { 8 EmptyState, 9 type EmptyStateButtonProps, 10} from '#/view/com/util/EmptyState' 11import {CenteredView} from '#/view/com/util/Views' 12import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13import {Button, ButtonText} from '#/components/Button' 14import {Error} from '#/components/Error' 15import {Loader} from '#/components/Loader' 16import {Text} from '#/components/Typography' 17 18export function ListFooter({ 19 isFetchingNextPage, 20 hasNextPage, 21 error, 22 onRetry, 23 height, 24 style, 25 showEndMessage = false, 26 endMessageText, 27 renderEndMessage, 28}: { 29 isFetchingNextPage?: boolean 30 hasNextPage?: boolean 31 error?: string 32 onRetry?: () => Promise<unknown> 33 height?: number 34 style?: StyleProp<ViewStyle> 35 showEndMessage?: boolean 36 endMessageText?: string 37 renderEndMessage?: () => React.ReactNode 38}) { 39 const t = useTheme() 40 41 return ( 42 <View 43 style={[ 44 a.w_full, 45 a.align_center, 46 a.border_t, 47 a.pb_lg, 48 t.atoms.border_contrast_low, 49 {height: height ?? 180, paddingTop: 30}, 50 style, 51 ]}> 52 {isFetchingNextPage ? ( 53 <Loader size="xl" /> 54 ) : error ? ( 55 <ListFooterMaybeError error={error} onRetry={onRetry} /> 56 ) : !hasNextPage && showEndMessage ? ( 57 renderEndMessage ? ( 58 renderEndMessage() 59 ) : ( 60 <Text style={[a.text_sm, t.atoms.text_contrast_low]}> 61 {endMessageText ?? <Trans>You have reached the end</Trans>} 62 </Text> 63 ) 64 ) : null} 65 </View> 66 ) 67} 68 69function ListFooterMaybeError({ 70 error, 71 onRetry, 72}: { 73 error?: string 74 onRetry?: () => Promise<unknown> 75}) { 76 const t = useTheme() 77 const {_} = useLingui() 78 79 if (!error) return null 80 81 return ( 82 <View style={[a.w_full, a.px_lg]}> 83 <View 84 style={[ 85 a.flex_row, 86 a.gap_md, 87 a.p_md, 88 a.rounded_sm, 89 a.align_center, 90 t.atoms.bg_contrast_25, 91 ]}> 92 <Text 93 style={[a.flex_1, a.text_sm, t.atoms.text_contrast_medium]} 94 numberOfLines={2}> 95 {error ? ( 96 cleanError(error) 97 ) : ( 98 <Trans>Oops, something went wrong!</Trans> 99 )} 100 </Text> 101 <Button 102 variant="solid" 103 label={_(msg`Press to retry`)} 104 style={[ 105 a.align_center, 106 a.justify_center, 107 a.rounded_sm, 108 a.overflow_hidden, 109 a.px_md, 110 a.py_sm, 111 ]} 112 onPress={onRetry}> 113 <ButtonText> 114 <Trans>Retry</Trans> 115 </ButtonText> 116 </Button> 117 </View> 118 </View> 119 ) 120} 121 122let ListMaybePlaceholder = ({ 123 isLoading, 124 noEmpty, 125 isError, 126 emptyTitle, 127 emptyMessage, 128 errorTitle, 129 errorMessage, 130 emptyType = 'page', 131 onRetry, 132 onGoBack, 133 hideBackButton, 134 sideBorders, 135 topBorder = false, 136 emptyStateIcon, 137 emptyStateButton, 138 useEmptyState = false, 139}: { 140 isLoading: boolean 141 noEmpty?: boolean 142 isError?: boolean 143 emptyTitle?: string 144 emptyMessage?: string 145 errorTitle?: string 146 errorMessage?: string 147 emptyType?: 'page' | 'results' 148 onRetry?: () => Promise<unknown> 149 onGoBack?: () => void 150 hideBackButton?: boolean 151 sideBorders?: boolean 152 topBorder?: boolean 153 emptyStateIcon?: React.ComponentType<any> | React.ReactElement 154 emptyStateButton?: EmptyStateButtonProps 155 useEmptyState?: boolean 156}): React.ReactNode => { 157 const t = useTheme() 158 const {_} = useLingui() 159 const {gtMobile, gtTablet} = useBreakpoints() 160 161 if (isLoading) { 162 return ( 163 <CenteredView 164 style={[ 165 a.h_full_vh, 166 a.align_center, 167 !gtMobile ? a.justify_between : a.gap_5xl, 168 t.atoms.border_contrast_low, 169 {paddingTop: 175, paddingBottom: 110}, 170 ]} 171 sideBorders={sideBorders ?? gtMobile} 172 topBorder={topBorder && !gtTablet}> 173 <View style={[a.w_full, a.align_center, {top: 100}]}> 174 <Loader size="xl" /> 175 </View> 176 </CenteredView> 177 ) 178 } 179 180 if (isError) { 181 return ( 182 <Error 183 title={errorTitle ?? _(msg`Oops!`)} 184 message={errorMessage ?? _(msg`Something went wrong!`)} 185 onRetry={onRetry} 186 onGoBack={onGoBack} 187 sideBorders={sideBorders} 188 hideBackButton={hideBackButton} 189 /> 190 ) 191 } 192 193 if (useEmptyState) { 194 return ( 195 <CenteredView 196 style={[t.atoms.border_contrast_low]} 197 sideBorders={sideBorders ?? gtMobile}> 198 <EmptyState 199 icon={emptyStateIcon} 200 message={ 201 emptyMessage ?? 202 (emptyType === 'results' 203 ? _(msg`No results found`) 204 : _(msg`Page not found`)) 205 } 206 button={emptyStateButton} 207 /> 208 </CenteredView> 209 ) 210 } 211 212 if (!noEmpty) { 213 return ( 214 <Error 215 title={ 216 emptyTitle ?? 217 (emptyType === 'results' 218 ? _(msg`No results found`) 219 : _(msg`Page not found`)) 220 } 221 message={ 222 emptyMessage ?? 223 _(msg`We're sorry! We can't find the page you were looking for.`) 224 } 225 onRetry={onRetry} 226 onGoBack={onGoBack} 227 hideBackButton={hideBackButton} 228 sideBorders={sideBorders} 229 /> 230 ) 231 } 232 233 return null 234} 235ListMaybePlaceholder = memo(ListMaybePlaceholder) 236export {ListMaybePlaceholder}