Skip to content

feat: add sectionList #362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/hooks/use-scroll-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export function resolveScrollRef(ref: any) {
if (ref.current?.rlvRef) {
return ref.current?.rlvRef?._scrollComponent?._scrollViewRef;
}
// SectionList
if (ref.current?._wrapperListRef) {
return ref.current?._wrapperListRef?._listRef?._scrollRef
}
// ScrollView
return ref.current;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as React from 'react'
import { ForwardedRef, PropsWithChildren, ReactElement, RefAttributes } from 'react'
import { SectionList as RNSectionList, SectionListProps as RNSectionListProps } from 'react-native'
import {
NativeViewGestureHandlerProps,
RefreshControl,
ScrollView
} from 'react-native-gesture-handler'

export const nativeViewGestureHandlerProps = [
'shouldActivateOnStart',
'disallowInterruption'
] as const

const commonProps = [
'id',
'enabled',
'shouldCancelWhenOutside',
'hitSlop',
'cancelsTouchesInView',
'userSelect',
'activeCursor',
'mouseButton',
'enableContextMenu',
'touchAction'
] as const

const componentInteractionProps = ['waitFor', 'simultaneousHandlers', 'blocksHandlers'] as const

export const baseGestureHandlerProps = [
...commonProps,
...componentInteractionProps,
'onBegan',
'onFailed',
'onCancelled',
'onActivated',
'onEnded',
'onGestureEvent',
'onHandlerStateChange'
] as const

export const nativeViewProps = [
...baseGestureHandlerProps,
...nativeViewGestureHandlerProps
] as const

export const SectionList = React.forwardRef((props, ref) => {
const refreshControlGestureRef = React.useRef<RefreshControl>(null)
const { waitFor, refreshControl, ...rest } = props
const sectionListProps = {}
const scrollViewProps = {}
for (const [propName, value] of Object.entries(rest)) {
if ((nativeViewProps as readonly string[]).includes(propName)) {
// @ts-ignore - this function cannot have generic type so we have to ignore this error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
scrollViewProps[propName] = value
} else {
// @ts-ignore - this function cannot have generic type so we have to ignore this error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
sectionListProps[propName] = value
}
}

return (
// @ts-ignore - this function cannot have generic type so we have to ignore this error
<RNSectionList
ref={ref}
{...sectionListProps}
renderScrollComponent={(scrollProps) => (
<ScrollView
{...{
...scrollProps,
...scrollViewProps,
waitFor: [...toArray(waitFor ?? []), refreshControlGestureRef]
}}
/>
)}
// @ts-ignore we don't pass `refreshing` prop as we only want to override the ref
refreshControl={
refreshControl
? React.cloneElement(refreshControl, {
// @ts-ignore for reasons unknown to me, `ref` doesn't exist on the type inferred by TS
ref: refreshControlGestureRef
})
: undefined
}
/>
)
}) as <ItemT = any>(
props: PropsWithChildren<
RNSectionListProps<ItemT> & RefAttributes<SectionList<ItemT>> & NativeViewGestureHandlerProps
>,
ref: ForwardedRef<SectionList<ItemT>>
) => ReactElement | null
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type SectionList<ItemT = any> = typeof RNSectionList<ItemT>

export function toArray<T>(object: T | T[]): T[] {
if (!Array.isArray(object)) {
return [object]
}
return object
}
48 changes: 48 additions & 0 deletions src/views/SectionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable curly */
import React, { RefObject, useImperativeHandle } from 'react'
import { Platform, SectionList as RNSectionList, SectionListProps } from 'react-native'
import { NativeViewGestureHandlerProps } from 'react-native-gesture-handler'
import { SectionList as RNGHSectionList } from '../react-native-gesture-handler-extend/SectionListGestureComponent'
import { useScrollHandlers } from '../hooks/use-scroll-handlers'

type Props<T = any> = SectionListProps<T> &
Partial<NativeViewGestureHandlerProps> &
React.RefAttributes<RNSectionList> & {
/**
* By default refresh control gesture will work in top 15% area of the ScrollView. You can set a different value here.
*
* Accepts a value between 0-1.
*/
refreshControlGestureArea?: number
}

function $SectionList<T>(props: Props<T>, ref: React.ForwardedRef<RefObject<RNSectionList>>) {
const handlers = useScrollHandlers<RNSectionList>({
hasRefreshControl: !!props.refreshControl,
refreshControlBoundary: props.refreshControlGestureArea || 0.15
})
useImperativeHandle(ref, () => handlers.ref)
const ScrollComponent = Platform.OS === 'web' ? RNSectionList : RNGHSectionList

return (
//@ts-ignore
<ScrollComponent
{...props}
{...handlers}
//@ts-ignore
onScroll={(event) => {
handlers.onScroll(event)
props.onScroll?.(event)
}}
bounces={false}
//@ts-ignore
onLayout={(event) => {
handlers.onLayout()
props.onLayout?.(event)
}}
scrollEventThrottle={1}
/>
)
}

export const SectionList = React.forwardRef($SectionList) as unknown as typeof RNSectionList