forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}