Skip to content
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
66 changes: 66 additions & 0 deletions src/components/FactoryList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react'
import {
FlatList,
ScrollView,
SectionList,
SectionListProps,
} from 'react-native'

import type { Params } from '../types'

import { useSmartScroll } from '../useSmartScroll'

// TODO generic type
interface Props<T> extends SectionListProps<T>, Params {
readonly children: React.ReactNode
}

export const listFactory = <T,>(
ListComponent: typeof FlatList | typeof SectionList | typeof ScrollView
) => {
const ListFactory = ({
children,
scrollEnabled,
onLayout,
onContentSizeChange,
horizontal,
onSmartScrollStatusChange,
...props
}: Props<T>) => {
// let Component: typeof FlatList | typeof SectionList | typeof ScrollView

// switch (ListComponent) {
// case 'FlatList':
// Component = FlatList
// break

// case 'SectionList':
// Component = SectionList
// break

// default:
// Component = ScrollView
// break
// }

const { handleContentSizeChange, handleLayout, isScrollEnabled } =
useSmartScroll({
horizontal,
onSmartScrollStatusChange,
onLayout,
onContentSizeChange,
})

return (
<ListComponent
{...props}
horizontal={horizontal}
scrollEnabled={isScrollEnabled}
onLayout={handleLayout}
onContentSizeChange={handleContentSizeChange}
/>
)
}

return ListFactory
}
41 changes: 41 additions & 0 deletions src/components/FlatList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import { FlatList as BaseFlatList, FlatListProps } from 'react-native'
import { listFactory } from './FactoryList'

// import type { Params } from '../types'

// import { useSmartScroll } from '../useSmartScroll'

// interface Props<T> extends FlatListProps<T>, Params {
// readonly children: React.ReactNode
// }

// export const FlatList = <T,>({
// children,
// scrollEnabled,
// onLayout,
// onContentSizeChange,
// horizontal,
// onSmartScrollStatusChange,
// ...props
// }: Props<T>) => {
// const { handleContentSizeChange, handleLayout, isScrollEnabled } =
// useSmartScroll({
// horizontal,
// onSmartScrollStatusChange,
// onLayout,
// onContentSizeChange,
// })

// return (
// <BaseFlatList
// {...props}
// horizontal={horizontal}
// scrollEnabled={isScrollEnabled}
// onLayout={handleLayout}
// onContentSizeChange={handleContentSizeChange}
// />
// )
// }

export const FlatList = listFactory<FlatListProps<T>>(BaseFlatList)
43 changes: 43 additions & 0 deletions src/components/ScrollView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import { ScrollView as BaseScrollView, ScrollViewProps } from 'react-native'

import type { Params } from '../types'

import { useSmartScroll } from '../useSmartScroll'
import { listFactory } from './FactoryList'

interface Props extends ScrollViewProps, Params {
readonly children: React.ReactNode
}

// export const ScrollView = ({
// children,
// scrollEnabled,
// onLayout,
// onContentSizeChange,
// horizontal,
// onSmartScrollStatusChange,
// ...props
// }: Props) => {
// const { handleContentSizeChange, handleLayout, isScrollEnabled } =
// useSmartScroll({
// horizontal,
// onSmartScrollStatusChange,
// onLayout,
// onContentSizeChange,
// })

// return (
// <BaseScrollView
// {...props}
// horizontal={horizontal}
// scrollEnabled={isScrollEnabled}
// onLayout={handleLayout}
// onContentSizeChange={handleContentSizeChange}
// >
// {children}
// </BaseScrollView>
// )
// }

export const ScrollView = listFactory<ScrollViewProps>(BaseScrollView)
38 changes: 38 additions & 0 deletions src/components/SectionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import { SectionList as BaseSectionList, SectionListProps } from 'react-native'

import type { Params } from '../types'

import { useSmartScroll } from '../useSmartScroll'

interface Props<T> extends SectionListProps<T>, Params {
readonly children: React.ReactNode
}

