Skip to content

Commit

Permalink
feat: allow header to move down on pull to refresh on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
andreialecu committed Jan 15, 2022
1 parent dd6d2ef commit e32a4b8
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 11 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const Example = () => {
|:----:|:----:|:----:|:----:|
|HeaderComponent|`((props: TabBarProps<T>) => React.ReactElement) \| null \| undefined`||@obsolete use `renderHeader` instead. This property will be removed in 5.0.0|
|TabBarComponent|`((props: TabBarProps<T>) => React.ReactElement) \| null \| undefined`|`MaterialTabBar`|@obsolete use `renderTabBar` instead. This property will be removed in 5.0.0|
|allowHeaderOverscroll|`boolean`|`false`|Whether the header moves down during overscrolling (for example on pull-to-refresh on iOS) or sticks to the top|
|cancelLazyFadeIn|`boolean \| undefined`|||
|cancelTranslation|`boolean \| undefined`|||
|containerStyle|`StyleProp<ViewStyle>`|||
Expand All @@ -207,7 +208,7 @@ const Example = () => {
|revealHeaderOnScroll|`boolean \| undefined`||Reveal header when scrolling down. Implements diffClamp.|
|snapThreshold|`number \| null \| undefined`|`null`|Percentage of header height to define as the snap point. A number between 0 and 1, or `null` to disable snapping.|
|tabBarHeight|`number \| undefined`||Is optional, but will optimize the first render.|
|width|`number \| undefined`||Optionally set a custom width of the container. Defaults to the window width.|
|width|`number \| undefined`||Custom width of the container. Defaults to the window width.|

### Tabs.Tab

Expand Down Expand Up @@ -343,6 +344,8 @@ Any additional props are passed to the pressable component.
|scrollEnabled|`boolean \| undefined`|||
|style|`StyleProp<ViewStyle>`||Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles.|



# Known issues

## Android FlatList pull to refresh
Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import AnimatedHeader from './AnimatedHeader'
import CenteredEmptyList from './CenteredEmptyList'
import Default from './Default'
import DynamicTabs from './DynamicTabs'
import HeaderOverscrollExample from './HeaderOverscroll'
import Lazy from './Lazy'
import MinHeaderHeight from './MinHeaderHeight'
import OnTabChange from './OnTabChange'
Expand Down Expand Up @@ -49,6 +50,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [
MinHeaderHeight,
AnimatedHeader,
AndroidSharedPullToRefresh,
HeaderOverscrollExample,
]

const ExampleList: React.FC<object> = () => {
Expand Down
18 changes: 18 additions & 0 deletions example/src/HeaderOverscroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'

import ExampleComponent from './Shared/ExampleComponent'
import { buildHeader } from './Shared/Header'
import { ExampleComponentType } from './types'

const title = 'Header Overscroll'
const description = 'Pull to Refresh appears above header (iOS)'

const Header = buildHeader(title, description)

const HeaderOverscrollExample: ExampleComponentType = () => {
return <ExampleComponent renderHeader={Header} allowHeaderOverscroll />
}

HeaderOverscrollExample.title = title

export default HeaderOverscrollExample
21 changes: 17 additions & 4 deletions example/src/Shared/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,33 @@ import { TabBarProps } from 'react-native-collapsible-tab-view'

type Props = {
title: string
description?: string
height?: number
}

export const HEADER_HEIGHT = 250

export const Header = ({
title,
description,
height = HEADER_HEIGHT,
}: TabBarProps & Props) => {
return (
<View style={[styles.root, { height }]}>
<Text style={styles.text}>{title}</Text>
<Text style={styles.title}>
{title}
{'\n'}<Text style={styles.description}>{description}</Text>
</Text>
</View>
)
}

function buildHeader<T extends TabBarProps<any>>(title: string) {
function buildHeader<T extends TabBarProps<any>>(
title: string,
description: string
) {
const NewHeader = (props: T) => {
return <Header title={title} {...props} />
return <Header title={title} description={description} {...props} />
}

return NewHeader
Expand All @@ -37,11 +45,16 @@ const styles = StyleSheet.create({
alignItems: 'center',
padding: 16,
},
text: {
title: {
color: 'white',
fontSize: 24,
textAlign: 'center',
},
description: {
color: 'white',
fontSize: 16,
textAlign: 'center',
},
})

export default Header
6 changes: 6 additions & 0 deletions src/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const Container = React.memo(
onIndexChange,
onTabChange,
width: customWidth,
allowHeaderOverscroll,
},
ref
) => {
Expand All @@ -106,6 +107,10 @@ export const Container = React.memo(
)

const contentInset = useDerivedValue(() => {
if (allowHeaderOverscroll) return 0

// necessary for the refresh control on iOS to be positioned underneath the header
// this also adjusts the scroll bars to clamp underneath the header area
return IS_IOS
? (headerHeight.value || 0) + (tabBarHeight.value || 0)
: 0
Expand Down Expand Up @@ -467,6 +472,7 @@ export const Container = React.memo(
contentHeights,
headerTranslateY,
width,
allowHeaderOverscroll,
}}
>
<Animated.View
Expand Down
10 changes: 4 additions & 6 deletions src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export const useScrollHandlerY = (name: TabName) => {
snappingTo,
contentHeights,
indexDecimal,
allowHeaderOverscroll,
} = useTabsContext()

const enabled = useSharedValue(false)
Expand Down Expand Up @@ -360,12 +361,9 @@ export const useScrollHandlerY = (name: TabName) => {
(containerHeight.value || 0) +
contentInset.value
// make sure the y value is clamped to the scrollable size (clamps overscrolling)
scrollYCurrent.value = interpolate(
y,
[0, clampMax],
[0, clampMax],
Extrapolate.CLAMP
)
scrollYCurrent.value = allowHeaderOverscroll
? y
: interpolate(y, [0, clampMax], [0, clampMax], Extrapolate.CLAMP)
} else {
const { y } = event.contentOffset
scrollYCurrent.value = y
Expand Down
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ export type CollapsibleProps = {
* Custom width of the container. Defaults to the window width.
*/
width?: number

/**
* Whether the header moves down during overscrolling (for example on pull-to-refresh on iOS) or sticks to the top
*
* @default false
*/
allowHeaderOverscroll: boolean
}

export type ContextType<T extends TabName = TabName> = {
Expand Down Expand Up @@ -234,6 +241,13 @@ export type ContextType<T extends TabName = TabName> = {
headerTranslateY: Animated.SharedValue<number>

width: number

/**
* Whether the header moves down during overscrolling (for example on pull-to-refresh on iOS) or sticks to the top
*
* @default false
*/
allowHeaderOverscroll: boolean
}

export type ScrollViewProps = ComponentProps<typeof Animated.ScrollView>
Expand Down

0 comments on commit e32a4b8

Please sign in to comment.