Add header/footer offset

This commit is contained in:
Eric Bailey
2025-08-27 20:25:10 -05:00
parent 661d20d176
commit 0098d617ee
2 changed files with 56 additions and 4 deletions
+54 -4
View File
@@ -5,7 +5,7 @@ import Animated, {
useAnimatedScrollHandler,
} from 'react-native-reanimated'
import {atoms as a, web} from '#/alf'
import {atoms as a, useTheme, web} from '#/alf'
import {useListScrollContext} from '#/components/List/ListScrollProvider'
export {
@@ -16,16 +16,31 @@ export {
/**
* Cleaned up FlatList without some problematic props.
*
* - `contentOffset` - Doesn't work, use padding on `contentContainerStyle` instead.
* - `contentOffset` - Use `headerOffset` or `footerOffset`
*/
type ListProps<Item> = Omit<FlatListProps<Item>, 'contentOffset'> & {
/**
* Wrapper around `onViewableItemsChanged` that calls back with individual
* items IF they `item.isViewable` is true.
*/
onItemSeen?: (item: Item) => void
/**
* Sugar for adding padding to the top of the list to accommodate fixed
* headers. Also applies insets to the scroll indicators.
*/
headerOffset?: number
/**
* Sugar for adding padding to the bottom of the list to accommodate fixed
* footers. Also applies insets to the scroll indicators.
*/
footerOffset?: number
}
export const List = forwardRef(function List<Item>(
props: ListProps<Item>,
ref: React.Ref<FlatList<Item>>,
) {
const t = useTheme()
const scrollHandlers = useListScrollContext()
const onScroll = useAnimatedScrollHandler({
onScroll(e, ctx) {
@@ -71,7 +86,7 @@ export const List = forwardRef(function List<Item>(
}, [props.onItemSeen])
return (
<Animated.FlatList<Item>
<Animated.FlatList
ref={ref}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged}
@@ -82,13 +97,48 @@ export const List = forwardRef(function List<Item>(
* @see https://github.com/bluesky-social/social-app/pull/7131
*/
automaticallyAdjustsScrollIndicatorInsets={false}
/**
* For better UX, we default to true, but it can be disabled if needed.
* @see https://github.com/bluesky-social/social-app/pull/8529
*/
showsVerticalScrollIndicator
indicatorStyle={t.name === 'light' ? 'black' : 'white'}
scrollIndicatorInsets={{
top: props.headerOffset ?? 0,
bottom: props.footerOffset ?? 0,
/**
* May fix a bug where the scroll indicator is in the middle of the screen
* @see https://github.com/facebook/react-native/issues/26610
*/
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.
*/
contentOffset={
props.headerOffset ? {x: 0, y: props.headerOffset * -1} : undefined
}
{...(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),
web([
a.h_full,
{
paddingTop: props.headerOffset,
paddingBottom: props.footerOffset,
},
]),
]}
onScroll={onScroll}
/>
@@ -46,6 +46,8 @@ export function Inner() {
<ListScrollProvider onScroll={onScrollWorklet}>
<List<Item>
data={items}
headerOffset={100}
footerOffset={100}
renderItem={({item}) => (
<View style={[a.p_md, a.border_b]}>
<Text>{item.title}</Text>