refactor(mobile): simplify scroll-to-top implementation
Remove scroll-to-top atom and consolidate scroll-to-top logic into a new hook, improving component integration and reducing complexity Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
15e3e1c27f
commit
c4c9ceb385
|
|
@ -1,29 +0,0 @@
|
|||
import { jotaiStore } from "@follow/utils"
|
||||
import type { FlashList } from "@shopify/flash-list"
|
||||
import { atom } from "jotai"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
const defaultScrollToTop = { scrollToTop: () => {} }
|
||||
const scrollToTopAtom = atom<{ scrollToTop: () => void }>(defaultScrollToTop)
|
||||
|
||||
export const scrollToTop = () => {
|
||||
const { scrollToTop } = jotaiStore.get(scrollToTopAtom)
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
export const useScrollToTopRef = <T extends FlashList<unknown>>(enabled = true) => {
|
||||
const ref = useRef<T>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return
|
||||
const scrollToTop = () => {
|
||||
ref.current?.scrollToOffset({ animated: true, offset: 0 })
|
||||
}
|
||||
jotaiStore.set(scrollToTopAtom, { scrollToTop })
|
||||
return () => {
|
||||
if (jotaiStore.get(scrollToTopAtom).scrollToTop !== scrollToTop) return
|
||||
jotaiStore.set(scrollToTopAtom, defaultScrollToTop)
|
||||
}
|
||||
}, [enabled, ref])
|
||||
return ref
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { createContext, useContext, useEffect, useMemo, useRef } from "react"
|
|||
import type { ScrollView, ScrollViewProps } from "react-native"
|
||||
import {
|
||||
Animated as RNAnimated,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
useAnimatedValue,
|
||||
|
|
@ -16,12 +17,11 @@ import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescri
|
|||
import { useSafeAreaInsets } from "react-native-safe-area-context"
|
||||
import { useColor } from "react-native-uikit-colors"
|
||||
|
||||
import { scrollToTop } from "@/src/atoms/scroll-to-top"
|
||||
import {
|
||||
AttachNavigationScrollViewContext,
|
||||
SetAttachNavigationScrollViewContext,
|
||||
} from "@/src/components/ui/tabbar/contexts/AttachNavigationScrollViewContext"
|
||||
import { useBottomTabBarHeight } from "@/src/components/ui/tabbar/hooks"
|
||||
import { useBottomTabBarHeight, useNavigationScrollToTop } from "@/src/components/ui/tabbar/hooks"
|
||||
import { useDefaultHeaderHeight } from "@/src/hooks/useDefaultHeaderHeight"
|
||||
import { MingcuteLeftLineIcon } from "@/src/icons/mingcute_left_line"
|
||||
|
||||
|
|
@ -147,6 +147,7 @@ export const NavigationBlurEffectHeader = ({
|
|||
|
||||
const hideableBottom = headerHideableBottom?.()
|
||||
const { headerLeft, ...rest } = props
|
||||
const scrollToTop = useNavigationScrollToTop()
|
||||
|
||||
return (
|
||||
<Stack.Screen
|
||||
|
|
@ -175,13 +176,11 @@ export const NavigationBlurEffectHeader = ({
|
|||
<View pointerEvents="box-none" style={[StyleSheet.absoluteFill]}>
|
||||
{options.headerBackground?.()}
|
||||
</View>
|
||||
<TouchableOpacity onPress={scrollToTop}>
|
||||
<Header
|
||||
title={options.title ?? ""}
|
||||
{...options}
|
||||
headerBackground={() => null}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Pressable className="absolute inset-x-0 top-0 h-28" onPress={scrollToTop} />
|
||||
|
||||
<Header title={options.title ?? ""} {...options} headerBackground={() => null} />
|
||||
|
||||
{hideableBottom}
|
||||
</Animated.View>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"
|
|||
import { Tabs } from "expo-router"
|
||||
import type { ForwardRefExoticComponent } from "react"
|
||||
import { forwardRef, useMemo, useRef, useState } from "react"
|
||||
import type { FlatList, ScrollView } from "react-native"
|
||||
import type { ScrollView } from "react-native"
|
||||
import { useSharedValue } from "react-native-reanimated"
|
||||
|
||||
import { BottomTabHeightProvider } from "./BottomTabHeightProvider"
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
BottomTabBarVisibleContext,
|
||||
SetBottomTabBarVisibleContext,
|
||||
} from "./contexts/BottomTabBarVisibleContext"
|
||||
import { useNavigationScrollToTop } from "./hooks"
|
||||
import { Tabbar } from "./Tabbar"
|
||||
|
||||
type ExtractReactForwardRefExoticComponent<T> =
|
||||
|
|
@ -29,6 +30,7 @@ export const BottomTabs: ForwardRefExoticComponent<
|
|||
useState<React.RefObject<ScrollView> | null>(null)
|
||||
|
||||
const currentTarget = useRef<string | undefined>(undefined)
|
||||
const scrollToTop = useNavigationScrollToTop(attachNavigationScrollViewRef)
|
||||
return (
|
||||
<AttachNavigationScrollViewContext.Provider value={attachNavigationScrollViewRef}>
|
||||
<SetAttachNavigationScrollViewContext.Provider value={setAttachNavigationScrollViewRef}>
|
||||
|
|
@ -47,24 +49,7 @@ export const BottomTabs: ForwardRefExoticComponent<
|
|||
}
|
||||
|
||||
if (currentTarget.current === e.target) {
|
||||
const $scroller = attachNavigationScrollViewRef?.current as any
|
||||
|
||||
if ("scrollTo" in $scroller) {
|
||||
;($scroller as ScrollView).scrollTo({
|
||||
y: 0,
|
||||
animated: true,
|
||||
})
|
||||
} else if ("scrollToIndex" in $scroller) {
|
||||
;($scroller as FlatList<any>).scrollToIndex({
|
||||
index: 0,
|
||||
animated: true,
|
||||
})
|
||||
} else if ("scrollToOffset" in $scroller) {
|
||||
;($scroller as FlatList<any>).scrollToOffset({
|
||||
offset: 0,
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
scrollToTop()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,38 @@
|
|||
import { useContext } from "react"
|
||||
import { useCallback, useContext } from "react"
|
||||
import type { FlatList, ScrollView } from "react-native"
|
||||
|
||||
import { AttachNavigationScrollViewContext } from "./contexts/AttachNavigationScrollViewContext"
|
||||
import { BottomTabBarHeightContext } from "./contexts/BottomTabBarHeightContext"
|
||||
|
||||
export const useBottomTabBarHeight = () => {
|
||||
const height = useContext(BottomTabBarHeightContext)
|
||||
return height
|
||||
}
|
||||
|
||||
export const useNavigationScrollToTop = (
|
||||
overrideScrollerRef?: React.RefObject<ScrollView> | React.RefObject<FlatList<any>> | null,
|
||||
) => {
|
||||
const attachNavigationScrollViewRef = useContext(AttachNavigationScrollViewContext)
|
||||
return useCallback(() => {
|
||||
const $scroller = overrideScrollerRef?.current ?? attachNavigationScrollViewRef?.current
|
||||
if (!$scroller) return
|
||||
|
||||
if ("scrollTo" in $scroller) {
|
||||
;($scroller as ScrollView).scrollTo({
|
||||
y: 0,
|
||||
animated: true,
|
||||
})
|
||||
} else if ("scrollToIndex" in $scroller) {
|
||||
;($scroller as FlatList<any>).scrollToIndex({
|
||||
index: 0,
|
||||
animated: true,
|
||||
})
|
||||
} else if ("scrollToOffset" in $scroller) {
|
||||
;($scroller as FlatList<any>).scrollToOffset({
|
||||
offset: 0,
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
return
|
||||
}, [attachNavigationScrollViewRef, overrideScrollerRef])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import type { FlashList } from "@shopify/flash-list"
|
||||
import type { RefObject } from "react"
|
||||
import { useContext, useEffect, useRef } from "react"
|
||||
import type { ScrollView } from "react-native"
|
||||
|
||||
import { useScrollToTopRef } from "@/src/atoms/scroll-to-top"
|
||||
import { SetAttachNavigationScrollViewContext } from "@/src/components/ui/tabbar/contexts/AttachNavigationScrollViewContext"
|
||||
import { EntryListContentGrid } from "@/src/modules/entry-list/EntryListContentGrid"
|
||||
|
||||
import { EntryListContentArticle } from "./EntryListContentArticle"
|
||||
|
|
@ -15,7 +19,15 @@ export function EntryListSelector({
|
|||
viewId: FeedViewType
|
||||
active?: boolean
|
||||
}) {
|
||||
const ref = useScrollToTopRef(active)
|
||||
const setAttachNavigationScrollViewRef = useContext(SetAttachNavigationScrollViewContext)
|
||||
|
||||
const ref = useRef<FlashList<any>>(null)
|
||||
useEffect(() => {
|
||||
if (!active) return
|
||||
if (setAttachNavigationScrollViewRef) {
|
||||
setAttachNavigationScrollViewRef(ref as unknown as RefObject<ScrollView>)
|
||||
}
|
||||
}, [setAttachNavigationScrollViewRef, ref, active])
|
||||
|
||||
let ContentComponent: typeof EntryListContentSocial | typeof EntryListContentGrid =
|
||||
EntryListContentArticle
|
||||
|
|
|
|||
Loading…
Reference in New Issue