Compare commits
60 Commits
#102-scrol
...
#126-scrol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef8ac83f3b | ||
|
|
95f2b99fbb | ||
|
|
3bc004c8f4 | ||
|
|
7f59e78f87 | ||
|
|
235d1a8efd | ||
|
|
c2875ea452 | ||
|
|
dd00177c16 | ||
|
|
158bcceeb7 | ||
|
|
d5f39e71e2 | ||
|
|
1643e99c92 | ||
|
|
0dd8befcc1 | ||
|
|
e973344d7f | ||
|
|
d5ddd6d27b | ||
|
|
f8686ad3ef | ||
|
|
c55cdba695 | ||
|
|
57b69fb8aa | ||
|
|
89b9409d05 | ||
|
|
d9fd875f17 | ||
|
|
23cdd7248d | ||
|
|
700cdac419 | ||
|
|
0247a0ab6f | ||
|
|
af9268e14d | ||
|
|
32582cc9ba | ||
|
|
5d79cd3f89 | ||
|
|
08c5958fc4 | ||
|
|
15578efb73 | ||
|
|
2bba9893cc | ||
|
|
7c2d1776c8 | ||
|
|
4132ca4d9a | ||
|
|
756ea48a23 | ||
|
|
b585a6a070 | ||
|
|
47594b66fb | ||
|
|
f4d77f44ce | ||
|
|
7bbe85c416 | ||
|
|
0f3d0b800b | ||
|
|
620a5ef91f | ||
|
|
d7e30e6eb9 | ||
|
|
99a5bc39f5 | ||
|
|
2e9d88c446 | ||
|
|
0cbf384b02 | ||
|
|
a06b1a4390 | ||
|
|
5251867f25 | ||
|
|
44db4f7882 | ||
|
|
30adeccf83 | ||
|
|
debf4ff8b9 | ||
|
|
afe974f9e3 | ||
|
|
577c7d4aa5 | ||
|
|
ba77e5742a | ||
|
|
1b605f0cba | ||
|
|
0297e38391 | ||
|
|
a4c91834e5 | ||
|
|
3300eff474 | ||
|
|
1b7b1f512a | ||
|
|
b70609c279 | ||
|
|
87f55cb4a0 | ||
|
|
b827ca6db0 | ||
|
|
cdb421265a | ||
|
|
3e4c65ec77 | ||
|
|
e0035433ca | ||
|
|
9f67f84c49 |
144
README.md
144
README.md
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
# Introduction
|
||||
# Introduction
|
||||
|
||||
<p align="center">
|
||||
Stickyheader.js is a simple React Native library, enabling to create a fully custom header for your iOS and Android apps.
|
||||
@ -34,7 +34,7 @@
|
||||
<h2> Features </h2>
|
||||
Stickyheader.js ships with 3 different use cases for sticky headers and a possibility to create fully custom header!
|
||||
|
||||
| Tabbed Header | Avatar Header | Details Header|
|
||||
| Tabbed Header | Avatar Header | Details Header|
|
||||
| :------: | :------: | :------: |
|
||||
|  || |
|
||||
|
||||
@ -62,34 +62,57 @@ export default TestScreen
|
||||
|
||||
Below are examples of those components and description of the props they are accepting.
|
||||
|
||||
## Shared props
|
||||
|
||||
### Tabbed Header, Details Header, Avatar Header
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :---------------------------: | :---------------------------------------------------: | :-------:| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------:|
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `contentContainerStyles` | `View.propTypes.style` | Yes | | Set style for content container |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `scrollRef` | `func or object` | Yes | `null` | ScrollView body ref. Allows programmatically scroll body [ScrollView](https://reactnative.dev/docs/scrollview#methods) |
|
||||
| `keyboardShouldPersistTaps` | `'always', 'never', 'handled', false, true` | Yes | `undefined` | Determines when the keyboard should stay visible after a tap.|
|
||||
|
||||
### Details Header, Avatar Header
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :---------------------------: | :---------------------------------------------------: | :-------:| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------:|
|
||||
| `hasBorderRadius` | `boolean` | No | `true` | Adds radius to header's right bottom border |
|
||||
| `image` | `ImageSourcePropType` | No | `require('../../assets/images/photosPortraitBrandon.png')` | Sets header image |
|
||||
| `leftTopIcon` | `ImageSourcePropType` | No | `require('../../assets/icons/iconCloseWhite.png')` | Set icon for left top button |
|
||||
| `leftTopIconOnPress` | `() => void` | No | `() => {}` | Define action on left top button press |
|
||||
| `rightTopIcon` | `ImageSourcePropType` | No | `require('../../assets/icons/Icon-Menu.png') ` | Set icon for right top button |
|
||||
| `rightTopIconOnPress` | `() => void` | No | `() => {}` | Define action on right top button press |
|
||||
|
||||
## Tabbed Header
|
||||
|
||||

|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :---------------------------: | :---------------------------------------------------: | :-------:| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------:|
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` | Sets height of folded header |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `title` | `string` | Yes | `"Mornin' Mark! \nReady for a quiz?"` | Sets header title |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `tabs` | `arrayOf(shape({}))` | Yes | `[{title: 'Popular',content: <RenderContent title="Popular Quizes" />},...]` | Array with tabs names and content |
|
||||
| `foregroundImage` |`oneOfType([object, number])` | Yes | | Set image in the foreground |
|
||||
| `header` |`func` | Yes | | Function that renders custom header |
|
||||
| `logo` | `func` | Yes | `require('../../assets/images/logo.png')` | Set header logo |
|
||||
| `logoStyle` | `style` | Yes | `{ height: 24, width: 142 }` | Set header logo style |
|
||||
| `logoContainerStyle` | `style` | Yes | `{ width: '100%', paddingHorizontal: 24, paddingTop: Platform.select({ ios: ifIphoneX(50, 40), android: 55 }), flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'}`| Set header logo container style |
|
||||
| `logoResizeMode` | `"contain", "cover", "stretch", "center", "repeat" ` | Yes | `"contain"` | Set header logo resize mode |
|
||||
| `foregroundImage` |`oneOfType([object, number])` | Yes | | Set image in the foreground |
|
||||
| `titleStyle` |`Text.propTypes.style` | Yes | | Set style for text in foreground |
|
||||
| `onRef` |`func` | Yes | | Reference callback. You can call goToPage(pageNumber) method through ref to programmatically navigate to given tab |
|
||||
| `rememberTabScrollPosition` |`bool` | Yes |`false` | When switching between tabs remember current scroll position |
|
||||
| `scrollEvent` |`func` | Yes | | Scroll event to apply custom animations |
|
||||
| `tabs` | `arrayOf(shape({}))` | Yes | `[{title: 'Popular',content: <RenderContent title="Popular Quizes" />},...]` | Array with tabs names and content |
|
||||
| `tabText` |`Text.propTypes.style` | Yes |`{fontSize: 16, lineHeight: 20, paddingHorizontal: 12, paddingVertical: 8, color: colors.white}` | Set inactive tab style |
|
||||
| `tabTextActiveStyle` |`Text.propTypes.style` | Yes |`{fontSize: 16, lineHeight: 20, paddingHorizontal: 12, paddingVertical: 8, color: colors.white}` | Set active tab stylee |
|
||||
| `tabTextContainerStyle` |`ViewPropTypes.style` | Yes |`{backgroundColor: colors.transparent, borderRadius: 18}` | Set inactive tab container style |
|
||||
| `tabTextContainerActiveStyle` |`ViewPropTypes.style` | Yes |`{backgroundColor: colors.darkMint}` | Set active tab container style |
|
||||
| `tabWrapperStyle` |`ViewPropTypes.style` | Yes |`{paddingVertical: 12}` | Set single tab container style |
|
||||
| `tabsContainerStyle` |`ViewPropTypes.style` | Yes | | Set whole tab bar container style |
|
||||
| `header` |`func` | Yes | | Fuction that renders custom header |
|
||||
| `scrollEvent` |`func` | Yes | | Scroll event to apply custom animations |
|
||||
| `title` | `string` | Yes | `"Mornin' Mark! \nReady for a quiz?"` | Sets header title |
|
||||
| `titleStyle` |`Text.propTypes.style` | Yes | | Set style for text in foreground |
|
||||
|
||||
[Check how to customise Tabbed Header example](docs/TABBEDHEADER.MD)
|
||||
|
||||
@ -97,55 +120,68 @@ Below are examples of those components and description of the props they are acc
|
||||
|
||||

|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :-------------------: | :--------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `leftTopIconOnPress` | `func` | Yes | `() => {}` | Define action on left top button press |
|
||||
| `rightTopIconOnPress` | `func` | Yes | `() => {}` | Define action on right top button press |
|
||||
| `leftTopIcon` | `number` | Yes | `require('../../assets/icons/iconCloseWhite.png')` | Set icon for left top button |
|
||||
| `rightTopIcon` | `number` | Yes | `require('../../assets/icons/Icon-Menu.png') ` | Set icon for right top button |
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` | Sets height of folded header |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `tag` | `string` | Yes | `"Product Designer"` | Sets header tag name |
|
||||
| `title` | `string` | Yes | `"Design System"` | Sets header title |
|
||||
| `image` | `number` | Yes | `require('../../assets/images/photosPortraitBrandon.png')` | Sets header image |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `hasBorderRadius` | `bool` | Yes | `true` | Adds radius to header's left bottom border |
|
||||
| `iconNumber` | `number` | Yes | `10` | Set amount of cards shown on icon |
|
||||
| Property | Type | Required | Default | Description |
|
||||
| :-------------------: | :--------------------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `iconNumber` | `number` | No | `10` | Set amount of cards shown on icon |
|
||||
| `tag` | `string` | No | `"Product Designer"` | Sets header tag name |
|
||||
| `title` | `string` | No | `"Design System"` | Sets header title |
|
||||
|
||||
|
||||
## Avatar Header
|
||||
|
||||

|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :-------------------: | :--------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `leftTopIconOnPress` | `func` | Yes | `() => {}` | Define action on left top button press |
|
||||
| `rightTopIconOnPress` | `func` | Yes | `() => {}` | Define action on right top button press |
|
||||
| `leftTopIcon` | `number` | Yes | `require('../../assets/icons/iconCloseWhite.png')` | Set icon for left top button |
|
||||
| `rightTopIcon` | `number` | Yes | `require('../../assets/icons/Icon-Menu.png') ` | Set icon for right top button |
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` | Sets height of folded header |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `title` | `string` | Yes | `"Brandon` | Sets header title |
|
||||
| `subtitle` | `string` | Yes | `"Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer."`| Sets description(subtitle) section |
|
||||
| `image` | `number` | Yes | `require('../../assets/images/photosPortraitBrandon.png')` | Sets header image |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `hasBorderRadius` | `bool` | Yes | `true` | Adds radius to header's left bottom border |
|
||||
| `parallaxHeight` | `number` | Yes | | Set parallax header height |
|
||||
| `transparentHeader` | `bool` | Yes | `false` | Set header transparency to render custom header |
|
||||
| `snapStartThreshold` | `number` | Yes | | Set start value Threshold of snap |
|
||||
| `snapStopThreshold` | `number` | Yes | | Set stop value Threshold of snap |
|
||||
| `snapValue` | `number` | Yes | | Set value where header is closed |
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :-------------------: | :---------------------------------------------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `foreground` | `() => ReactElement` | No | - | Function that renders the foreground of the header |
|
||||
| `header` | `() => ReactElement` | No | - | Function that renders custom header |
|
||||
| `parallaxHeight` | `number` | No | - | Set parallax header height |
|
||||
| `scrollEvent` | `(event: NativeSyntheticEvent<NativeScrollEvent>) => void` | No | `require('../../assets/icons/Icon-Menu.png') ` | Scroll event to apply custom animations |
|
||||
| `snapStartThreshold` | `number` | No | - | Set start value Threshold of snap |
|
||||
| `snapStopThreshold` | `number` | No | - | Set stop value Threshold of snap |
|
||||
| `snapValue` | `number` | No | - | Set value where header is closed |
|
||||
| `subtitle` | `string` | No | `"Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer."`| Sets description(subtitle) section |
|
||||
| `title` | `string` | No | `"Brandon` | Sets header title |
|
||||
| `transparentHeader` | `boolean` | No | `false` | Set header transparency to render custom header |
|
||||
|
||||
## Custom Header
|
||||
## Custom Header
|
||||
|
||||
[Custom header props and example](docs/CUSTOM.md)
|
||||
|
||||
## Handling StickyParallaxHeader body ScrollView reference
|
||||
### As callback function
|
||||
```
|
||||
<StickyParallaxHeader
|
||||
scrollRef={(ref) => {
|
||||
paralaxScrollRef.current = ref;
|
||||
}}
|
||||
foreground={this.renderForeground()}
|
||||
header={this.renderHeader()}
|
||||
/>
|
||||
{renderBody()}
|
||||
<StickyParallaxHeader/>
|
||||
```
|
||||
|
||||
### As useRef value
|
||||
```
|
||||
const paralaxScrollRef = useRef(null);
|
||||
|
||||
<StickyParallaxHeader
|
||||
scrollRef={paralaxScrollRef}
|
||||
foreground={this.renderForeground()}
|
||||
header={this.renderHeader()}
|
||||
/>
|
||||
{renderBody()}
|
||||
<StickyParallaxHeader/>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Handling nested scrollables
|
||||
|
||||
[Handling nested flatlist props and example](docs/CUSTOM.md#Tips)
|
||||
|
||||
|
||||
<h1 id="Getting-Started">Getting Started</h1>
|
||||
|
||||
## Prerequisites
|
||||
@ -204,4 +240,4 @@ $ yarn add react-native-sticky-parallax-header
|
||||
|
||||
|
||||
# License
|
||||
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
||||
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
@ -21,6 +21,7 @@ $ yarn test
|
||||
* adding video or screenshot is very beneficial but it's not mandatory
|
||||
* additionally please remember to add appropriate Pull Request title from following:
|
||||
* `[RNS-XX] short description` - for normal feature branches
|
||||
* remember one pull request should always address one issue or feature
|
||||
|
||||
## Code structure
|
||||
```
|
||||
@ -36,3 +37,4 @@ src/
|
||||
* Name branch according to your ticket following this pattern: RNS-XX-short_description
|
||||
* Imports and exports inside `index.js` files eg. `screens/index.js`, `components/index.js` should be alfabetically
|
||||
* Style names in `ComponentName.styles.js` should be ordered alfabetically
|
||||
* Please use commit lint and follow commit naming convention (https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
|
||||
107
docs/CUSTOM.md
107
docs/CUSTOM.md
@ -2,30 +2,36 @@
|
||||
|
||||
## Custom Header Props
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
| :------------------------------: | :-----------------: | :--------: | :-------: | :-------------------------------------------------------------: |
|
||||
| `background` | `node` | No | - | This renders background component |
|
||||
| `backgroundImage` | `number` | No | - | This renders background image instead of background component |
|
||||
| `backgroundColor` | `string` | Yes |`""` | Header background color |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `children` | `node` | No | - | This renders all the children inside the component |
|
||||
| `foreground` | `node` | Yes | - | This renders foreground component |
|
||||
| `header` | `node` | Yes | - | This renders header component |
|
||||
| `headerHeight` | `number` | No | `92` | Sets height of folded header |
|
||||
| `headerSize` | `func` | No | - | Returns size of header for current device |
|
||||
| `initialPage` | `number` | No | `0` | Set initial page of tab bar |
|
||||
| `onChangeTab` | `func` | No | - | Tab change event |
|
||||
| `onEndReached` | `func` | No | - | Tab change event |
|
||||
| `parallaxHeight` | `number` | No | `0` | Sets height of opened header |
|
||||
| `snapToEdge` | `bool` | No | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `scrollEvent` | `func` | No | - | Returns offset of header to apply custom animations |
|
||||
| `tabs` | `arrayOf(string)` | No | - | Array of tab names |
|
||||
| `tabTextStyle` | `shape({})` | No | {} | Text styles of tab |
|
||||
| `tabTextActiveStyle` | `shape({})` | No | {} | Text styles of active tab |
|
||||
| `tabTextContainerStyle` | `shape({})` | No | {} | Container styles of tab |
|
||||
| `tabTextContainerActiveStyle` | `shape({})` | No | {} | Container styles of active tab |
|
||||
| `tabsContainerBackgroundColor` | `string` | No | - | Background color of tab bar container |
|
||||
| `tabsWrapperStyle` | `shape({})` | No | {} | Tabs Wrapper styles |
|
||||
| Property | Type | Required | Default | Description |
|
||||
| :------------------------------: | :-------------------------------------------------------------------: | :--------: | :-------: | :-------------------------------------------------------------: |
|
||||
| `backgroundColor` | `string` | Yes | `""` | Header background color |
|
||||
| `backgroundImage` | `ImageSourcePropType` | No | - | This renders background image instead of background component |
|
||||
| `background` | `ReactElement` | No | - | This renders background component |
|
||||
| `bounces` | `boolean` | Yes | `true` | Bounces on swiping up |
|
||||
| `children` | `ReactElement` | No | - | This renders all the children inside the component |
|
||||
| `foreground` | `ReactElement` | Yes | - | This renders foreground component |
|
||||
| `headerHeight` | `number` | No | `92` | Sets height of folded header |
|
||||
| `headerSize` | `({ x, y, width, height }: HeaderSizeProps) => void` | No | - | Handler that is called when header's size changes |
|
||||
| `header` | `ReactElement` | Yes | - | This renders header component |
|
||||
| `initialPage` | `number` | No | `0` | Set initial page of tab bar |
|
||||
| `onChangeTab` | `({ i, ref, from }: { from: number; i: number; ref: any; }) => void;` | No | - | Tab change event |
|
||||
| `onEndReached` | `() => void` | No | - | Reached end event
|
||||
| `onTopReached` | `() => void` | No | - | Reached top event |
|
||||
| `parallaxHeight` | `number` | No | `0` | Sets height of opened header |
|
||||
| `scrollEvent` | `(event: NativeSyntheticEvent<NativeScrollEvent>) => void` | No | - | Returns offset of header to apply custom animations |
|
||||
| `snapStartThreshold` | `number` | No | - | Set start value Threshold of snap |
|
||||
| `snapStopThreshold` | `number` | No | - | Set stop value Threshold of snap |
|
||||
| `snapToEdge` | `boolean` | No | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `snapValue` | `number` | No | - | Set value where header is closed |
|
||||
| `tabTextActiveStyle` | `TextStyle` | No | {} | Text styles of active tab |
|
||||
| `tabTextContainerActiveStyle` | `ViewStyle` | No | {} | Container styles of active tab |
|
||||
| `tabTextContainerStyle` | `ViewStyle` | No | {} | Container styles of tab |
|
||||
| `tabTextStyle` | `TextStyle` | No | {} | Text styles of tab |
|
||||
| `tabsContainerBackgroundColor` | `ViewStyle` | No | - | Background color of tab bar container |
|
||||
| `tabsWrapperStyle` | `ViewStyle` | No | {} | Tabs Wrapper styles |
|
||||
| `tabs` | `{ content: ReactElement; title: string; }[]` | No | - | Array of tab names |
|
||||
| `tabsContainerStyle` | `ViewStyle` | No | - | Set whole tab bar container style |
|
||||
| `transparentHeader` | `boolean` | No | `false` | Set header transparency to render custom header |
|
||||
|
||||
<h1 id="Usage">Usage</h1>
|
||||
|
||||
@ -180,4 +186,55 @@ class TabScreen extends React.Component {
|
||||
```
|
||||
|
||||
## Tips
|
||||
In order to nest scrollable component use `scrollEnabled={false}` on it and move all the logic to the header eg. by using `onEndReached` prop.
|
||||
In order to nest scrollable component use `scrollEnabled={false}` on it and move all the logic to the header eg. by using `onEndReached` and `onTopReached` props. You can find example in CardScreen.js it's really basic so probably you will want to extend it somehow:
|
||||
|
||||
```jsx
|
||||
|
||||
shouldBeEnabled = () => {
|
||||
const {
|
||||
endReached,
|
||||
stickyHeaderEndReached,
|
||||
topReached,
|
||||
stickyHeaderTopReached
|
||||
} = this.state
|
||||
const bottomCondition =
|
||||
endReached && stickyHeaderEndReached
|
||||
const topCondition =
|
||||
topReached && stickyHeaderTopReached
|
||||
return bottomCondition || !topCondition
|
||||
}
|
||||
|
||||
onScroll = ({nativeEvent}) => {
|
||||
const {contentOffset, layoutMeasurement, contentSize} = nativeEvent;
|
||||
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - 20) {
|
||||
this.setState({endReached: true, topReached: false})
|
||||
}
|
||||
|
||||
if (contentOffset.y <= 0) {
|
||||
this.setState({topReached: true, endReached: false, stickyHeaderTopReached:true})
|
||||
}
|
||||
}
|
||||
|
||||
renderFlatlistContent = (user) => (
|
||||
<View style={styles.flatlistContainer}>
|
||||
<FlatList
|
||||
data={user.cards}
|
||||
renderItem={({item, index}) => (
|
||||
<QuizCard
|
||||
data={item}
|
||||
num={index}
|
||||
key={item.question}
|
||||
cardsAmount={100}
|
||||
/>
|
||||
)}
|
||||
onScroll={this.onScroll}
|
||||
scrollEnabled={
|
||||
Platform.OS === 'android' ? true : this.shouldBeEnabled()
|
||||
}
|
||||
nestedScrollEnabled
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"react-native-gesture-handler": "^1.2.1",
|
||||
"react-native-iphone-x-helper": "^1.2.1",
|
||||
"react-native-screens": "^2.5.0",
|
||||
"react-native-sticky-parallax-header": "^0.2.1",
|
||||
"react-native-sticky-parallax-header": "^0.3.0",
|
||||
"react-navigation": "^3.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { StyleSheet, Platform } from 'react-native'
|
||||
import { ifIphoneX } from '../constants/utils'
|
||||
import { ifIphoneX } from './utils'
|
||||
import colors from './colors'
|
||||
import constants from './constants'
|
||||
|
||||
@ -16,6 +16,13 @@ const screenStyles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 24
|
||||
},
|
||||
flatlistContainer: {
|
||||
width: constants.deviceWidth,
|
||||
height: constants.deviceHeight - 80,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 24
|
||||
},
|
||||
contentText: {
|
||||
fontSize: 24,
|
||||
lineHeight: 28,
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
import React from 'react'
|
||||
import { Text, View, Image, TouchableOpacity, StatusBar, Animated } from 'react-native'
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
Image,
|
||||
TouchableOpacity,
|
||||
StatusBar,
|
||||
Animated,
|
||||
FlatList,
|
||||
} from 'react-native'
|
||||
import StickyParallaxHeader from 'react-native-sticky-parallax-header'
|
||||
import { withNavigation } from 'react-navigation'
|
||||
import { constants, sizes, colors } from '../../constants'
|
||||
@ -12,9 +20,13 @@ class CardScreen extends React.Component {
|
||||
super(props)
|
||||
this.state = {
|
||||
headerLayout: {
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
height: 0,
|
||||
},
|
||||
topReached: true,
|
||||
endReached: false,
|
||||
stickyHeaderEndReached: false,
|
||||
stickyHeaderTopReached: true,
|
||||
};
|
||||
|
||||
this.scrollY = new ValueXY()
|
||||
}
|
||||
@ -125,6 +137,52 @@ class CardScreen extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
shouldBeEnabled = () => {
|
||||
const {
|
||||
endReached,
|
||||
stickyHeaderEndReached,
|
||||
topReached,
|
||||
stickyHeaderTopReached
|
||||
} = this.state
|
||||
const bottomCondition =
|
||||
endReached && stickyHeaderEndReached
|
||||
const topCondition =
|
||||
topReached && stickyHeaderTopReached
|
||||
return bottomCondition || !topCondition
|
||||
}
|
||||
|
||||
onScroll = ({nativeEvent}) => {
|
||||
const {contentOffset, layoutMeasurement, contentSize} = nativeEvent;
|
||||
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - 20) {
|
||||
this.setState({endReached: true, topReached: false})
|
||||
}
|
||||
|
||||
if (contentOffset.y <= 0) {
|
||||
this.setState({topReached: true, endReached: false, stickyHeaderTopReached:true})
|
||||
}
|
||||
}
|
||||
|
||||
renderFlatlistContent = (user) => (
|
||||
<View style={styles.flatlistContainer}>
|
||||
<FlatList
|
||||
data={user.cards}
|
||||
renderItem={({item, index}) => (
|
||||
<QuizCard
|
||||
data={item}
|
||||
num={index}
|
||||
key={item.question}
|
||||
cardsAmount={100}
|
||||
/>
|
||||
)}
|
||||
onScroll={this.onScroll}
|
||||
scrollEnabled={
|
||||
constants.isAndroid ? true : this.shouldBeEnabled()
|
||||
}
|
||||
nestedScrollEnabled
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
||||
renderContent = user => (
|
||||
<View style={styles.content}>
|
||||
{user.cards.map((data, i, arr) => (
|
||||
@ -133,6 +191,20 @@ class CardScreen extends React.Component {
|
||||
</View>
|
||||
)
|
||||
|
||||
stickyHeaderEndReached = () => {
|
||||
this.setState({
|
||||
stickyHeaderEndReached: true,
|
||||
stickyHeaderTopReached: false,
|
||||
})
|
||||
}
|
||||
|
||||
stickyHeaderTopReached = () => {
|
||||
this.setState({
|
||||
stickyHeaderTopReached: true,
|
||||
stickyHeaderEndReached: false,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props
|
||||
const user = navigation.getParam('user', {})
|
||||
@ -149,8 +221,10 @@ class CardScreen extends React.Component {
|
||||
headerSize={this.setHeaderSize}
|
||||
headerHeight={sizes.cardScreenHeaderHeight}
|
||||
background={this.renderBackground(user)}
|
||||
>
|
||||
{this.renderContent(user)}
|
||||
onEndReached={this.stickyHeaderEndReached}
|
||||
onTopReached={this.stickyHeaderTopReached}>
|
||||
{/* {this.renderContent(user)} */}
|
||||
{this.renderFlatlistContent(user)}
|
||||
</StickyParallaxHeader>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@ -5058,10 +5058,10 @@ react-native-screens@^2.5.0:
|
||||
dependencies:
|
||||
debounce "^1.2.0"
|
||||
|
||||
react-native-sticky-parallax-header@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-sticky-parallax-header/-/react-native-sticky-parallax-header-0.2.1.tgz#ac399e685d01997a8dd0d5d94db1580602cf23f1"
|
||||
integrity sha512-fbIySVvr4fa6b6JPHgtphAxqZyV8mmQIbpJkXwxuD89Qj/VjKpjhQfsnBDOJ3wDDvngs1PRcyVv7gs1lZYehEw==
|
||||
react-native-sticky-parallax-header@^0.3.0:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.npmjs.org/react-native-sticky-parallax-header/-/react-native-sticky-parallax-header-0.3.1.tgz#6946ed62ef916859108aba013e4652e3a4c6c3fa"
|
||||
integrity sha512-6tDOu2N9UzjDyHpOXQXWmNWhQjfC/qtddGwu1k04zr1vCYbF6ws+HaySK8k3Da2m9YRd0MHxem9UAI5zOL7fQw==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "react-native-sticky-parallax-header",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"main": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"repository": "https://github.com/netguru/sticky-parallax-header",
|
||||
"author": "IdaszakDaniel <idaszak1@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
@ -1,10 +1,30 @@
|
||||
import React, { Component } from 'react'
|
||||
import { arrayOf, bool, func, node, number, shape, string, oneOfType } from 'prop-types'
|
||||
import { Dimensions, ImageBackground, ScrollView, View, Animated, Easing, ViewPropTypes, Image } from 'react-native'
|
||||
import {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
node,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
oneOfType,
|
||||
oneOf,
|
||||
instanceOf
|
||||
} from 'prop-types'
|
||||
import {
|
||||
Dimensions,
|
||||
ImageBackground,
|
||||
ScrollView,
|
||||
View,
|
||||
Animated,
|
||||
Easing,
|
||||
ViewPropTypes,
|
||||
Image
|
||||
} from 'react-native'
|
||||
import { ScrollableTabBar, ScrollableTabView } from './components'
|
||||
import { constants } from './constants'
|
||||
import styles from './styles'
|
||||
import { getSafelyScrollNode } from './utils'
|
||||
import { getSafelyScrollNode, setRef } from './utils'
|
||||
|
||||
const { divide, Value, createAnimatedComponent, event, timing, ValueXY } = Animated
|
||||
const AnimatedScrollView = createAnimatedComponent(ScrollView)
|
||||
@ -16,6 +36,7 @@ class StickyParallaxHeader extends Component {
|
||||
const { width } = Dimensions.get('window')
|
||||
const scrollXIOS = new Value(initialPage * width)
|
||||
const containerWidthAnimatedValue = new Value(width)
|
||||
this.tabsScrollPosition = []
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
containerWidthAnimatedValue.__makeNative()
|
||||
@ -32,10 +53,35 @@ class StickyParallaxHeader extends Component {
|
||||
componentDidMount() {
|
||||
// eslint-disable-next-line
|
||||
this.scrollY.addListener(({ value }) => (this._value = value))
|
||||
this.props.onRef?.(this)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { headerHeight, parallaxHeight, tabs, rememberTabScrollPosition } = this.props
|
||||
const prevPage = prevState.currentPage
|
||||
const { currentPage, isFolded } = this.state
|
||||
const isRenderingTabs = tabs && tabs.length > 0
|
||||
|
||||
if (isRenderingTabs && prevPage !== currentPage && isFolded) {
|
||||
const currentScrollPosition = this.scrollY.__getValue().y
|
||||
const scrollHeight = Math.max(parallaxHeight, headerHeight * 2)
|
||||
|
||||
this.tabsScrollPosition[prevPage] = currentScrollPosition
|
||||
|
||||
setTimeout(() => {
|
||||
const scrollTargetPosition =
|
||||
rememberTabScrollPosition && this.tabsScrollPosition[currentPage]
|
||||
? this.tabsScrollPosition[currentPage]
|
||||
: scrollHeight
|
||||
const scrollNode = getSafelyScrollNode(this.scroll)
|
||||
scrollNode.scrollTo({ y: scrollTargetPosition, duration: 1000 })
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.scrollY.removeAllListeners()
|
||||
this.props.onRef?.(null)
|
||||
}
|
||||
|
||||
spring = () => {
|
||||
@ -72,42 +118,42 @@ class StickyParallaxHeader extends Component {
|
||||
if (y > 0 && y < snapToEdgeThreshold) {
|
||||
return constants.isAndroid
|
||||
? this.setState(
|
||||
{
|
||||
isFolded: false
|
||||
},
|
||||
scrollNode.scrollTo({ x: 0, y: 0, animated: true })
|
||||
)
|
||||
{
|
||||
isFolded: false
|
||||
},
|
||||
scrollNode.scrollTo({ x: 0, y: 0, animated: true })
|
||||
)
|
||||
: timing(snapToEdgeAnimatedValue, {
|
||||
toValue: { x: 0, y: 0 },
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true
|
||||
}).start(() => {
|
||||
snapToEdgeAnimatedValue.removeListener(id)
|
||||
this.setState({
|
||||
isFolded: false
|
||||
})
|
||||
toValue: { x: 0, y: 0 },
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true
|
||||
}).start(() => {
|
||||
snapToEdgeAnimatedValue.removeListener(id)
|
||||
this.setState({
|
||||
isFolded: false
|
||||
})
|
||||
})
|
||||
}
|
||||
if (y >= snapToEdgeThreshold && y < scrollHeight) {
|
||||
return constants.isAndroid
|
||||
? this.setState(
|
||||
{
|
||||
isFolded: true
|
||||
},
|
||||
scrollNode.scrollTo({ x: 0, y: scrollHeight, animated: true })
|
||||
)
|
||||
{
|
||||
isFolded: true
|
||||
},
|
||||
scrollNode.scrollTo({ x: 0, y: scrollHeight, animated: true })
|
||||
)
|
||||
: timing(snapToEdgeAnimatedValue, {
|
||||
toValue: { x: 0, y: snap },
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true
|
||||
}).start(() => {
|
||||
snapToEdgeAnimatedValue.removeListener(id)
|
||||
this.setState({
|
||||
isFolded: true
|
||||
})
|
||||
toValue: { x: 0, y: snap },
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true
|
||||
}).start(() => {
|
||||
snapToEdgeAnimatedValue.removeListener(id)
|
||||
this.setState({
|
||||
isFolded: true
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +205,15 @@ class StickyParallaxHeader extends Component {
|
||||
return null
|
||||
}
|
||||
|
||||
isCloseToTop = ({ contentOffset }) => {
|
||||
const { onTopReached } = this.props
|
||||
if (contentOffset.y <= 0) {
|
||||
return onTopReached && onTopReached()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
const { header, headerHeight, backgroundColor, transparentHeader } = this.props
|
||||
|
||||
@ -275,13 +330,16 @@ class StickyParallaxHeader extends Component {
|
||||
const {
|
||||
backgroundImage,
|
||||
children,
|
||||
contentContainerStyles,
|
||||
header,
|
||||
headerHeight,
|
||||
initialPage,
|
||||
parallaxHeight,
|
||||
tabs,
|
||||
bounces,
|
||||
scrollEvent
|
||||
scrollEvent,
|
||||
keyboardShouldPersistTaps,
|
||||
scrollRef
|
||||
} = this.props
|
||||
const { currentPage, isFolded } = this.state
|
||||
const scrollHeight = Math.max(parallaxHeight, headerHeight * 2)
|
||||
@ -292,7 +350,11 @@ class StickyParallaxHeader extends Component {
|
||||
headerStyle.map((el) => Object.assign(arrayHeaderStyle, el))
|
||||
}
|
||||
|
||||
const scrollViewMinHeight = Dimensions.get('window').height + parallaxHeight - headerHeight
|
||||
const innerScrollHeight = Dimensions.get('window').height - headerHeight - parallaxHeight
|
||||
|
||||
const shouldRenderTabs = tabs && tabs.length > 0
|
||||
const shouldUseBgColor = contentContainerStyles && contentContainerStyles.backgroundColor
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@ -305,11 +367,14 @@ class StickyParallaxHeader extends Component {
|
||||
nestedScrollEnabled
|
||||
ref={(c) => {
|
||||
this.scroll = c
|
||||
setRef(scrollRef, c)
|
||||
}}
|
||||
contentContainerStyle={{ minHeight: scrollViewMinHeight, backgroundColor: shouldUseBgColor }}
|
||||
onScrollEndDrag={() => this.onScrollEndSnapToEdge(scrollHeight)}
|
||||
scrollEventThrottle={1}
|
||||
stickyHeaderIndices={shouldRenderTabs ? [1] : []}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
|
||||
onScroll={event(
|
||||
[
|
||||
{
|
||||
@ -324,6 +389,7 @@ class StickyParallaxHeader extends Component {
|
||||
useNativeDriver: true,
|
||||
listener: (e) => {
|
||||
this.isCloseToBottom(e.nativeEvent)
|
||||
this.isCloseToTop(e.nativeEvent)
|
||||
scrollEvent(e)
|
||||
}
|
||||
}
|
||||
@ -334,15 +400,20 @@ class StickyParallaxHeader extends Component {
|
||||
style={[
|
||||
styles.overScrollPadding,
|
||||
{
|
||||
backgroundColor: isArray ? arrayHeaderStyle.backgroundColor : headerStyle?.backgroundColor
|
||||
backgroundColor: isArray
|
||||
? arrayHeaderStyle.backgroundColor
|
||||
: headerStyle?.backgroundColor
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{backgroundImage ? this.renderImageBackground(scrollHeight) : this.renderPlainBackground(scrollHeight)}
|
||||
{backgroundImage
|
||||
? this.renderImageBackground(scrollHeight)
|
||||
: this.renderPlainBackground(scrollHeight)}
|
||||
{this.renderForeground(scrollHeight)}
|
||||
</View>
|
||||
{shouldRenderTabs && this.renderTabs()}
|
||||
<ScrollableTabView
|
||||
contentContainerStyles={contentContainerStyles}
|
||||
initialPage={initialPage}
|
||||
onChangeTab={(i) => this.onChangeTabHandler(i)}
|
||||
tabs={tabs}
|
||||
@ -351,6 +422,8 @@ class StickyParallaxHeader extends Component {
|
||||
scrollRef={this.scroll}
|
||||
scrollHeight={scrollHeight}
|
||||
isHeaderFolded={isFolded}
|
||||
minScrollHeight={innerScrollHeight}
|
||||
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
|
||||
>
|
||||
{!tabs && children}
|
||||
{tabs &&
|
||||
@ -379,6 +452,7 @@ StickyParallaxHeader.propTypes = {
|
||||
backgroundImage: Image.propTypes.source,
|
||||
bounces: bool,
|
||||
children: node,
|
||||
contentContainerStyles: ViewPropTypes.style,
|
||||
foreground: node,
|
||||
header: node,
|
||||
headerHeight: number,
|
||||
@ -387,6 +461,7 @@ StickyParallaxHeader.propTypes = {
|
||||
onChangeTab: func,
|
||||
onEndReached: func,
|
||||
parallaxHeight: number,
|
||||
rememberTabScrollPosition: bool,
|
||||
scrollEvent: func,
|
||||
snapToEdge: bool,
|
||||
tabTextActiveStyle: shape({}),
|
||||
@ -400,11 +475,16 @@ StickyParallaxHeader.propTypes = {
|
||||
snapStartThreshold: oneOfType([bool, number]),
|
||||
snapStopThreshold: oneOfType([bool, number]),
|
||||
snapValue: oneOfType([bool, number]),
|
||||
transparentHeader: bool
|
||||
transparentHeader: bool,
|
||||
onRef: func,
|
||||
onTopReached: func,
|
||||
scrollRef: oneOfType([func, shape({ current: instanceOf(ScrollView) })]),
|
||||
keyboardShouldPersistTaps: oneOf(['never', 'always', 'handled', false, true, undefined])
|
||||
}
|
||||
|
||||
StickyParallaxHeader.defaultProps = {
|
||||
bounces: true,
|
||||
contentContainerStyles: {},
|
||||
headerHeight: 92,
|
||||
backgroundColor: '',
|
||||
initialPage: 0,
|
||||
@ -415,10 +495,14 @@ StickyParallaxHeader.defaultProps = {
|
||||
tabTextContainerStyle: {},
|
||||
tabTextStyle: {},
|
||||
tabWrapperStyle: {},
|
||||
rememberTabScrollPosition: false,
|
||||
snapStartThreshold: false,
|
||||
snapStopThreshold: false,
|
||||
snapValue: false,
|
||||
transparentHeader: false
|
||||
transparentHeader: false,
|
||||
onRef: null,
|
||||
scrollRef: null,
|
||||
keyboardShouldPersistTaps: undefined
|
||||
}
|
||||
|
||||
export default StickyParallaxHeader
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React from 'react'
|
||||
import { Animated, StyleSheet, View } from 'react-native'
|
||||
import { func, node, number, shape, bool } from 'prop-types'
|
||||
import {
|
||||
Animated,
|
||||
StyleSheet,
|
||||
View,
|
||||
ViewPropTypes
|
||||
} from 'react-native'
|
||||
import { func, node, number, shape, bool, oneOf } from 'prop-types'
|
||||
import SceneComponent from './SceneComponent'
|
||||
import constants from '../../constants/constants'
|
||||
import { getSafelyScrollNode } from '../../utils'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
@ -76,18 +82,6 @@ class ScrollableTabView extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
scrollToTop = () => {
|
||||
const { scrollRef, scrollHeight, isHeaderFolded } = this.props
|
||||
|
||||
return (
|
||||
isHeaderFolded &&
|
||||
scrollRef.scrollTo({
|
||||
y: scrollHeight,
|
||||
duration: 1000
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
updateSelectedPage = (nextPage) => {
|
||||
let localNextPage = nextPage
|
||||
if (typeof localNextPage === 'object') {
|
||||
@ -103,12 +97,20 @@ class ScrollableTabView extends React.Component {
|
||||
this.children().map((child, idx) => {
|
||||
const key = this.makeSceneKey(child, idx)
|
||||
const { currentPage, containerWidth, sceneKeys } = this.state
|
||||
const { contentContainerStyles, minScrollHeight } = this.props
|
||||
|
||||
return (
|
||||
<SceneComponent
|
||||
key={child.key}
|
||||
shouldUpdated={this.shouldRenderSceneKey(idx, currentPage)}
|
||||
style={{ width: containerWidth }}
|
||||
/* eslint-disable-next-line react-native/no-inline-styles */
|
||||
style={[{
|
||||
width: containerWidth,
|
||||
minHeight: minScrollHeight,
|
||||
maxHeight: idx === currentPage ? null : minScrollHeight,
|
||||
},
|
||||
contentContainerStyles
|
||||
]}
|
||||
>
|
||||
{this.keyExists(sceneKeys, key) ? child : null}
|
||||
</SceneComponent>
|
||||
@ -157,7 +159,7 @@ class ScrollableTabView extends React.Component {
|
||||
return newKeys
|
||||
}
|
||||
|
||||
updateSceneKeys = ({ page, children = this.props.children, callback = () => {} }) => {
|
||||
updateSceneKeys = ({ page, children = this.props.children, callback = () => { } }) => {
|
||||
const { sceneKeys } = this.state
|
||||
const newKeys = this.newSceneKeys({ previousKeys: sceneKeys, currentPage: page, children })
|
||||
this.setState({ currentPage: page, sceneKeys: newKeys }, callback)
|
||||
@ -166,8 +168,9 @@ class ScrollableTabView extends React.Component {
|
||||
goToPage = (pageNumber) => {
|
||||
const { containerWidth } = this.state
|
||||
const offset = pageNumber * containerWidth
|
||||
if (this.scrollView) {
|
||||
this.scrollView.scrollTo({ x: offset, y: 0, animated: true })
|
||||
const scrollNode = getSafelyScrollNode(this.scrollView);
|
||||
if (scrollNode) {
|
||||
scrollNode.scrollTo({ x: offset, y: 0, animated: true })
|
||||
}
|
||||
|
||||
const { currentPage } = this.state
|
||||
@ -175,8 +178,6 @@ class ScrollableTabView extends React.Component {
|
||||
page: pageNumber,
|
||||
callback: this.onChangeTab.bind(this, currentPage, pageNumber)
|
||||
})
|
||||
|
||||
this.scrollToTop()
|
||||
}
|
||||
|
||||
onScroll = (e) => {
|
||||
@ -206,11 +207,14 @@ class ScrollableTabView extends React.Component {
|
||||
const scenes = this.composeScenes()
|
||||
const { initialPage } = this.props
|
||||
const { containerWidth, scrollXIOS } = this.state
|
||||
const { minScrollHeight, keyboardShouldPersistTaps } = this.props
|
||||
|
||||
return (
|
||||
<Animated.ScrollView
|
||||
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
|
||||
horizontal
|
||||
pagingEnabled
|
||||
contentContainerStyle={{ minHeight: minScrollHeight }}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
contentOffset={{ x: initialPage * containerWidth }}
|
||||
ref={(scrollView) => {
|
||||
@ -245,19 +249,24 @@ class ScrollableTabView extends React.Component {
|
||||
|
||||
ScrollableTabView.propTypes = {
|
||||
children: node,
|
||||
contentContainerStyles: ViewPropTypes.style,
|
||||
initialPage: number,
|
||||
page: number,
|
||||
onChangeTab: func,
|
||||
swipedPage: func,
|
||||
scrollHeight: number,
|
||||
minScrollHeight: number,
|
||||
isHeaderFolded: bool,
|
||||
scrollRef: shape({})
|
||||
scrollRef: shape({}),
|
||||
keyboardShouldPersistTaps: oneOf(['never', 'always', 'handled', false, true, undefined])
|
||||
}
|
||||
|
||||
ScrollableTabView.defaultProps = {
|
||||
contentContainerStyles: {},
|
||||
initialPage: 0,
|
||||
page: -1,
|
||||
onChangeTab: () => {}
|
||||
onChangeTab: () => { },
|
||||
keyboardShouldPersistTaps: undefined
|
||||
}
|
||||
|
||||
export default ScrollableTabView
|
||||
|
||||
119
src/index.d.ts
vendored
Normal file
119
src/index.d.ts
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
import { ReactElement, Component } from 'react';
|
||||
import { ImageSourcePropType, HeaderTypeProp, ScrollView, NativeScrollEvent, NativeSyntheticEvent, TextStyle, ViewStyle, ImageResizeMode } from 'react-native';
|
||||
|
||||
export interface HeaderTypeProp {
|
||||
headerType?: 'TabbedHeader' | 'DetailsHeader' | 'AvatarHeader';
|
||||
keyboardShouldPersistTaps?: 'always' | 'never' | 'handled' | false | true;
|
||||
scrollRef?:(ref:ScrollView)=>void | object;
|
||||
}
|
||||
|
||||
export interface HeaderSizeProps {
|
||||
height: number;
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface OnChangeTabArguments {
|
||||
from: number;
|
||||
i: number;
|
||||
ref: any;
|
||||
}
|
||||
|
||||
export interface Tab {
|
||||
content: ReactElement;
|
||||
title: string;
|
||||
}
|
||||
export interface SharedProps {
|
||||
backgroundImage?: ImageSourcePropType;
|
||||
headerHeight?: number;
|
||||
snapToEdge?: boolean;
|
||||
bounces?: boolean;
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
leftTopIcon?: ImageSourcePropType;
|
||||
leftTopIconOnPress?: () => void;
|
||||
rightTopIcon?: ImageSourcePropType;
|
||||
rightTopIconOnPress?: () => void;
|
||||
}
|
||||
|
||||
export interface TabsSharedProps {
|
||||
tabTextActiveStyle: TextStyle;
|
||||
tabTextContainerActiveStyle: ViewStyle;
|
||||
tabTextContainerStyle: ViewStyle;
|
||||
tabTextStyle: TextStyle;
|
||||
tabWrapperStyle: ViewStyle;
|
||||
tabs: Tab[];
|
||||
tabsContainerStyle: ViewStyle;
|
||||
}
|
||||
|
||||
|
||||
export type TabbedHeaderProps = SharedProps & TabsSharedProps & {
|
||||
headerType: 'TabbedHeader';
|
||||
backgroundColor?: string;
|
||||
foregroundImage?: ImageSourcePropType;
|
||||
header?: () => ReactElement;
|
||||
logo?: ImageSourcePropType;
|
||||
logoContainerStyle?: ViewStyle;
|
||||
logoResizeMode?: ImageResizeMode;
|
||||
logoStyle?: ViewStyle;
|
||||
rememberTabScrollPosition?: boolean;
|
||||
renderBody?: (title: string) => ReactElement;
|
||||
scrollEvent?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
title?: string;
|
||||
titleStyle?: TextStyle;
|
||||
}
|
||||
|
||||
export type DetailsHeaderProps = SharedProps & IconProps & {
|
||||
headerType: 'DetailsHeader';
|
||||
backgroundColor?: string;
|
||||
hasBorderRadius?: boolean;
|
||||
iconNumber?: number;
|
||||
image?: number;
|
||||
renderBody?: (title: string) => ReactElement;
|
||||
tag?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export type AvatarHeaderProps = SharedProps & IconProps & {
|
||||
headerType: 'AvatarHeader';
|
||||
backgroundColor?: string;
|
||||
foreground?: () => ReactElement;
|
||||
hasBorderRadius?: boolean;
|
||||
header?: () => ReactElement;
|
||||
image?: number;
|
||||
parallaxHeight?: number;
|
||||
renderBody?: (title: string) => ReactElement;
|
||||
scrollEvent?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
snapStartThreshold?: number;
|
||||
snapStopThreshold?: number;
|
||||
snapValue?: number;
|
||||
subtitle?: string;
|
||||
title?: string;
|
||||
transparentHeader?: boolean;
|
||||
}
|
||||
|
||||
export type CustomHeaderProps = SharedProps & TabsSharedProps & {
|
||||
headerType: undefined;
|
||||
background: ReactElement;
|
||||
backgroundColor: string;
|
||||
children?: ReactElement;
|
||||
foreground?: ReactElement;
|
||||
header: ReactElement;
|
||||
headerSize?: ({ x, y, width, height }: HeaderSizeProps) => void;
|
||||
initialPage?: number;
|
||||
onChangeTab?: ({ i, ref, from }: OnChangeTabArguments) => void;
|
||||
onEndReached?: () => void;
|
||||
parallaxHeight?: number;
|
||||
scrollEvent?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
snapStartThreshold?: number;
|
||||
snapStopThreshold?: number;
|
||||
snapValue?: number;
|
||||
tabsContainerBackgroundColor?: string;
|
||||
transparentHeader?: boolean;
|
||||
}
|
||||
|
||||
type StickyParallaxHeaderProps = HeaderTypeProp & (DetailsHeaderProps | AvatarHeaderProps | TabbedHeaderProps | CustomHeaderProps)
|
||||
|
||||
export default class StickyParallaxHeader extends Component<StickyParallaxHeaderProps, any> { }
|
||||
@ -159,10 +159,10 @@ class AvatarHeader extends React.Component {
|
||||
const { backgroundColor, hasBorderRadius } = this.props
|
||||
|
||||
const headerBorderRadius = this.scrollY.y.interpolate({
|
||||
inputRange: [0, height],
|
||||
outputRange: [80, 0],
|
||||
extrapolate: 'extend'
|
||||
})
|
||||
inputRange: [0, height],
|
||||
outputRange: [80, 0],
|
||||
extrapolate: 'extend'
|
||||
})
|
||||
|
||||
const borderBottomRightRadius = hasBorderRadius ? headerBorderRadius : 0
|
||||
|
||||
@ -235,18 +235,18 @@ AvatarHeader.propTypes = {
|
||||
rightTopIcon: number,
|
||||
backgroundColor: string,
|
||||
headerHeight: number,
|
||||
backgroundImage: number,
|
||||
backgroundImage: Image.propTypes.source,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
image: number,
|
||||
image: Image.propTypes.source,
|
||||
renderBody: func,
|
||||
scrollEvent: func,
|
||||
parallaxHeight: number,
|
||||
foreground: func,
|
||||
header: func,
|
||||
snapStartThreshold: oneOfType([ bool, number]),
|
||||
snapStopThreshold: oneOfType([ bool, number]),
|
||||
snapValue: oneOfType([ bool, number]),
|
||||
snapStartThreshold: oneOfType([bool, number]),
|
||||
snapStopThreshold: oneOfType([bool, number]),
|
||||
snapValue: oneOfType([bool, number]),
|
||||
transparentHeader: bool
|
||||
}
|
||||
AvatarHeader.defaultProps = {
|
||||
@ -265,9 +265,6 @@ AvatarHeader.defaultProps = {
|
||||
snapToEdge: true,
|
||||
hasBorderRadius: true,
|
||||
parallaxHeight: sizes.userScreenParallaxHeader,
|
||||
snapStartThreshold: false,
|
||||
snapStopThreshold: false,
|
||||
snapValue: false,
|
||||
transparentHeader: false
|
||||
}
|
||||
|
||||
|
||||
@ -130,14 +130,14 @@ class DetailsHeader extends React.Component {
|
||||
const { backgroundColor, backgroundImage, renderBody, headerHeight, snapToEdge, bounces } = this.props
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<StatusBar barStyle="light-content" backgroundColor={backgroundColor} translucent />
|
||||
<StickyParallaxHeader
|
||||
foreground={this.renderForeground(user)}
|
||||
header={this.renderHeader(user)}
|
||||
deviceWidth={constants.deviceWidth}
|
||||
parallaxHeight={sizes.cardScreenParallaxHeader}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], {useNativeDriver: false})}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], { useNativeDriver: false })}
|
||||
headerSize={this.setHeaderSize}
|
||||
headerHeight={headerHeight}
|
||||
background={this.renderBackground(user)}
|
||||
@ -147,7 +147,7 @@ class DetailsHeader extends React.Component {
|
||||
>
|
||||
{renderBody(user)}
|
||||
</StickyParallaxHeader>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -159,10 +159,10 @@ DetailsHeader.propTypes = {
|
||||
rightTopIcon: number,
|
||||
backgroundColor: string,
|
||||
headerHeight: number,
|
||||
backgroundImage: number,
|
||||
backgroundImage: Image.propTypes.source,
|
||||
title: string,
|
||||
tag: string,
|
||||
image: number,
|
||||
image: Image.propTypes.source,
|
||||
renderBody: func,
|
||||
bounces: bool,
|
||||
snapToEdge: bool,
|
||||
|
||||
@ -1,20 +1,6 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
Image,
|
||||
StatusBar,
|
||||
Animated,
|
||||
ViewPropTypes
|
||||
} from 'react-native'
|
||||
import {
|
||||
arrayOf,
|
||||
bool,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
func
|
||||
} from 'prop-types'
|
||||
import { Text, View, Image, StatusBar, Animated, ViewPropTypes } from 'react-native'
|
||||
import { arrayOf, bool, number, shape, string, func } from 'prop-types'
|
||||
import StickyParallaxHeader from '../../index'
|
||||
import { constants, colors, sizes } from '../../constants'
|
||||
import styles from './TabbedHeader.styles'
|
||||
@ -53,21 +39,11 @@ export default class TabbedHeader extends React.Component {
|
||||
}
|
||||
|
||||
renderLogoHeader = () => {
|
||||
const {
|
||||
backgroundColor,
|
||||
logo,
|
||||
logoResizeMode,
|
||||
logoStyle,
|
||||
logoContainerStyle
|
||||
} = this.props
|
||||
const { backgroundColor, logo, logoResizeMode, logoStyle, logoContainerStyle } = this.props
|
||||
|
||||
return (
|
||||
<View style={[logoContainerStyle, { backgroundColor }]}>
|
||||
<Image
|
||||
resizeMode={logoResizeMode}
|
||||
source={logo}
|
||||
style={logoStyle}
|
||||
/>
|
||||
<Image resizeMode={logoResizeMode} source={logo} style={logoStyle} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@ -105,10 +81,10 @@ export default class TabbedHeader extends React.Component {
|
||||
|
||||
const renderImage = () => {
|
||||
const logo = isUndefined(foregroundImage)
|
||||
? require('../../assets/images/photosPortraitMe.png')
|
||||
: foregroundImage
|
||||
? require('../../assets/images/photosPortraitMe.png')
|
||||
: foregroundImage
|
||||
|
||||
if(foregroundImage !== null){
|
||||
if (foregroundImage !== null) {
|
||||
return (
|
||||
<Animated.View style={{ opacity: imageOpacity }}>
|
||||
<Animated.Image
|
||||
@ -152,7 +128,8 @@ export default class TabbedHeader extends React.Component {
|
||||
return marginBottom
|
||||
}
|
||||
|
||||
marginBottom = constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title]
|
||||
marginBottom =
|
||||
constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title]
|
||||
|
||||
return marginBottom
|
||||
}
|
||||
@ -175,18 +152,22 @@ export default class TabbedHeader extends React.Component {
|
||||
tabTextContainerStyle,
|
||||
tabTextContainerActiveStyle,
|
||||
tabWrapperStyle,
|
||||
tabsContainerStyle
|
||||
tabsContainerStyle,
|
||||
onRef
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<StatusBar barStyle="light-content" backgroundColor={backgroundColor} translucent />
|
||||
<StickyParallaxHeader
|
||||
foreground={this.renderForeground(this.scrollY)}
|
||||
header={this.renderHeader()}
|
||||
deviceWidth={constants.deviceWidth}
|
||||
parallaxHeight={sizes.homeScreenParallaxHeader}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], {useNativeDriver: false, listener: e => scrollEvent && scrollEvent(e)})}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], {
|
||||
useNativeDriver: false,
|
||||
listener: (e) => scrollEvent && scrollEvent(e)
|
||||
})}
|
||||
headerSize={this.setHeaderSize}
|
||||
headerHeight={headerHeight}
|
||||
tabs={tabs}
|
||||
@ -200,10 +181,11 @@ export default class TabbedHeader extends React.Component {
|
||||
bounces={bounces}
|
||||
snapToEdge={snapToEdge}
|
||||
tabsContainerStyle={tabsContainerStyle}
|
||||
onRef={onRef}
|
||||
>
|
||||
{renderBody('Popular Quizes')}
|
||||
</StickyParallaxHeader>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -217,7 +199,7 @@ TabbedHeader.propTypes = {
|
||||
snapToEdge: bool,
|
||||
tabs: arrayOf(shape({})),
|
||||
renderBody: func,
|
||||
logo: number,
|
||||
logo: Image.propTypes.source,
|
||||
logoResizeMode: string,
|
||||
logoStyle: ViewPropTypes.style,
|
||||
logoContainerStyle: ViewPropTypes.style,
|
||||
@ -230,7 +212,8 @@ TabbedHeader.propTypes = {
|
||||
tabsContainerStyle: ViewPropTypes.style,
|
||||
foregroundImage: Image.propTypes.source,
|
||||
titleStyle: Text.propTypes.style,
|
||||
header: func
|
||||
header: func,
|
||||
onRef: func
|
||||
}
|
||||
|
||||
TabbedHeader.defaultProps = {
|
||||
@ -241,10 +224,10 @@ TabbedHeader.defaultProps = {
|
||||
bounces: true,
|
||||
snapToEdge: true,
|
||||
logo: require('../../assets/images/logo.png'),
|
||||
logoResizeMode: "contain",
|
||||
logoResizeMode: 'contain',
|
||||
logoStyle: styles.logo,
|
||||
logoContainerStyle: styles.headerWrapper,
|
||||
renderBody: title => <RenderContent title={title} />,
|
||||
renderBody: (title) => <RenderContent title={title} />,
|
||||
tabs: [
|
||||
{
|
||||
title: 'Popular',
|
||||
@ -267,5 +250,6 @@ TabbedHeader.defaultProps = {
|
||||
tabTextActiveStyle: styles.tabText,
|
||||
tabTextContainerStyle: styles.tabTextContainerStyle,
|
||||
tabTextContainerActiveStyle: styles.tabTextContainerActiveStyle,
|
||||
tabWrapperStyle: styles.tabsWrapper
|
||||
tabWrapperStyle: styles.tabsWrapper,
|
||||
onRef: null
|
||||
}
|
||||
|
||||
@ -28,7 +28,8 @@ const RenderContent = ({ title }) => {
|
||||
return marginBottom
|
||||
}
|
||||
|
||||
marginBottom = constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title]
|
||||
marginBottom =
|
||||
constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title]
|
||||
|
||||
return marginBottom
|
||||
}
|
||||
@ -38,21 +39,22 @@ const RenderContent = ({ title }) => {
|
||||
const marginBottom = Platform.select({ ios: calcMargin(title), android: 0 })
|
||||
|
||||
return (
|
||||
<View style={[styles.content, { marginBottom }]} onLayout={e => onLayoutContent(e, title)}>
|
||||
<View style={[styles.content, { marginBottom }]} onLayout={(e) => onLayoutContent(e, title)}>
|
||||
<Text style={styles.contentText}>{title}</Text>
|
||||
{users.map(
|
||||
user => (title === 'Popular Quizes' || title === user.type) && (
|
||||
<QuizListElement
|
||||
key={JSON.stringify(user)}
|
||||
elements={user.cardsAmount}
|
||||
authorName={user.author}
|
||||
mainText={user.label}
|
||||
labelText={user.type}
|
||||
imageSource={user.image}
|
||||
onPress={() => {}}
|
||||
pressUser={() => {}}
|
||||
/>
|
||||
)
|
||||
(user) =>
|
||||
(title === 'Popular Quizes' || title === user.type) && (
|
||||
<QuizListElement
|
||||
key={JSON.stringify(user)}
|
||||
elements={user.cardsAmount}
|
||||
authorName={user.author}
|
||||
mainText={user.label}
|
||||
labelText={user.type}
|
||||
imageSource={user.image}
|
||||
onPress={() => {}}
|
||||
pressUser={() => {}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
|
||||
@ -24,3 +24,11 @@ export function getSafelyScrollNode(scrollNode) {
|
||||
// before react-native 0.62
|
||||
return scrollNode.getNode()
|
||||
}
|
||||
|
||||
export function setRef(ref, value) {
|
||||
if (typeof ref === 'function') {
|
||||
ref(value);
|
||||
} else if (ref !== null) {
|
||||
ref.current = value;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user