Add initial impl, scroll context
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import {createContext, useCallback, useContext, useMemo} from 'react'
|
||||
import {type NativeScrollEvent} from 'react-native'
|
||||
import {type ScrollHandlers} from 'react-native-reanimated'
|
||||
|
||||
export type NormalizedScrollHandlers = {
|
||||
/**
|
||||
* Web + Native — needs to be a `worklet` on native, but can be either on web
|
||||
*/
|
||||
onScroll?: ScrollHandlers<any>['onScroll']
|
||||
/**
|
||||
* Native only
|
||||
*/
|
||||
onScrollBeginDrag?: ScrollHandlers<any>['onBeginDrag']
|
||||
/**
|
||||
* Native only
|
||||
*/
|
||||
onScrollEndDrag?: ScrollHandlers<any>['onEndDrag']
|
||||
/**
|
||||
* Native only
|
||||
*/
|
||||
onMomentumScrollBegin?: ScrollHandlers<any>['onMomentumBegin']
|
||||
/**
|
||||
* Native only
|
||||
*/
|
||||
onMomentumScrollEnd?: ScrollHandlers<any>['onMomentumEnd']
|
||||
}
|
||||
|
||||
const ListScrollContext = createContext<NormalizedScrollHandlers>({
|
||||
onScroll: undefined,
|
||||
onScrollBeginDrag: undefined,
|
||||
onScrollEndDrag: undefined,
|
||||
onMomentumScrollBegin: undefined,
|
||||
onMomentumScrollEnd: undefined,
|
||||
})
|
||||
ListScrollContext.displayName = 'ListScrollContext'
|
||||
|
||||
export function ListScrollProvider({
|
||||
children,
|
||||
onScrollBeginDrag,
|
||||
onScrollEndDrag,
|
||||
onScroll,
|
||||
onMomentumScrollBegin,
|
||||
onMomentumScrollEnd,
|
||||
}: {children: React.ReactNode} & NormalizedScrollHandlers) {
|
||||
const handlers = useMemo(
|
||||
() => ({
|
||||
onScroll,
|
||||
onScrollBeginDrag: onScrollBeginDrag,
|
||||
onScrollEndDrag: onScrollEndDrag,
|
||||
onMomentumScrollBegin: onMomentumScrollBegin,
|
||||
onMomentumScrollEnd: onMomentumScrollEnd,
|
||||
}),
|
||||
[
|
||||
onScrollBeginDrag,
|
||||
onScrollEndDrag,
|
||||
onScroll,
|
||||
onMomentumScrollBegin,
|
||||
onMomentumScrollEnd,
|
||||
],
|
||||
)
|
||||
return (
|
||||
<ListScrollContext.Provider value={handlers}>
|
||||
{children}
|
||||
</ListScrollContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useListScrollContext(): NormalizedScrollHandlers {
|
||||
return useContext(ListScrollContext)
|
||||
}
|
||||
|
||||
export const useListScrollHandler = useCallback<(e: NativeScrollEvent) => void>
|
||||
@@ -0,0 +1,66 @@
|
||||
import {forwardRef} from 'react'
|
||||
import {type FlatList, type FlatListProps} from 'react-native'
|
||||
import Animated, {
|
||||
type FlatListPropsWithLayout,
|
||||
useAnimatedScrollHandler,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
import {atoms as a, web} from '#/alf'
|
||||
import {useListScrollContext} from '#/components/List/ListScrollProvider'
|
||||
|
||||
export {
|
||||
ListScrollProvider,
|
||||
useListScrollHandler,
|
||||
} from '#/components/List/ListScrollProvider'
|
||||
|
||||
/**
|
||||
* Cleaned up FlatList without some problematic props.
|
||||
*
|
||||
* - `contentOffset` - Doesn't work, use padding on `contentContainerStyle` instead.
|
||||
*/
|
||||
type ListProps<Item> = Omit<FlatListProps<Item>, 'contentOffset'>
|
||||
|
||||
export const List = forwardRef(function List<Item>(
|
||||
props: ListProps<Item>,
|
||||
ref: React.Ref<FlatList<Item>>,
|
||||
) {
|
||||
const scrollHandlers = useListScrollContext()
|
||||
const onScroll = useAnimatedScrollHandler({
|
||||
onScroll(e, ctx) {
|
||||
scrollHandlers.onScroll?.(e, ctx)
|
||||
},
|
||||
onBeginDrag(e, ctx) {
|
||||
scrollHandlers.onScrollBeginDrag?.(e, ctx)
|
||||
},
|
||||
onEndDrag(e, ctx) {
|
||||
scrollHandlers.onScrollEndDrag?.(e, ctx)
|
||||
},
|
||||
onMomentumBegin(e, ctx) {
|
||||
scrollHandlers.onMomentumScrollBegin?.(e, ctx)
|
||||
},
|
||||
/*
|
||||
* Note: adding onMomentumBegin here makes simulator scroll lag on Android.
|
||||
* So either don't add it, or figure out why. - sfn
|
||||
*/
|
||||
onMomentumEnd(e, ctx) {
|
||||
scrollHandlers.onMomentumScrollEnd?.(e, ctx)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Animated.FlatList<Item>
|
||||
ref={ref}
|
||||
{...(props as FlatListPropsWithLayout<Item>)}
|
||||
style={[
|
||||
/*
|
||||
* On web, the List should always fill its container, otherwise
|
||||
* `onScroll` will not work due to the entire page scrolling.
|
||||
*/
|
||||
web(a.h_full),
|
||||
]}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
)
|
||||
}) as <Item>(
|
||||
props: ListProps<Item> & {ref?: React.Ref<FlatList<Item>>},
|
||||
) => React.ReactElement
|
||||
@@ -1,13 +1,10 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {runOnJS} from 'react-native-reanimated'
|
||||
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {useSetThemePrefs} from '#/state/shell'
|
||||
import {ListContained} from '#/view/screens/Storybook/ListContained'
|
||||
import {atoms as a, ThemeProvider} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {atoms as a} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {List, ListScrollProvider, useListScrollHandler} from '#/components/List'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function StorybookLists() {
|
||||
return (
|
||||
@@ -20,12 +17,43 @@ export function StorybookLists() {
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content keyboardShouldPersistTaps="handled">
|
||||
<StorybookInner />
|
||||
<Inner />
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
function StorybookInner() {
|
||||
return null
|
||||
const items = Array.from({length: 100}).map((_, i) => ({
|
||||
key: `item-${i + 1}`,
|
||||
title: `Item ${i + 1}`,
|
||||
}))
|
||||
|
||||
type Item = {
|
||||
key: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const log = (msg: any) => console.log(msg)
|
||||
|
||||
export function Inner() {
|
||||
const onScrollWorklet = useListScrollHandler(e => {
|
||||
'worklet'
|
||||
runOnJS(log)(`Scroll Y: ${e.contentOffset.y}`)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={[a.h_full_vh]}>
|
||||
<ListScrollProvider onScroll={onScrollWorklet}>
|
||||
<List<Item>
|
||||
data={items}
|
||||
renderItem={({item}) => (
|
||||
<View style={[a.p_md, a.border_b]}>
|
||||
<Text>{item.title}</Text>
|
||||
</View>
|
||||
)}
|
||||
style={[a.debug]}
|
||||
/>
|
||||
</ListScrollProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user