Add toggle to switch between list impl, add default keyExtractor

This commit is contained in:
Eric Bailey
2025-08-27 20:57:54 -05:00
parent d53c0e548f
commit afa6d67c23
2 changed files with 67 additions and 35 deletions
+13 -17
View File
@@ -26,7 +26,10 @@ export {
* *
* - `contentOffset` - Use `headerOffset` or `footerOffset` * - `contentOffset` - Use `headerOffset` or `footerOffset`
*/ */
type ListProps<Item> = Omit<FlatListProps<Item>, 'contentOffset'> & { type ListProps<Item extends {key: string}> = Omit<
FlatListProps<Item>,
'contentOffset'
> & {
/** /**
* Wrapper around `onViewableItemsChanged` that calls back with individual * Wrapper around `onViewableItemsChanged` that calls back with individual
* items IF they `item.isViewable` is true. * items IF they `item.isViewable` is true.
@@ -49,7 +52,7 @@ type ListProps<Item> = Omit<FlatListProps<Item>, 'contentOffset'> & {
onScrolledDownChange?: (isScrolledDown: boolean) => void onScrolledDownChange?: (isScrolledDown: boolean) => void
} }
export const List = forwardRef(function List<Item>( export const List = forwardRef(function List<Item extends {key: string}>(
props: ListProps<Item>, props: ListProps<Item>,
ref: React.Ref<FlatList<Item>>, ref: React.Ref<FlatList<Item>>,
) { ) {
@@ -126,6 +129,7 @@ export const List = forwardRef(function List<Item>(
return ( return (
<Animated.FlatList <Animated.FlatList
ref={ref} ref={ref}
keyExtractor={props.keyExtractor || (i => i.key)}
viewabilityConfig={viewabilityConfig} viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged} onViewableItemsChanged={onViewableItemsChanged}
/** /**
@@ -150,14 +154,6 @@ export const List = forwardRef(function List<Item>(
*/ */
right: 1, right: 1,
}} }}
/**
* iOS-only, should match `scrollIndicatorInsets`
* @see https://reactnative.dev/docs/scrollview#contentinset-ios
*/
contentInset={{
top: props.headerOffset ?? 0,
bottom: props.footerOffset ?? 0,
}}
/** /**
* Native only. On web, we use padding on `style` instead. * Native only. On web, we use padding on `style` instead.
*/ */
@@ -168,21 +164,21 @@ export const List = forwardRef(function List<Item>(
refreshControl={refreshControl} refreshControl={refreshControl}
{...(props as FlatListPropsWithLayout<Item>)} {...(props as FlatListPropsWithLayout<Item>)}
style={[ 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,
{ {
paddingTop: props.headerOffset, paddingTop: props.headerOffset,
paddingBottom: props.footerOffset, paddingBottom: props.footerOffset,
}, },
web([
/*
* On web, the List should always fill its container, otherwise
* `onScroll` will not work due to the entire page scrolling.
*/
a.h_full,
]), ]),
]} ]}
onScroll={onScroll} onScroll={onScroll}
/> />
) )
}) as <Item>( }) as <Item extends {key: string}>(
props: ListProps<Item> & {ref?: React.Ref<FlatList<Item>>}, props: ListProps<Item> & {ref?: React.Ref<FlatList<Item>>},
) => React.ReactElement ) => React.ReactElement
+36
View File
@@ -1,7 +1,10 @@
import {useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {runOnJS} from 'react-native-reanimated' import {runOnJS} from 'react-native-reanimated'
import {List as OldList} from '#/view/com/util/List'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {List, ListScrollProvider, useListScrollHandler} from '#/components/List' import {List, ListScrollProvider, useListScrollHandler} from '#/components/List'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -36,6 +39,7 @@ type Item = {
const log = (msg: any) => console.log(msg) const log = (msg: any) => console.log(msg)
export function Inner() { export function Inner() {
const [old, setOld] = useState<boolean>(false)
const onScrollWorklet = useListScrollHandler(e => { const onScrollWorklet = useListScrollHandler(e => {
'worklet' 'worklet'
runOnJS(log)(`scroll ${e.contentOffset.y}`) runOnJS(log)(`scroll ${e.contentOffset.y}`)
@@ -43,6 +47,20 @@ export function Inner() {
return ( return (
<View style={[a.h_full_vh]}> <View style={[a.h_full_vh]}>
<View
style={[a.fixed, a.w_full, a.py_sm, a.px_xl, a.align_center, a.z_10]}>
<Button
label="Toggle Old/New List"
onPress={() => setOld(v => !v)}
size="tiny"
color="primary_subtle">
<ButtonText>
{old ? 'Testing old List' : 'Testing new List'}
</ButtonText>
</Button>
</View>
{!old ? (
<ListScrollProvider onScroll={onScrollWorklet}> <ListScrollProvider onScroll={onScrollWorklet}>
<List<Item> <List<Item>
data={items} data={items}
@@ -59,6 +77,24 @@ export function Inner() {
style={[a.debug]} style={[a.debug]}
/> />
</ListScrollProvider> </ListScrollProvider>
) : (
<ListScrollProvider onScroll={onScrollWorklet}>
<OldList
data={items}
keyExtractor={item => item.key}
headerOffset={100}
onScrolledDownChange={scrolledDown => {
console.log(`Scrolled down: ${scrolledDown}`)
}}
renderItem={({item}) => (
<View style={[a.p_md, a.border_b]}>
<Text>{item.title}</Text>
</View>
)}
style={[a.debug]}
/>
</ListScrollProvider>
)}
</View> </View>
) )
} }