From d4e7355cca05483bfba8c30dbb9b4cdd2be14e51 Mon Sep 17 00:00:00 2001
From: Kadi Kraman <hellokadi@gmail.com>
Date: Thu, 1 Jun 2023 18:00:00 +0200
Subject: fix: support scroll to top on profile screen (#725)

* Support scroll to top on profile screen

* Refactor types

* Remove async

* Improve types
---
 src/view/com/util/ViewSelector.tsx | 200 ++++++++++++++++++++-----------------
 1 file changed, 110 insertions(+), 90 deletions(-)

(limited to 'src/view/com/util/ViewSelector.tsx')

diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx
index 5b671d06c..705178a8a 100644
--- a/src/view/com/util/ViewSelector.tsx
+++ b/src/view/com/util/ViewSelector.tsx
@@ -13,103 +13,123 @@ const HEADER_ITEM = {_reactKey: '__header__'}
 const SELECTOR_ITEM = {_reactKey: '__selector__'}
 const STICKY_HEADER_INDICES = [1]
 
-export function ViewSelector({
-  sections,
-  items,
-  refreshing,
-  renderHeader,
-  renderItem,
-  ListFooterComponent,
-  onSelectView,
-  onScroll,
-  onRefresh,
-  onEndReached,
-}: {
-  sections: string[]
-  items: any[]
-  refreshing?: boolean
-  swipeEnabled?: boolean
-  renderHeader?: () => JSX.Element
-  renderItem: (item: any) => JSX.Element
-  ListFooterComponent?:
-    | React.ComponentType<any>
-    | React.ReactElement
-    | null
-    | undefined
-  onSelectView?: (viewIndex: number) => void
-  onScroll?: OnScrollCb
-  onRefresh?: () => void
-  onEndReached?: (info: {distanceFromEnd: number}) => void
-}) {
-  const pal = usePalette('default')
-  const [selectedIndex, setSelectedIndex] = useState<number>(0)
+export type ViewSelectorHandle = {
+  scrollToTop: () => void
+}
 
-  // events
-  // =
+export const ViewSelector = React.forwardRef<
+  ViewSelectorHandle,
+  {
+    sections: string[]
+    items: any[]
+    refreshing?: boolean
+    swipeEnabled?: boolean
+    renderHeader?: () => JSX.Element
+    renderItem: (item: any) => JSX.Element
+    ListFooterComponent?:
+      | React.ComponentType<any>
+      | React.ReactElement
+      | null
+      | undefined
+    onSelectView?: (viewIndex: number) => void
+    onScroll?: OnScrollCb
+    onRefresh?: () => void
+    onEndReached?: (info: {distanceFromEnd: number}) => void
+  }
+>(
+  (
+    {
+      sections,
+      items,
+      refreshing,
+      renderHeader,
+      renderItem,
+      ListFooterComponent,
+      onSelectView,
+      onScroll,
+      onRefresh,
+      onEndReached,
+    },
+    ref,
+  ) => {
+    const pal = usePalette('default')
+    const [selectedIndex, setSelectedIndex] = useState<number>(0)
+    const flatListRef = React.useRef<FlatList>(null)
 
-  const keyExtractor = React.useCallback(item => item._reactKey, [])
+    // events
+    // =
 
-  const onPressSelection = React.useCallback(
-    (index: number) => setSelectedIndex(clamp(index, 0, sections.length)),
-    [setSelectedIndex, sections],
-  )
-  useEffect(() => {
-    onSelectView?.(selectedIndex)
-  }, [selectedIndex, onSelectView])
+    const keyExtractor = React.useCallback(item => item._reactKey, [])
+
+    const onPressSelection = React.useCallback(
+      (index: number) => setSelectedIndex(clamp(index, 0, sections.length)),
+      [setSelectedIndex, sections],
+    )
+    useEffect(() => {
+      onSelectView?.(selectedIndex)
+    }, [selectedIndex, onSelectView])
 
-  // rendering
-  // =
+    React.useImperativeHandle(ref, () => ({
+      scrollToTop: () => {
+        flatListRef.current?.scrollToOffset({offset: 0})
+      },
+    }))
 
-  const renderItemInternal = React.useCallback(
-    ({item}: {item: any}) => {
-      if (item === HEADER_ITEM) {
-        if (renderHeader) {
-          return renderHeader()
+    // rendering
+    // =
+
+    const renderItemInternal = React.useCallback(
+      ({item}: {item: any}) => {
+        if (item === HEADER_ITEM) {
+          if (renderHeader) {
+            return renderHeader()
+          }
+          return <View />
+        } else if (item === SELECTOR_ITEM) {
+          return (
+            <Selector
+              items={sections}
+              selectedIndex={selectedIndex}
+              onSelect={onPressSelection}
+            />
+          )
+        } else {
+          return renderItem(item)
         }
-        return <View />
-      } else if (item === SELECTOR_ITEM) {
-        return (
-          <Selector
-            items={sections}
-            selectedIndex={selectedIndex}
-            onSelect={onPressSelection}
-          />
-        )
-      } else {
-        return renderItem(item)
-      }
-    },
-    [sections, selectedIndex, onPressSelection, renderHeader, renderItem],
-  )
+      },
+      [sections, selectedIndex, onPressSelection, renderHeader, renderItem],
+    )
 
-  const data = React.useMemo(
-    () => [HEADER_ITEM, SELECTOR_ITEM, ...items],
-    [items],
-  )
-  return (
-    <FlatList
-      data={data}
-      keyExtractor={keyExtractor}
-      renderItem={renderItemInternal}
-      ListFooterComponent={ListFooterComponent}
-      // NOTE sticky header disabled on android due to major performance issues -prf
-      stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES}
-      onScroll={onScroll}
-      onEndReached={onEndReached}
-      refreshControl={
-        <RefreshControl
-          refreshing={refreshing!}
-          onRefresh={onRefresh}
-          tintColor={pal.colors.text}
-        />
-      }
-      onEndReachedThreshold={0.6}
-      contentContainerStyle={s.contentContainer}
-      removeClippedSubviews={true}
-      scrollIndicatorInsets={{right: 1}} // fixes a bug where the scroll indicator is on the middle of the screen https://github.com/bluesky-social/social-app/pull/464
-    />
-  )
-}
+    const data = React.useMemo(
+      () => [HEADER_ITEM, SELECTOR_ITEM, ...items],
+      [items],
+    )
+    return (
+      <FlatList
+        ref={flatListRef}
+        data={data}
+        keyExtractor={keyExtractor}
+        renderItem={renderItemInternal}
+        ListFooterComponent={ListFooterComponent}
+        // NOTE sticky header disabled on android due to major performance issues -prf
+        stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES}
+        onScroll={onScroll}
+        onEndReached={onEndReached}
+        refreshControl={
+          <RefreshControl
+            refreshing={refreshing!}
+            onRefresh={onRefresh}
+            tintColor={pal.colors.text}
+          />
+        }
+        onEndReachedThreshold={0.6}
+        contentContainerStyle={s.contentContainer}
+        removeClippedSubviews={true}
+        scrollIndicatorInsets={{right: 1}} // fixes a bug where the scroll indicator is on the middle of the screen https://github.com/bluesky-social/social-app/pull/464
+      />
+    )
+  },
+)
 
 export function Selector({
   selectedIndex,
-- 
cgit 1.4.1

