about summary refs log tree commit diff
path: root/src/components/Lists.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/Lists.tsx')
-rw-r--r--src/components/Lists.tsx200
1 files changed, 200 insertions, 0 deletions
diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx
new file mode 100644
index 000000000..d3e072028
--- /dev/null
+++ b/src/components/Lists.tsx
@@ -0,0 +1,200 @@
+import React from 'react'
+import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {View} from 'react-native'
+import {useLingui} from '@lingui/react'
+import {Trans, msg} from '@lingui/macro'
+
+import {CenteredView} from 'view/com/util/Views'
+import {Loader} from '#/components/Loader'
+import {cleanError} from 'lib/strings/errors'
+import {Button} from '#/components/Button'
+import {Text} from '#/components/Typography'
+import {Error} from '#/components/Error'
+
+export function ListFooter({
+  isFetching,
+  isError,
+  error,
+  onRetry,
+  height,
+}: {
+  isFetching?: boolean
+  isError?: boolean
+  error?: string
+  onRetry?: () => Promise<unknown>
+  height?: number
+}) {
+  const t = useTheme()
+
+  return (
+    <View
+      style={[
+        a.w_full,
+        a.align_center,
+        a.border_t,
+        a.pb_lg,
+        t.atoms.border_contrast_low,
+        {height: height ?? 180, paddingTop: 30},
+      ]}>
+      {isFetching ? (
+        <Loader size="xl" />
+      ) : (
+        <ListFooterMaybeError
+          isError={isError}
+          error={error}
+          onRetry={onRetry}
+        />
+      )}
+    </View>
+  )
+}
+
+function ListFooterMaybeError({
+  isError,
+  error,
+  onRetry,
+}: {
+  isError?: boolean
+  error?: string
+  onRetry?: () => Promise<unknown>
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+
+  if (!isError) return null
+
+  return (
+    <View style={[a.w_full, a.px_lg]}>
+      <View
+        style={[
+          a.flex_row,
+          a.gap_md,
+          a.p_md,
+          a.rounded_sm,
+          a.align_center,
+          t.atoms.bg_contrast_25,
+        ]}>
+        <Text
+          style={[a.flex_1, a.text_sm, t.atoms.text_contrast_medium]}
+          numberOfLines={2}>
+          {error ? (
+            cleanError(error)
+          ) : (
+            <Trans>Oops, something went wrong!</Trans>
+          )}
+        </Text>
+        <Button
+          variant="gradient"
+          label={_(msg`Press to retry`)}
+          style={[
+            a.align_center,
+            a.justify_center,
+            a.rounded_sm,
+            a.overflow_hidden,
+            a.px_md,
+            a.py_sm,
+          ]}
+          onPress={onRetry}>
+          <Trans>Retry</Trans>
+        </Button>
+      </View>
+    </View>
+  )
+}
+
+export function ListHeaderDesktop({
+  title,
+  subtitle,
+}: {
+  title: string
+  subtitle?: string
+}) {
+  const {gtTablet} = useBreakpoints()
+  const t = useTheme()
+
+  if (!gtTablet) return null
+
+  return (
+    <View style={[a.w_full, a.py_lg, a.px_xl, a.gap_xs]}>
+      <Text style={[a.text_3xl, a.font_bold]}>{title}</Text>
+      {subtitle ? (
+        <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+          {subtitle}
+        </Text>
+      ) : undefined}
+    </View>
+  )
+}
+
+export function ListMaybePlaceholder({
+  isLoading,
+  isEmpty,
+  isError,
+  emptyTitle,
+  emptyMessage,
+  errorTitle,
+  errorMessage,
+  emptyType = 'page',
+  onRetry,
+}: {
+  isLoading: boolean
+  isEmpty?: boolean
+  isError?: boolean
+  emptyTitle?: string
+  emptyMessage?: string
+  errorTitle?: string
+  errorMessage?: string
+  emptyType?: 'page' | 'results'
+  onRetry?: () => Promise<unknown>
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+  const {gtMobile, gtTablet} = useBreakpoints()
+
+  if (!isLoading && isError) {
+    return (
+      <Error
+        title={errorTitle ?? _(msg`Oops!`)}
+        message={errorMessage ?? _(`Something went wrong!`)}
+        onRetry={onRetry}
+      />
+    )
+  }
+
+  if (isLoading) {
+    return (
+      <CenteredView
+        style={[
+          a.flex_1,
+          a.align_center,
+          !gtMobile ? a.justify_between : a.gap_5xl,
+          t.atoms.border_contrast_low,
+          {paddingTop: 175, paddingBottom: 110},
+        ]}
+        sideBorders={gtMobile}
+        topBorder={!gtTablet}>
+        <View style={[a.w_full, a.align_center, {top: 100}]}>
+          <Loader size="xl" />
+        </View>
+      </CenteredView>
+    )
+  }
+
+  if (isEmpty) {
+    return (
+      <Error
+        title={
+          emptyTitle ??
+          (emptyType === 'results'
+            ? _(msg`No results found`)
+            : _(msg`Page not found`))
+        }
+        message={
+          emptyMessage ??
+          _(msg`We're sorry! We can't find the page you were looking for.`)
+        }
+        onRetry={onRetry}
+      />
+    )
+  }
+}