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
+15 -19
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 paddingTop: props.headerOffset,
* `onScroll` will not work due to the entire page scrolling. paddingBottom: props.footerOffset,
*/ },
web([ web([
/*
* On web, the List should always fill its container, otherwise
* `onScroll` will not work due to the entire page scrolling.
*/
a.h_full, a.h_full,
{
paddingTop: props.headerOffset,
paddingBottom: props.footerOffset,
},
]), ]),
]} ]}
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
+52 -16
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,22 +47,54 @@ export function Inner() {
return ( return (
<View style={[a.h_full_vh]}> <View style={[a.h_full_vh]}>
<ListScrollProvider onScroll={onScrollWorklet}> <View
<List<Item> style={[a.fixed, a.w_full, a.py_sm, a.px_xl, a.align_center, a.z_10]}>
data={items} <Button
headerOffset={100} label="Toggle Old/New List"
footerOffset={100} onPress={() => setOld(v => !v)}
onScrolledDownChange={scrolledDown => { size="tiny"
console.log(`Scrolled down: ${scrolledDown}`) color="primary_subtle">
}} <ButtonText>
renderItem={({item}) => ( {old ? 'Testing old List' : 'Testing new List'}
<View style={[a.p_md, a.border_b]}> </ButtonText>
<Text>{item.title}</Text> </Button>
</View> </View>
)}
style={[a.debug]} {!old ? (
/> <ListScrollProvider onScroll={onScrollWorklet}>
</ListScrollProvider> <List<Item>
data={items}
headerOffset={100}
footerOffset={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>
) : (
<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>
) )
} }