export const SectionList = <T,>({
children,
scrollEnabled,
onLayout,
onContentSizeChange,
horizontal,
onSmartScrollStatusChange,
...props
}: Props<T>) => {
const { handleContentSizeChange, handleLayout, isScrollEnabled } =
useSmartScroll({
horizontal,
onSmartScrollStatusChange,
onLayout,
onContentSizeChange,
})

return (
<BaseSectionList
{...props}
horizontal={horizontal}
scrollEnabled={isScrollEnabled}
onLayout={handleLayout}
onContentSizeChange={handleContentSizeChange}
/>
)
}
75 changes: 4 additions & 71 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,5 @@
import React, { useCallback, useMemo, useState, useEffect } from 'react'
import { ScrollViewProps, ScrollView } from 'react-native'
export { FlatList } from './components/FlatList'
export { ScrollView } from './components/ScrollView'
export { SectionList } from './components/SectionList'

interface Props extends ScrollViewProps {
readonly children: React.ReactNode
onSmartScrollStatusChange?: (isScrollEnabled: boolean) => void
}

type HandleLayoutCallback = NonNullable<ScrollViewProps['onLayout']>
type HandleContentSizeChangeCallback = NonNullable<
ScrollViewProps['onContentSizeChange']
>

const SmartScrollContainer = ({
children,
scrollEnabled,
onLayout,
onContentSizeChange,
horizontal,
onSmartScrollStatusChange,
...props
}: Props) => {
const [wrapperWidth, setWrapperWidth] = useState(0)
const [wrapperHeight, setWrapperHeight] = useState(0)
const [contentSize, setContentSize] = useState(0)

const isScrollEnabled = useMemo(
() =>
scrollEnabled ||
(horizontal ? wrapperWidth : wrapperHeight) < contentSize,
[contentSize, horizontal, scrollEnabled, wrapperHeight, wrapperWidth]
)

useEffect(() => {
onSmartScrollStatusChange?.(isScrollEnabled)
}, [isScrollEnabled, onSmartScrollStatusChange])

const handleLayout = useCallback<HandleLayoutCallback>(
(e) => {
const { width, height } = e.nativeEvent.layout

setWrapperWidth(width)
setWrapperHeight(height)

onLayout?.(e)
},
[onLayout]
)

const handleContentSizeChange = useCallback<HandleContentSizeChangeCallback>(
(w, h) => {
setContentSize(horizontal ? w : h)

onContentSizeChange?.(w, h)
},
[horizontal, onContentSizeChange]
)

return (
<ScrollView
{...props}
horizontal={horizontal}
scrollEnabled={isScrollEnabled}
onLayout={handleLayout}
onContentSizeChange={handleContentSizeChange}
>
{children}
</ScrollView>
)
}

export default SmartScrollContainer
export { useSmartScroll } from './useSmartScroll'
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Params {
onSmartScrollStatusChange?: (isScrollEnabled: boolean) => void
}
59 changes: 59 additions & 0 deletions src/useSmartScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useCallback, useMemo, useState, useEffect } from 'react'
import { ScrollViewProps, VirtualizedListProps } from 'react-native'

// type Params = Pick<
// VirtualizedListProps<any>,
// 'horizontnal' | 'onLayout' | 'onContentSizeChange'
// > &

type HandleLayoutCallback = NonNullable<ScrollViewProps['onLayout']>
type HandleContentSizeChangeCallback = NonNullable<
ScrollViewProps['onContentSizeChange']
>

export const useSmartScroll = ({
horizontal,
onSmartScrollStatusChange,
onLayout,
onContentSizeChange,
}: Params) => {
const [wrapperWidth, setWrapperWidth] = useState(0)
const [wrapperHeight, setWrapperHeight] = useState(0)
const [contentSize, setContentSize] = useState(0)

const isScrollEnabled = useMemo(
() => (horizontal ? wrapperWidth : wrapperHeight) < contentSize,
[contentSize, horizontal, wrapperHeight, wrapperWidth]
)

useEffect(() => {
onSmartScrollStatusChange?.(isScrollEnabled)
}, [isScrollEnabled, onSmartScrollStatusChange])

const handleLayout = useCallback<HandleLayoutCallback>(
(e) => {
const { width, height } = e.nativeEvent.layout

setWrapperWidth(width)
setWrapperHeight(height)

onLayout?.(e)
},
[onLayout]
)

const handleContentSizeChange = useCallback<HandleContentSizeChangeCallback>(
(w, h) => {
setContentSize(horizontal ? w : h)

onContentSizeChange?.(w, h)
},
[horizontal, onContentSizeChange]
)

return {
handleLayout,
handleContentSizeChange,
isScrollEnabled,
}
}