Add header/footer offset
This commit is contained in:
@@ -5,7 +5,7 @@ import Animated, {
|
|||||||
useAnimatedScrollHandler,
|
useAnimatedScrollHandler,
|
||||||
} from 'react-native-reanimated'
|
} 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'
|
import {useListScrollContext} from '#/components/List/ListScrollProvider'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -16,16 +16,31 @@ export {
|
|||||||
/**
|
/**
|
||||||
* Cleaned up FlatList without some problematic props.
|
* 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'> & {
|
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
|
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>(
|
export const List = forwardRef(function List<Item>(
|
||||||
props: ListProps<Item>,
|
props: ListProps<Item>,
|
||||||
ref: React.Ref<FlatList<Item>>,
|
ref: React.Ref<FlatList<Item>>,
|
||||||
) {
|
) {
|
||||||
|
const t = useTheme()
|
||||||
const scrollHandlers = useListScrollContext()
|
const scrollHandlers = useListScrollContext()
|
||||||
const onScroll = useAnimatedScrollHandler({
|
const onScroll = useAnimatedScrollHandler({
|
||||||
onScroll(e, ctx) {
|
onScroll(e, ctx) {
|
||||||
@@ -71,7 +86,7 @@ export const List = forwardRef(function List<Item>(
|
|||||||
}, [props.onItemSeen])
|
}, [props.onItemSeen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.FlatList<Item>
|
<Animated.FlatList
|
||||||
ref={ref}
|
ref={ref}
|
||||||
viewabilityConfig={viewabilityConfig}
|
viewabilityConfig={viewabilityConfig}
|
||||||
onViewableItemsChanged={onViewableItemsChanged}
|
onViewableItemsChanged={onViewableItemsChanged}
|
||||||
@@ -82,13 +97,48 @@ export const List = forwardRef(function List<Item>(
|
|||||||
* @see https://github.com/bluesky-social/social-app/pull/7131
|
* @see https://github.com/bluesky-social/social-app/pull/7131
|
||||||
*/
|
*/
|
||||||
automaticallyAdjustsScrollIndicatorInsets={false}
|
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>)}
|
{...(props as FlatListPropsWithLayout<Item>)}
|
||||||
style={[
|
style={[
|
||||||
/*
|
/*
|
||||||
* On web, the List should always fill its container, otherwise
|
* On web, the List should always fill its container, otherwise
|
||||||
* `onScroll` will not work due to the entire page scrolling.
|
* `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}
|
onScroll={onScroll}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export function Inner() {
|
|||||||
<ListScrollProvider onScroll={onScrollWorklet}>
|
<ListScrollProvider onScroll={onScrollWorklet}>
|
||||||
<List<Item>
|
<List<Item>
|
||||||
data={items}
|
data={items}
|
||||||
|
headerOffset={100}
|
||||||
|
footerOffset={100}
|
||||||
renderItem={({item}) => (
|
renderItem={({item}) => (
|
||||||
<View style={[a.p_md, a.border_b]}>
|
<View style={[a.p_md, a.border_b]}>
|
||||||
<Text>{item.title}</Text>
|
<Text>{item.title}</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user