├── .gitignore ├── Examples ├── Example1 │ ├── Example1.tsx │ └── style.ts ├── Example2 │ ├── Example2.tsx │ └── style.ts ├── Example3 │ ├── Example3.tsx │ └── style.ts ├── Example4 │ ├── Example4.tsx │ └── style.ts ├── assets │ └── img │ │ ├── 150.png │ │ ├── Example1.gif │ │ ├── Example2.gif │ │ ├── Example3.gif │ │ ├── Example4.gif │ │ └── header-img.jpg └── index.ts ├── LICENSE ├── README.md ├── package.json ├── src ├── AnimatedScreenCollapsibleElement │ ├── AnimatedScreenCollapsibleElement.tsx │ └── index.ts ├── AnimatedScreenContext.tsx ├── AnimatedScreenElement │ ├── AnimatedScreenElement.tsx │ └── index.ts ├── AnimatedScreenFlatList │ ├── AnimatedScreenFlatList.tsx │ ├── index.ts │ └── styles.ts ├── AnimatedScreenHeader │ ├── AnimatedScreenHeader.tsx │ ├── index.ts │ └── style.ts ├── AnimatedScreenScrollView │ ├── AnimatedScreenScrollView.tsx │ └── index.ts ├── AnimatedScreenSectionList │ ├── AnimatedScreenSectionList.tsx │ └── index.ts ├── AnimatedScreenWrapper │ ├── AnimatedScreenWrapper.tsx │ └── index.ts └── index.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # build 6 | # 7 | lib/ 8 | 9 | # node.js 10 | # 11 | node_modules/ 12 | npm-debug.log 13 | yarn-error.log 14 | package-lock.json 15 | 16 | .npmrc 17 | -------------------------------------------------------------------------------- /Examples/Example1/Example1.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Image, Text, View } from 'react-native'; 3 | import img1 from '../assets/img/150.png'; 4 | import AnimatedScreen from '../../src'; 5 | import style from './style'; 6 | 7 | const Example1: React.FC = () => { 8 | return ( 9 | 10 | 11 | 12 | Example 1 13 | 14 | Simple header with ScrollView 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Fusce fermentum. Pellentesque egestas, neque sit amet convallis 23 | pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. 24 | In turpis. Cras ultricies mi eu turpis hendrerit fringilla. 25 | Maecenas nec odio et ante tincidunt tempus. Aliquam lobortis. 26 | Etiam vitae tortor. Suspendisse pulvinar, augue ac venenatis 27 | condimentum, sem libero volutpat nibh, nec pellentesque velit pede 28 | quis nunc. Etiam vitae tortor. Cras ultricies mi eu turpis 29 | hendrerit fringilla. Morbi mattis ullamcorper velit. Proin 30 | viverra, ligula sit amet ultrices semper, ligula arcu tristique 31 | sapien, a accumsan nisi mauris ac eros. Vivamus elementum semper 32 | nisi. Praesent metus tellus, elementum eu, semper a, adipiscing 33 | nec, purus. Etiam vitae tortor. 34 | 35 | 36 | 37 | 38 | 39 | Fusce fermentum. Pellentesque egestas, neque sit amet convallis 40 | pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. 41 | In turpis. Cras ultricies mi eu turpis hendrerit fringilla. 42 | Maecenas nec odio et ante tincidunt tempus. Aliquam lobortis. 43 | Etiam vitae tortor. Suspendisse pulvinar, augue ac venenatis 44 | condimentum, sem libero volutpat nibh, nec pellentesque velit pede 45 | quis nunc. Etiam vitae tortor. Cras ultricies mi eu turpis 46 | hendrerit fringilla. Morbi mattis ullamcorper velit. Proin 47 | viverra, ligula sit amet ultrices semper, ligula arcu tristique 48 | sapien, a accumsan nisi mauris ac eros. Vivamus elementum semper 49 | nisi. Praesent metus tellus, elementum eu, semper a, adipiscing 50 | nec, purus. Etiam vitae tortor. 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default React.memo(Example1); 60 | -------------------------------------------------------------------------------- /Examples/Example1/style.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | paddingHorizontal: 10, 6 | }, 7 | title: { 8 | fontSize: 30, 9 | fontWeight: '900', 10 | }, 11 | subtitle: { 12 | fontSize: 20, 13 | fontWeight: '400', 14 | }, 15 | body: { 16 | paddingVertical: 10, 17 | }, 18 | imgWrapper: { 19 | padding: 10, 20 | justifyContent: 'center', 21 | alignItems: 'center', 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /Examples/Example2/Example2.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { Image, Text, View } from 'react-native'; 3 | import img1 from '../assets/img/150.png'; 4 | import AnimatedScreen from '../../src'; 5 | import style from './style'; 6 | 7 | const Example2: React.FC = () => { 8 | const renderItem = useCallback(({ index }) => { 9 | return ( 10 | 11 | 12 | FlatList item {index} 13 | 14 | ); 15 | }, []); 16 | return ( 17 | 18 | 19 | 20 | Example 2 21 | 22 | Simple header with FlatList 23 | 24 | 25 | 26 | 31 | 32 | ); 33 | }; 34 | 35 | export default React.memo(Example2); 36 | -------------------------------------------------------------------------------- /Examples/Example2/style.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | paddingHorizontal: 10, 6 | }, 7 | title: { 8 | fontSize: 30, 9 | fontWeight: '900', 10 | }, 11 | subtitle: { 12 | fontSize: 20, 13 | fontWeight: '400', 14 | }, 15 | body: { 16 | paddingVertical: 10, 17 | }, 18 | item: { 19 | height: 60, 20 | borderWidth: 1, 21 | borderRadius: 5, 22 | margin: 10, 23 | marginTop: 0, 24 | padding: 10, 25 | flexDirection: 'row', 26 | alignItems: 'center', 27 | }, 28 | text: { 29 | flex: 1, 30 | textAlign: 'center', 31 | }, 32 | image: { 33 | width: 40, 34 | height: 40, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /Examples/Example3/Example3.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { Image, Text, View } from 'react-native'; 3 | import img1 from '../assets/img/150.png'; 4 | import AnimatedScreen from '../../src'; 5 | import style from './style'; 6 | 7 | const sections = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L']; 8 | 9 | const Example3: React.FC = () => { 10 | const renderItem = useCallback(({ item }) => { 11 | return ( 12 | 13 | 14 | {item} 15 | 16 | ); 17 | }, []); 18 | const renderSectionHeader = useCallback(({ section }) => { 19 | return ( 20 | 21 | {section.title} 22 | 23 | ); 24 | }, []); 25 | return ( 26 | 27 | 28 | 29 | Example 3 30 | 31 | Simple header with SectionList 32 | 33 | 34 | 35 | ({ 37 | title: section, 38 | data: new Array(Math.floor(Math.random() * 5) + 1).fill( 39 | `Element in ${section} section`, 40 | ), 41 | }))} 42 | renderSectionHeader={renderSectionHeader} 43 | renderItem={renderItem} 44 | /> 45 | 46 | ); 47 | }; 48 | 49 | export default React.memo(Example3); 50 | -------------------------------------------------------------------------------- /Examples/Example3/style.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | paddingHorizontal: 10, 6 | }, 7 | title: { 8 | fontSize: 30, 9 | fontWeight: '900', 10 | }, 11 | subtitle: { 12 | fontSize: 20, 13 | fontWeight: '400', 14 | }, 15 | section: { 16 | backgroundColor: '#e0e0e0', 17 | padding: 10, 18 | }, 19 | item: { 20 | height: 60, 21 | borderWidth: 1, 22 | borderRadius: 5, 23 | marginVertical: 5, 24 | marginHorizontal: 10, 25 | padding: 10, 26 | flexDirection: 'row', 27 | alignItems: 'center', 28 | }, 29 | text: { 30 | flex: 1, 31 | textAlign: 'center', 32 | }, 33 | image: { 34 | width: 40, 35 | height: 40, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /Examples/Example4/Example4.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { Animated, Image, Text, TextInput, View } from 'react-native'; 3 | import { widthPercentageToDP } from 'react-native-responsive-screen'; 4 | import img1 from '../assets/img/150.png'; 5 | import AnimatedScreen from '../../src'; 6 | import style from './style'; 7 | 8 | const sections = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L']; 9 | 10 | const Example4: React.FC = () => { 11 | const renderItem = useCallback(({ item }) => { 12 | return ( 13 | 14 | 15 | {item} 16 | 17 | ); 18 | }, []); 19 | const renderSectionHeader = useCallback(({ section }) => { 20 | return ( 21 | 22 | {section.title} 23 | 24 | ); 25 | }, []); 26 | const getBackgroundOpacity = useCallback( 27 | (scrollY: Animated.Value) => ({ 28 | backgroundColor: scrollY.interpolate({ 29 | inputRange: [0, 200], 30 | outputRange: ['#236979', '#6c2379'], 31 | extrapolate: 'clamp', 32 | }), 33 | }), 34 | [], 35 | ); 36 | const renderBackground = useCallback( 37 | (scrollY: Animated.Value) => ( 38 | 41 | ), 42 | [getBackgroundOpacity], 43 | ); 44 | const headerAnimatedStyle = useCallback( 45 | scrollY => ({ 46 | transform: [ 47 | { 48 | scale: scrollY.interpolate({ 49 | inputRange: [0, 200], 50 | outputRange: [0, 1], 51 | extrapolate: 'clamp', 52 | }), 53 | }, 54 | ], 55 | }), 56 | [], 57 | ); 58 | return ( 59 | 64 | 69 | 70 | 71 | 75 | 76 | 77 | 81 | Example 4 82 | 83 | 84 | 85 | Example 4 86 | OnScroll Animated Header 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ({ 98 | title: section, 99 | data: new Array(Math.floor(Math.random() * 5) + 1).fill( 100 | `Element in ${section} section`, 101 | ), 102 | }))} 103 | renderSectionHeader={renderSectionHeader} 104 | renderItem={renderItem} 105 | /> 106 | 107 | ); 108 | }; 109 | 110 | export default React.memo(Example4); 111 | -------------------------------------------------------------------------------- /Examples/Example4/style.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | paddingHorizontal: 10, 6 | }, 7 | title: { 8 | fontSize: 30, 9 | fontWeight: '900', 10 | }, 11 | subtitle: { 12 | fontSize: 20, 13 | fontWeight: '400', 14 | }, 15 | section: { 16 | backgroundColor: '#e0e0e0', 17 | padding: 10, 18 | }, 19 | header: { 20 | justifyContent: 'flex-start', 21 | flexDirection: 'column', 22 | }, 23 | spacer: { 24 | height: 40, 25 | }, 26 | smallHeader: { 27 | flexDirection: 'row', 28 | alignItems: 'center', 29 | }, 30 | logoWrapper: { 31 | alignItems: 'center', 32 | marginRight: 10, 33 | }, 34 | logo: { 35 | width: 30, 36 | height: 30, 37 | borderRadius: 30, 38 | }, 39 | input: { 40 | width: '100%', 41 | height: 30, 42 | backgroundColor: '#e0e0e0', 43 | opacity: 0.8, 44 | borderRadius: 15, 45 | paddingVertical: 0, 46 | paddingHorizontal: 15, 47 | }, 48 | background: { 49 | position: 'absolute', 50 | width: '100%', 51 | height: '100%', 52 | }, 53 | item: { 54 | height: 60, 55 | borderWidth: 1, 56 | borderRadius: 5, 57 | marginVertical: 5, 58 | marginHorizontal: 10, 59 | padding: 10, 60 | flexDirection: 'row', 61 | alignItems: 'center', 62 | }, 63 | text: { 64 | flex: 1, 65 | textAlign: 'center', 66 | }, 67 | image: { 68 | width: 40, 69 | height: 40, 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /Examples/assets/img/150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/2e8c0b27a6238fe141101e88f63e5d2bb61bc32d/Examples/assets/img/150.png -------------------------------------------------------------------------------- /Examples/assets/img/Example1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/2e8c0b27a6238fe141101e88f63e5d2bb61bc32d/Examples/assets/img/Example1.gif -------------------------------------------------------------------------------- /Examples/assets/img/Example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/2e8c0b27a6238fe141101e88f63e5d2bb61bc32d/Examples/assets/img/Example2.gif -------------------------------------------------------------------------------- /Examples/assets/img/Example3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/2e8c0b27a6238fe141101e88f63e5d2bb61bc32d/Examples/assets/img/Example3.gif -------------------------------------------------------------------------------- /Examples/assets/img/Example4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/2e8c0b27a6238fe141101e88f63e5d2bb61bc32d/Examples/assets/img/Example4.gif -------------------------------------------------------------------------------- /Examples/assets/img/header-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/2e8c0b27a6238fe141101e88f63e5d2bb61bc32d/Examples/assets/img/header-img.jpg -------------------------------------------------------------------------------- /Examples/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Example1 } from './Example1/Example1'; 2 | export { default as Example2 } from './Example2/Example2'; 3 | export { default as Example3 } from './Example3/Example3'; 4 | export { default as Example4 } from './Example4/Example4'; 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Giovanni Davì 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Animated Screen 2 | 3 | [![Version](https://img.shields.io/npm/v/react-native-animated-screen)](https://www.npmjs.com/package/react-native-animated-screen) [![License](https://img.shields.io/npm/l/react-native-animated-screen)](https://www.npmjs.com/package/react-native-animated-screen) [![Build Status](https://img.shields.io/github/repo-size/giovannidavi/react-native-animated-screen)](https://github.com/giovannidavi/react-native-animated-screen) 4 | 5 | ![](https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/master/Examples/assets/img/header-img.jpg) 6 | 7 | 8 | This module includes all the necessary components to generate outstanding **scroll-based** **animated screens** and **screens header**. 9 | 10 | 11 | 12 | The package is both **Android** and **iOS** compatible. 13 | 14 | ## Try it out 15 | 16 | You can play with our ready-to-use **examples** from our GitHub repository 17 | 18 | 19 | 20 | |Example 1|Example 2|Example 3|Example 4| 21 | |---|---|---|---| 22 | |![](https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/master/Examples/assets/img/Example1.gif)|![](https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/master/Examples/assets/img/Example2.gif)|![](https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/master/Examples/assets/img/Example3.gif)|![](https://raw.githubusercontent.com/giovannidavi/react-native-animated-screen/master/Examples/assets/img/Example4.gif)| 23 | 24 | 25 | ``` 26 | 27 | $ git clone https://github.com/giovannidavi/react-native-animated-screen.git 28 | 29 | ``` 30 | 31 | 32 | You can check the examples code here [examples](https://github.com/giovannidavi/react-native-animated-screen/tree/master/Examples) 33 | 34 | 35 | ## Installation 36 | 37 | With npm 38 | 39 | ``` 40 | $ npm install react-native-animated-screen 41 | 42 | ``` 43 | 44 | With yarn 45 | 46 | ``` 47 | $ yarn add react-native-animated-screen 48 | ``` 49 | 50 | 51 | 52 | The solution is implemented in JavaScript so no native module linking is required. 53 | 54 | 55 | 56 | ## Usage 57 | 58 | By default the component exposes 7 sub-components to help you creating the stage. 59 | 60 | - AnimatedScreen.Wrapper 61 | - AnimatedScreen.Header 62 | - AnimatedScreen.ScrollView 63 | - AnimatedScreen.FlatList 64 | - AnimatedScreen.SectionList 65 | - AnimatedScreen.Element 66 | - AniamtedScreen.CollapsibleElement 67 | 68 | For the animation to work properly you need to wrap your entire view in an AnimatedScreen.Wrapper component which will include the AnimatedScreen.Header and at least one of the scrollable elements: 69 | 70 | - AnimatedScreen.ScrollView 71 | - AnimatedScreen.FlatList 72 | - AnimatedScreen.SectionList 73 | 74 | 75 | 76 | 77 | ```javascript 78 | 79 | import React from 'react'; 80 | import { Image, Text, View } from 'react-native'; 81 | import AnimatedScreen from 'react-native-animated-screen'; 82 | 83 | const Component = () => { 84 | return ( 85 | 86 | 87 | 88 | Title 89 | 90 | Subtitle 91 | 92 | 93 | 94 | 95 | 96 | 97 | Body 98 | 99 | 100 | 101 | 102 | ); 103 | }; 104 | ``` 105 | 106 | 107 | 108 | ### AnimatedScreen.Wrapper 109 | 110 | Is the main component and the wrapper of the entire animated screen, **required** 111 | 112 | 113 | 114 | #### Basic parameters 115 | 116 | 117 | ```javascript 118 | 119 | ) => void; 137 | onScroll={() => null} 138 | 139 | > 140 | // at least an AnimatedScreen.Header and an AnimatedScreen scrollable element required as children 141 | { children } 142 | 143 | 144 | ``` 145 | 146 | ### AnimatedScreen.Header 147 | 148 | Is one of the children of the **Wrapper**, is required, and it contains all the header elements. Its dimensions are inherited from the wrapper and define the scroll animation behaviours. 149 | It expect at least one child component. 150 | 151 | 152 | 153 | #### Basic parameters 154 | 155 | 156 | ```javascript 157 | 158 | ViewStyle; 165 | animatedStyle={ scrollY => ({ 166 | opacity: scrollY.interpolate({ 167 | inputRange: [0, 200], // from 0 scrolled to headerMaxHeight scrolled 168 | outputRange: [1, 0] 169 | }) 170 | }) 171 | } 172 | 173 | // Expects a callback returning the component to be used as background 174 | // renderBackground?: (scrollY: Animated.Value) => JSX.Element; 175 | renderBackground={ scrollY => ( 176 | 177 | ) 178 | 179 | // Set if your header should generate a shadow on scroll 180 | // withShadow?: boolean; (default: false) 181 | withShadow={ true } 182 | 183 | // Set if your header should have a translucent blured effect on scroll 184 | // (ONLY Android - not recommended in iOS) 185 | // blur?: boolean; (default: false) 186 | blur={ false } 187 | 188 | // Expects a string or undefined containing the background color to be passed to the style 189 | // backgroundColor?: string; (default: undefined) 190 | backgroundColor="#303030" 191 | 192 | // Select if the header should take care of SafeArea 193 | // withSafeArea?: boolean; (default: true) 194 | withSafeArea={ true } 195 | 196 | // Expect a style object that can extend the header style 197 | // (preferred to animatedStyle if static) 198 | // style?: ViewStyle; (default: undefined) 199 | style={{ paddingBottom: 10 }} 200 | > 201 | // at least one child is required, can be any component (AnimatedScreen.CollapsibleElement and AnimatedScreen.Element are provided to create animated effects) 202 | { children } 203 | 204 | 205 | ``` 206 | 207 | 208 | ### AnimatedScreen.ScrollView 209 | 210 | Extends the react-native **ScrollView** and can be used as scrollable element for the screen. 211 | For the entire list of possible props refer to react-native ScrollView documentation. 212 | Never to be used in combination with AnimatedScreen.FlatList or AnimatedScreen.SectionList. 213 | 214 | 215 | #### Basic parameters 216 | 217 | 218 | ```javascript 219 | 220 | ViewStyle; 223 | animatedStyle={ scrollY => ({ 224 | opacity: scrollY.interpolate({ 225 | inputRange: [0, 200], // from 0 scrolled to headerMaxHeight scrolled 226 | outputRange: [1, 0] 227 | }) 228 | }) 229 | } 230 | 231 | // Expect a style object that can extend the header style 232 | // (preferred to animatedStyle if static) 233 | // style?: ViewStyle; (default: undefined) 234 | style={{ paddingBottom: 10 }} 235 | 236 | // Expect a React.RefObject to be attached to the ScrollView 237 | // scrollViewRef?: React.RefObject; 238 | scrollViewRef={ scrollViewRef } 239 | > 240 | { children } 241 | 242 | 243 | ``` 244 | 245 | 246 | ### AnimatedScreen.FlatList 247 | 248 | Extends the react-native **FlatList** and can be used as scrollable element for the screen. 249 | For the entire list of possible props refer to react-native FlatList documentation. 250 | Never to be used in combination with AnimatedScreen.ScrollView or AnimatedScreen.SectionList. 251 | 252 | 253 | #### Basic parameters 254 | 255 | 256 | ```javascript 257 | 258 | ViewStyle; 261 | animatedStyle={ scrollY => ({ 262 | opacity: scrollY.interpolate({ 263 | inputRange: [0, 200], // from 0 scrolled to headerMaxHeight scrolled 264 | outputRange: [1, 0] 265 | }) 266 | }) 267 | } 268 | 269 | // Expect a style object that can extend the header style 270 | // (preferred to animatedStyle if static) 271 | // style?: ViewStyle; (default: undefined) 272 | style={{ paddingBottom: 10 }} 273 | 274 | // Expect a React.RefObject to be attached to the ScrollView 275 | // flatlistRef?: React.RefObject>; 276 | flatlistRef={ flatlistRef } 277 | 278 | { ...restOfFlatListProps } 279 | /> 280 | 281 | ``` 282 | 283 | 284 | ### AnimatedScreen.SectionList 285 | 286 | Extends the react-native **SectionList** and can be used as scrollable element for the screen. 287 | For the entire list of possible props refer to react-native SectionList documentation. 288 | Never to be used in combination with AnimatedScreen.ScrollView or AnimatedScreen.FlatList. 289 | 290 | 291 | #### Basic parameters 292 | 293 | 294 | ```javascript 295 | 296 | ViewStyle; 299 | animatedStyle={ scrollY => ({ 300 | opacity: scrollY.interpolate({ 301 | inputRange: [0, 200], // from 0 scrolled to headerMaxHeight scrolled 302 | outputRange: [1, 0] 303 | }) 304 | }) 305 | } 306 | 307 | // Expect a style object that can extend the header style 308 | // (preferred to animatedStyle if static) 309 | // style?: ViewStyle; (default: undefined) 310 | style={{ paddingBottom: 10 }} 311 | 312 | // Expect a React.RefObject to be attached to the ScrollView 313 | // sectionlistRef?: React.RefObject>; 314 | sectionlistRef={ sectionlistRef } 315 | 316 | { ...restOfSectionListProps } 317 | /> 318 | 319 | ``` 320 | 321 | 322 | ### AnimatedScreen.CollapsibleElement 323 | 324 | Can only be used as a **child of AnimatedScreen.Header**, it can wrap any type of component that you want to **disappear** while scrolling 325 | 326 | 327 | #### Basic parameters 328 | 329 | 330 | ```javascript 331 | 332 | ViewStyle; 335 | animatedStyle={ scrollY => ({ 336 | opacity: scrollY.interpolate({ 337 | inputRange: [0, 200], // from 0 scrolled to headerMaxHeight scrolled 338 | outputRange: [1, 0] 339 | }) 340 | }) 341 | } 342 | 343 | // Expect a style object that can extend the header style 344 | // (preferred to animatedStyle if static) 345 | // style?: ViewStyle; (default: undefined) 346 | style={{ paddingBottom: 10 }} 347 | 348 | // Set if you want the content of the element to overflow the boxed header (and take full-width) 349 | // only to be used if AnimatedScreen.Header is set to `boxed` 350 | // unboxed?: boolean; (default: false) 351 | unboxed={ true } 352 | 353 | // Expects an object containing all the style elements to interpolate on scroll, can be considered as a shortcut for animatedStyle 354 | // interpolate?: { [key: string]: string[] | number[] }; 355 | interpolate={ height: [200, 0] } // will interpolated from an height of 200 to an height of 0 while scrolling 356 | > 357 | { children } 358 | 359 | 360 | ``` 361 | 362 | 363 | ### AnimatedScreen.Element 364 | 365 | As the CollapsibleElement can only be used as a **child of AnimatedScreen.Header** but it should wrap element that you **don't want to disappear** while scrolling 366 | 367 | 368 | #### Basic parameters 369 | 370 | 371 | ```javascript 372 | 373 | ViewStyle; 376 | animatedStyle={ scrollY => ({ 377 | opacity: scrollY.interpolate({ 378 | inputRange: [0, 200], // from 0 scrolled to headerMaxHeight scrolled 379 | outputRange: [1, 0] 380 | }) 381 | }) 382 | } 383 | 384 | // Expect a style object that can extend the header style 385 | // (preferred to animatedStyle if static) 386 | // style?: ViewStyle; (default: undefined) 387 | style={{ paddingBottom: 10 }} 388 | 389 | // Set if you want the content of the element to overflow the boxed header (and take full-width) 390 | // only to be used if AnimatedScreen.Header is set to `boxed` 391 | // unboxed?: boolean; (default: false) 392 | unboxed={ true } 393 | 394 | // Expects an object containing all the style elements to interpolate on scroll, can be considered as a shortcut for animatedStyle 395 | // interpolate?: { [key: string]: string[] | number[] }; 396 | interpolate={ height: [200, 0] } // will interpolated from an height of 200 to an height of 0 while scrolling 397 | > 398 | { children } 399 | 400 | 401 | ``` 402 | 403 | 404 | ## Author 405 | 406 | * [Giovanni Davì](https://github.com/giovannidavi) 407 | 408 | 409 | ## Contributing 410 | 411 | 412 | 413 | Pull requests are most welcome! 414 | 415 | Don't forget to add a **title** and a **description** that explain the issue you're trying to solve and your suggested solution. Screenshots and gifs are very helpful. 416 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-animated-screen", 3 | "version": "1.1.1", 4 | "description": "React Native Animated Screens made easy", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "build": "rm -rf ./lib && tsc --project ./tsconfig.json" 9 | }, 10 | "dependencies": { 11 | "@react-native-community/blur": "^3.6.0", 12 | "@types/react": "^16.9.56", 13 | "@types/react-native": "^0.63.33", 14 | "react-native-responsive-screen": "^1.4.1", 15 | "react-native-safe-area-context": "^3.1.1" 16 | }, 17 | "devDependencies": { 18 | "@react-native-community/eslint-config": "^1.1.0", 19 | "tslint": "^6.1.3", 20 | "tslint-config-prettier": "^1.18.0", 21 | "tslint-react": "^5.0.0", 22 | "typescript": "^3.8.3" 23 | }, 24 | "peerDependencies": { 25 | "react": "*", 26 | "react-dom": "*", 27 | "react-native": "*" 28 | }, 29 | "files": [ 30 | "lib/**/*" 31 | ], 32 | "keywords": [ 33 | "animated screen", 34 | "parallax header", 35 | "animated header" 36 | ], 37 | "homepage": "https://github.com/giovannidavi/react-native-animated-screen", 38 | "author": "Giovanni Davì", 39 | "license": "MIT" 40 | } 41 | -------------------------------------------------------------------------------- /src/AnimatedScreenCollapsibleElement/AnimatedScreenCollapsibleElement.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useMemo } from 'react'; 2 | import { Animated, ViewStyle } from 'react-native'; 3 | import { widthPercentageToDP } from 'react-native-responsive-screen'; 4 | import AnimatedScreenContext from '../AnimatedScreenContext'; 5 | 6 | type Props = { 7 | animatedStyle?: (scrollY: Animated.Value) => ViewStyle; 8 | children: JSX.Element | JSX.Element[]; 9 | unboxed?: boolean; 10 | style?: ViewStyle; 11 | interpolate?: { [key: string]: string[] | number[] }; 12 | }; 13 | 14 | type Interpolations = { 15 | [key: string]: Animated.AnimatedInterpolation; 16 | }; 17 | 18 | const AnimatedScreenCollapsibleElement: React.FC = ({ 19 | animatedStyle, 20 | children, 21 | unboxed, 22 | interpolate, 23 | style, 24 | }) => { 25 | const { scrollY, headerMaxHeight } = useContext(AnimatedScreenContext); 26 | const interpolations = useMemo(() => { 27 | const interpolationsStyle: Interpolations = {}; 28 | if (interpolate) { 29 | Object.keys(interpolate).forEach(property => { 30 | interpolationsStyle[property] = scrollY.interpolate({ 31 | inputRange: [0, headerMaxHeight], 32 | outputRange: interpolate[property], 33 | extrapolate: 'clamp', 34 | }); 35 | }); 36 | } 37 | return interpolationsStyle; 38 | }, [headerMaxHeight, interpolate, scrollY]); 39 | return ( 40 | <> 41 | {React.Children.map(children, (child: JSX.Element) => ( 42 | 62 | {React.cloneElement(child)} 63 | 64 | ))} 65 | 66 | ); 67 | }; 68 | 69 | export default React.memo(AnimatedScreenCollapsibleElement); 70 | -------------------------------------------------------------------------------- /src/AnimatedScreenCollapsibleElement/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenCollapsibleElement from './AnimatedScreenCollapsibleElement'; 2 | 3 | export default AnimatedScreenCollapsibleElement; 4 | -------------------------------------------------------------------------------- /src/AnimatedScreenContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { 3 | Animated, 4 | NativeSyntheticEvent, 5 | NativeScrollEvent, 6 | } from 'react-native'; 7 | import { heightPercentageToDP as hp } from 'react-native-responsive-screen'; 8 | 9 | type Props = { 10 | headerMaxHeight?: number; 11 | headerMinHeight?: number; 12 | }; 13 | 14 | type Context = { 15 | headerMaxHeight: number; 16 | headerMinHeight: number; 17 | onScroll?: (e: NativeSyntheticEvent) => void; 18 | scrollY: Animated.Value; 19 | disableParallaxEffect?: boolean; 20 | }; 21 | 22 | const AnimatedScreenContext = createContext({ 23 | headerMaxHeight: hp(30), 24 | headerMinHeight: 100, 25 | scrollY: new Animated.Value(0), 26 | }); 27 | 28 | export default AnimatedScreenContext; 29 | -------------------------------------------------------------------------------- /src/AnimatedScreenElement/AnimatedScreenElement.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useMemo } from 'react'; 2 | import { widthPercentageToDP } from 'react-native-responsive-screen'; 3 | import { Animated, ViewStyle, StyleProp } from 'react-native'; 4 | import AnimatedScreenContext from '../AnimatedScreenContext'; 5 | 6 | type Props = { 7 | animatedStyle?: ( 8 | scrollY: Animated.Value, 9 | ) => Animated.WithAnimatedValue>; 10 | children: JSX.Element | JSX.Element[]; 11 | unboxed?: boolean; 12 | interpolate?: { [key: string]: string[] | number[] }; 13 | style?: ViewStyle; 14 | }; 15 | 16 | type Interpolations = { 17 | [key: string]: Animated.AnimatedInterpolation; 18 | }; 19 | 20 | const AnimatedScreenElement: React.FC = ({ 21 | animatedStyle, 22 | children, 23 | unboxed, 24 | interpolate, 25 | style, 26 | }) => { 27 | const { scrollY, headerMaxHeight } = useContext(AnimatedScreenContext); 28 | const interpolations = useMemo(() => { 29 | const interpolationsStyle: Interpolations = {}; 30 | if (interpolate) { 31 | Object.keys(interpolate).forEach(property => { 32 | interpolationsStyle[property] = scrollY.interpolate({ 33 | inputRange: [0, headerMaxHeight], 34 | outputRange: interpolate[property], 35 | extrapolate: 'clamp', 36 | }); 37 | }); 38 | } 39 | return interpolationsStyle; 40 | }, [headerMaxHeight, interpolate, scrollY]); 41 | return ( 42 | <> 43 | {React.Children.map(children, (child: JSX.Element) => ( 44 | 57 | {React.cloneElement(child)} 58 | 59 | ))} 60 | 61 | ); 62 | }; 63 | 64 | export default React.memo(AnimatedScreenElement); 65 | -------------------------------------------------------------------------------- /src/AnimatedScreenElement/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenElement from './AnimatedScreenElement'; 2 | 3 | export default AnimatedScreenElement; 4 | -------------------------------------------------------------------------------- /src/AnimatedScreenFlatList/AnimatedScreenFlatList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useMemo, useCallback } from 'react'; 2 | import { 3 | Animated, 4 | NativeSyntheticEvent, 5 | NativeScrollEvent, 6 | ViewStyle, 7 | FlatListProps, 8 | View, 9 | FlatList, 10 | } from 'react-native'; 11 | import AnimatedScreenContext from '../AnimatedScreenContext'; 12 | import styles from './styles'; 13 | 14 | type Props = Animated.AnimatedProps> & { 15 | animatedStyle?: (scrollY: Animated.Value) => ViewStyle; 16 | style?: ViewStyle; 17 | ListFooterComponent?: () => JSX.Element | null; 18 | contentContainerStyle?: ViewStyle; 19 | flatlistRef?: React.RefObject>; 20 | }; 21 | 22 | const AnimatedScreenFlatList: React.FC = ({ 23 | animatedStyle, 24 | style, 25 | ListFooterComponent = () => null, 26 | contentContainerStyle = {}, 27 | onScroll: onScrollFromProps, 28 | flatlistRef, 29 | ...flatListProps 30 | }) => { 31 | const [contentHeight, setContentHeight] = useState(0); 32 | const [containerHeight, setContainerHeight] = useState(0); 33 | const { 34 | scrollY, 35 | onScroll, 36 | headerMaxHeight, 37 | headerMinHeight, 38 | disableParallaxEffect, 39 | } = useContext(AnimatedScreenContext); 40 | 41 | const shouldScroll = useMemo( 42 | () => contentHeight > containerHeight - headerMaxHeight, 43 | [contentHeight, containerHeight, headerMaxHeight], 44 | ); 45 | const handleSetContent = useCallback( 46 | (_, height) => setContentHeight(height), 47 | [], 48 | ); 49 | const handleSetContainer = useCallback( 50 | e => setContainerHeight(e.nativeEvent.layout.height), 51 | [], 52 | ); 53 | const contentStyle = useMemo( 54 | () => ({ 55 | minHeight: shouldScroll ? containerHeight + headerMaxHeight : undefined, 56 | ...contentContainerStyle, 57 | }), 58 | [shouldScroll, containerHeight, headerMaxHeight, contentContainerStyle], 59 | ); 60 | const offSet = disableParallaxEffect 61 | ? headerMaxHeight + headerMinHeight 62 | : headerMinHeight; 63 | 64 | const listFooter = useMemo(() => { 65 | return ( 66 | <> 67 | 68 | 69 | 70 | ); 71 | }, [ListFooterComponent, offSet]); 72 | 73 | const flatListStyle = useMemo(() => { 74 | return [ 75 | animatedStyle && animatedStyle(scrollY), 76 | { 77 | paddingTop: scrollY.interpolate({ 78 | inputRange: [0, headerMaxHeight], 79 | outputRange: [headerMaxHeight, offSet], 80 | extrapolate: 'clamp', 81 | }), 82 | }, 83 | style, 84 | ]; 85 | }, [animatedStyle, headerMaxHeight, headerMinHeight, scrollY, style]); 86 | 87 | const handleScroll = (e: NativeSyntheticEvent): void => { 88 | if (onScroll) { 89 | onScroll(e); 90 | } 91 | 92 | if (onScrollFromProps) { 93 | onScrollFromProps(e); 94 | } 95 | 96 | scrollY.setValue(e.nativeEvent.contentOffset.y); 97 | }; 98 | 99 | return ( 100 | 113 | ); 114 | }; 115 | 116 | export default React.memo(AnimatedScreenFlatList); 117 | -------------------------------------------------------------------------------- /src/AnimatedScreenFlatList/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenFlatList from './AnimatedScreenFlatList'; 2 | 3 | export default AnimatedScreenFlatList; 4 | -------------------------------------------------------------------------------- /src/AnimatedScreenFlatList/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | wrapper: { 5 | height: 200, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /src/AnimatedScreenHeader/AnimatedScreenHeader.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { View, Animated, ViewStyle, SafeAreaView } from 'react-native'; 3 | import { BlurView } from '@react-native-community/blur'; 4 | import AnimatedScreenContext from '../AnimatedScreenContext'; 5 | import useStyle from './style'; 6 | 7 | type Props = { 8 | boxed?: boolean; 9 | children?: JSX.Element | JSX.Element[]; 10 | animatedStyle?: (scrollY: Animated.Value) => ViewStyle; 11 | renderBackground?: (scrollY: Animated.Value) => JSX.Element; 12 | withShadow?: boolean; 13 | blur?: boolean; 14 | backgroundColor?: string; 15 | withSafeArea?: boolean; 16 | style?: ViewStyle; 17 | }; 18 | 19 | const AnimatedScreenHeader: React.FC = ({ 20 | children, 21 | boxed, 22 | animatedStyle, 23 | blur, 24 | withShadow, 25 | backgroundColor = 'white', 26 | renderBackground, 27 | withSafeArea = true, 28 | style, 29 | }) => { 30 | const { scrollY, headerMaxHeight, headerMinHeight } = useContext( 31 | AnimatedScreenContext, 32 | ); 33 | const styles = useStyle({ 34 | boxed, 35 | blur, 36 | withShadow, 37 | backgroundColor, 38 | }); 39 | 40 | const shadowOpacity = scrollY.interpolate({ 41 | inputRange: [0, headerMaxHeight], 42 | outputRange: [0, 0.3], 43 | extrapolate: 'clamp', 44 | }); 45 | 46 | const elevation = scrollY.interpolate({ 47 | inputRange: [0, headerMaxHeight], 48 | outputRange: [0, 14], 49 | extrapolate: 'clamp', 50 | }); 51 | 52 | return ( 53 | 68 | {blur ? ( 69 | 74 | ) : ( 75 | 76 | )} 77 | {renderBackground && renderBackground(scrollY)} 78 | {withSafeArea ? {children} : <>{children}} 79 | 80 | ); 81 | }; 82 | 83 | export default React.memo(AnimatedScreenHeader); 84 | -------------------------------------------------------------------------------- /src/AnimatedScreenHeader/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenHeader from './AnimatedScreenHeader'; 2 | 3 | export default AnimatedScreenHeader; 4 | -------------------------------------------------------------------------------- /src/AnimatedScreenHeader/style.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet, ViewStyle } from 'react-native'; 2 | import { widthPercentageToDP } from 'react-native-responsive-screen'; 3 | 4 | type AnimatedScreen = { 5 | animatedWrapper: ViewStyle; 6 | headerBackground: ViewStyle; 7 | }; 8 | 9 | type Props = { 10 | backgroundColor: string; 11 | boxed?: boolean; 12 | blur?: boolean; 13 | withShadow?: boolean; 14 | }; 15 | 16 | export default ({ 17 | boxed, 18 | blur, 19 | withShadow, 20 | backgroundColor, 21 | }: Props): AnimatedScreen => 22 | StyleSheet.create({ 23 | animatedWrapper: { 24 | paddingHorizontal: boxed ? widthPercentageToDP('8%') : undefined, 25 | position: 'absolute', 26 | width: '100%', 27 | zIndex: 5, 28 | justifyContent: 'center', 29 | backgroundColor: withShadow ? backgroundColor : undefined, 30 | shadowColor: '#000', 31 | shadowRadius: 6.68, 32 | shadowOffset: { 33 | width: 0, 34 | height: 2, 35 | }, 36 | }, 37 | headerBackground: { 38 | position: 'absolute', 39 | height: '100%', 40 | width: boxed ? widthPercentageToDP('116%') : '100%', 41 | backgroundColor: blur ? 'rgba(255, 255, 255, 0.5)' : backgroundColor, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /src/AnimatedScreenScrollView/AnimatedScreenScrollView.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { 3 | Animated, 4 | NativeSyntheticEvent, 5 | NativeScrollEvent, 6 | ViewStyle, 7 | ScrollViewProps, 8 | View, 9 | ScrollView, 10 | } from 'react-native'; 11 | import AnimatedScreenContext from '../AnimatedScreenContext'; 12 | 13 | type Props = Animated.AnimatedProps & { 14 | animatedStyle?: (scrollY: Animated.Value) => ViewStyle; 15 | children: JSX.Element | JSX.Element[] | null; 16 | style?: ViewStyle; 17 | scrollViewRef?: React.RefObject; 18 | }; 19 | 20 | const AnimatedScreenScrollView: React.FC = ({ 21 | animatedStyle, 22 | children, 23 | style, 24 | scrollViewRef, 25 | ...scrollViewProps 26 | }) => { 27 | const { 28 | scrollY, 29 | onScroll, 30 | headerMaxHeight, 31 | headerMinHeight, 32 | disableParallaxEffect, 33 | } = useContext(AnimatedScreenContext); 34 | const offSet = disableParallaxEffect 35 | ? headerMaxHeight + headerMinHeight 36 | : headerMinHeight; 37 | 38 | const handleScroll = (e: NativeSyntheticEvent): void => { 39 | if (onScroll) { 40 | onScroll(e); 41 | } 42 | scrollY.setValue(e.nativeEvent.contentOffset.y); 43 | }; 44 | 45 | return ( 46 | 65 | {children} 66 | 67 | 68 | ); 69 | }; 70 | 71 | export default React.memo(AnimatedScreenScrollView); 72 | -------------------------------------------------------------------------------- /src/AnimatedScreenScrollView/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenScrollView from './AnimatedScreenScrollView'; 2 | 3 | export default AnimatedScreenScrollView; 4 | -------------------------------------------------------------------------------- /src/AnimatedScreenSectionList/AnimatedScreenSectionList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useMemo, useCallback } from 'react'; 2 | import { 3 | Animated, 4 | NativeSyntheticEvent, 5 | NativeScrollEvent, 6 | SectionListProps, 7 | ViewStyle, 8 | View, 9 | SectionList, 10 | } from 'react-native'; 11 | import AnimatedScreenContext from '../AnimatedScreenContext'; 12 | 13 | type Props = Animated.AnimatedProps> & { 14 | animatedStyle?: (scrollY: Animated.Value) => ViewStyle; 15 | style?: ViewStyle; 16 | ListFooterComponent?: () => JSX.Element | null; 17 | sectionlistRef?: React.RefObject>; 18 | }; 19 | 20 | const AnimatedScreenSectionList: React.FC = ({ 21 | animatedStyle, 22 | style, 23 | ListFooterComponent = () => null, 24 | sectionlistRef, 25 | ...sectionListProps 26 | }) => { 27 | const [contentHeight, setContentHeight] = useState(0); 28 | const [containerHeight, setContainerHeight] = useState(0); 29 | const { scrollY, onScroll, headerMaxHeight, headerMinHeight } = useContext( 30 | AnimatedScreenContext, 31 | ); 32 | const shouldScroll = useMemo( 33 | () => contentHeight > containerHeight - headerMaxHeight, 34 | [contentHeight, containerHeight, headerMaxHeight], 35 | ); 36 | const handleSetContent = useCallback( 37 | (_, height) => setContentHeight(height), 38 | [], 39 | ); 40 | const handleSetContainer = useCallback( 41 | e => setContainerHeight(e.nativeEvent.layout.height), 42 | [], 43 | ); 44 | const contentStyle = useMemo( 45 | () => ({ 46 | minHeight: shouldScroll ? containerHeight + headerMaxHeight : undefined, 47 | }), 48 | [shouldScroll, containerHeight, headerMaxHeight], 49 | ); 50 | 51 | const handleScroll = (e: NativeSyntheticEvent): void => { 52 | if (onScroll) { 53 | onScroll(e); 54 | } 55 | scrollY.setValue(e.nativeEvent.contentOffset.y); 56 | }; 57 | 58 | return ( 59 | <> 60 | 72 | 73 | 74 | 75 | } 76 | style={[ 77 | animatedStyle && animatedStyle(scrollY), 78 | { 79 | paddingTop: scrollY.interpolate({ 80 | inputRange: [0, headerMaxHeight], 81 | outputRange: [headerMaxHeight, headerMinHeight], 82 | extrapolate: 'clamp', 83 | }), 84 | }, 85 | style, 86 | ]} 87 | // eslint-disable-next-line react/jsx-props-no-spreading 88 | {...sectionListProps} 89 | /> 90 | 91 | ); 92 | }; 93 | 94 | export default React.memo(AnimatedScreenSectionList); 95 | -------------------------------------------------------------------------------- /src/AnimatedScreenSectionList/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenSectionList from './AnimatedScreenSectionList'; 2 | 3 | export default AnimatedScreenSectionList; 4 | -------------------------------------------------------------------------------- /src/AnimatedScreenWrapper/AnimatedScreenWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, ReactElement } from 'react'; 2 | import { initialWindowMetrics } from 'react-native-safe-area-context'; 3 | import { 4 | Animated, 5 | NativeSyntheticEvent, 6 | NativeScrollEvent, 7 | } from 'react-native'; 8 | import AnimatedScreenContext from '../AnimatedScreenContext'; 9 | import AnimatedScreenHeader from '../AnimatedScreenHeader'; 10 | 11 | type Props = { 12 | headerMaxHeight?: number; 13 | headerMinHeight?: number; 14 | avoidSafeArea?: boolean; 15 | disableParallaxEffect?: boolean; 16 | onScroll?: (e: NativeSyntheticEvent) => void; 17 | children: [ 18 | ReactElement>, 19 | JSX.Element, 20 | ]; 21 | }; 22 | 23 | const AnimatedScreenWrapper: React.FC = ({ 24 | children, 25 | onScroll, 26 | headerMaxHeight = 200, 27 | headerMinHeight = 70, 28 | avoidSafeArea = false, 29 | disableParallaxEffect, 30 | }) => { 31 | const [scrollY] = useState(new Animated.Value(0)); 32 | const { top: safeTop } = initialWindowMetrics?.insets || { top: 0 }; 33 | 34 | return ( 35 | 44 | {children} 45 | 46 | ); 47 | }; 48 | 49 | export default React.memo(AnimatedScreenWrapper); 50 | -------------------------------------------------------------------------------- /src/AnimatedScreenWrapper/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenWrapper from './AnimatedScreenWrapper'; 2 | 3 | export default AnimatedScreenWrapper; 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import AnimatedScreenHeader from './AnimatedScreenHeader'; 2 | import AnimatedScreenWrapper from './AnimatedScreenWrapper'; 3 | import AnimatedScreenFlatList from './AnimatedScreenFlatList'; 4 | import AnimatedScreenSectionList from './AnimatedScreenSectionList'; 5 | import AnimatedScreenScrollView from './AnimatedScreenScrollView'; 6 | import AnimatedScreenCollapsibleElement from './AnimatedScreenCollapsibleElement'; 7 | import AnimatedScreenElement from './AnimatedScreenElement/AnimatedScreenElement'; 8 | 9 | const AnimatedScreen = { 10 | Header: AnimatedScreenHeader, 11 | Element: AnimatedScreenElement, 12 | Wrapper: AnimatedScreenWrapper, 13 | FlatList: AnimatedScreenFlatList, 14 | ScrollView: AnimatedScreenScrollView, 15 | SectionList: AnimatedScreenSectionList, 16 | CollapsibleElement: AnimatedScreenCollapsibleElement, 17 | }; 18 | 19 | export default AnimatedScreen; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // `tsconfig.json` for react native TypeScript package 2 | { 3 | "compilerOptions": { 4 | "module": "es6", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "skipLibCheck": true, 8 | "allowJs": true, 9 | "allowSyntheticDefaultImports": true, 10 | "esModuleInterop": true, 11 | "isolatedModules": true, 12 | "jsx": "react", 13 | "lib": [ 14 | "es6" 15 | ], 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "target": "esnext" 19 | }, 20 | "include": [ 21 | "src" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "**/__tests__/*" 26 | ] 27 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended" 4 | ] 5 | } --------------------------------------------------------------------------------