├── README.md ├── package.json └── src ├── index.js └── styles.js /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | iOS iOS 8 | 9 | npm compatible 10 | Twitter 11 | Facebook 12 | Medium 13 | 14 |

15 | Medium 16 | 17 | # About 18 | Our company provides custom UI design and development solutions for mobile applications and websites. 19 | 20 | Need a team to create a project? 21 | 22 | This project is developed and maintained by openGeeksLab LLC. 23 | 24 | 25 | 26 | 27 | # react-native-paper-onboarding 28 | 29 | ## Requirements 30 | - React Native 0.50+ 31 | - iOS 9.0+ 32 | - Android 4.2+ 33 | 34 | ## Installation 35 | Just run: 36 | - npm i @opengeekslab/react-native-paper-onboarding 37 | 38 | ## Basic usage 39 | The library depends on that each screen should contain a static backgroundColor field which contains the desired background color for this screen. The screen itself should have a transparent background 40 | ```javascript 41 | import React, { Component } from 'react'; 42 | 43 | import PaperOnboarding from 'react-native-paper-onboarding'; 44 | 45 | import Screen1 from './screens/screen1'; 46 | import Screen2 from './screens/screen2'; 47 | import Screen3 from './screens/screen3'; 48 | 49 | const screens = [Screen1, Screen2, Screen3]; 50 | 51 | export default class App extends Component { 52 | render() { 53 | return ( 54 | 57 | ); 58 | } 59 | } 60 | ``` 61 | ## Screen example 62 | ```javascript 63 | import React, { Component } from 'react'; 64 | import { 65 | StyleSheet, 66 | Image, 67 | View, 68 | Text, 69 | } from 'react-native'; 70 | 71 | import bgImage from './img.png'; 72 | 73 | export default class Screen1 extends Component { 74 | static backgroundColor = '#ff3631'; 75 | 76 | render() { 77 | return ( 78 | 79 | 80 | 86 | 87 | 88 | 89 | INVITE FRIENDS 90 | 91 | 92 | Listen Your Favorite Music Together 93 | 94 | 95 | 96 | ); 97 | } 98 | } 99 | 100 | const styles = StyleSheet.create({ 101 | container: { 102 | flex: 1, 103 | width: '100%', 104 | height: '100%', 105 | backgroundColor: 'transparent', 106 | }, 107 | imageContainer: { 108 | flex: 1, 109 | backgroundColor: 'transparent', 110 | }, 111 | image: { 112 | width: '100%', 113 | height: '100%', 114 | }, 115 | textContainer: { 116 | height: '27%', 117 | paddingLeft: 25, 118 | backgroundColor: 'transparent', 119 | }, 120 | textTitle: { 121 | fontSize: 56, 122 | fontFamily: 'Bebas Neue', 123 | color: 'rgb(255, 255, 255)', 124 | backgroundColor: 'transparent', 125 | }, 126 | lilText: { 127 | fontSize: 13, 128 | fontFamily: 'Montserrat', 129 | color: 'rgb(255, 255, 255)', 130 | backgroundColor: 'transparent', 131 | }, 132 | }); 133 | ``` 134 | 135 | # Contact us if interested. 136 | 137 | 138 | 139 | # Licence 140 | Expanding is released under the MIT license. 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opengeekslab/react-native-paper-onboarding", 3 | "version": "1.0.2", 4 | "description": "You can be used at the initial screen of the application, for a visual demonstration of the functions of the app. You can also show in quick access what has been changed or added, just swipe the screen left or right", 5 | "main": "src/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/openGeeksLab/react-native-paper-onboarding" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "ios", 16 | "android", 17 | "paper-onboarding" 18 | ], 19 | "author": "openGeeksLab", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/openGeeksLab/react-native-paper-onboarding/issues" 23 | }, 24 | "homepage": "https://github.com/openGeeksLab/react-native-paper-onboarding/#README.md", 25 | "dependencies": { 26 | "prop-types": "^15.6.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | PanResponder, 5 | Dimensions, 6 | Animated, 7 | View, 8 | } from 'react-native'; 9 | 10 | import styles from './styles'; 11 | 12 | const { width, height } = Dimensions.get('screen'); 13 | const RESPOND_THRESHHOLD = width / 3; 14 | 15 | 16 | const viewRadiusInterpolation = { 17 | inputRange: [0, 1], 18 | outputRange: [5, height / 2], 19 | }; 20 | 21 | const viewRadiusInterpolationR = { 22 | inputRange: [0, 1], 23 | outputRange: [height / 2, 5], 24 | }; 25 | 26 | const fadeInInterpolation = { 27 | inputRange: [0, 1], 28 | outputRange: [0.0, 1], 29 | }; 30 | 31 | const fadeOutInterpolation = { 32 | inputRange: [0, 1], 33 | outputRange: [1.0, 0.0], 34 | }; 35 | 36 | const viewScaleInterpolation = { 37 | inputRange: [0, 1], 38 | outputRange: [0, (height * 2) / 5], 39 | }; 40 | 41 | const viewScaleInterpolationR = { 42 | inputRange: [0, 1], 43 | outputRange: [(height * 2) / 5, 0], 44 | }; 45 | 46 | const tabpanelInterpolation = { 47 | inputRange: [0, 1], 48 | outputRange: [0, -12], 49 | }; 50 | 51 | const tabpanelInterpolationR = { 52 | inputRange: [0, 1], 53 | outputRange: [0, 12], 54 | }; 55 | 56 | class PaperOnboardingContainer extends Component { 57 | static propTypes = { 58 | screens: PropTypes.array, 59 | } 60 | 61 | constructor(props) { 62 | super(props); 63 | const routes = this.props.screens.map(item => React.createElement(item)); 64 | this.nextBackground = 0; 65 | this.state = { 66 | routes, 67 | currentScreen: 0, 68 | animationFinish: true, 69 | nextPoint: { x: 0, y: 0 }, 70 | isSwipeDirectionLeft: true, 71 | rootBackground: this.props.screens[0].backgroundColor, 72 | backgroundAnimation: new Animated.Value(0), 73 | panResponder: PanResponder.create({ 74 | onStartShouldSetPanResponder: () => true, 75 | onStartShouldSetPanResponderCapture: () => true, 76 | onMoveShouldSetResponderCapture: () => true, 77 | onMoveShouldSetPanResponderCapture: () => true, 78 | onPanResponderRelease: (e, gestureState) => { 79 | const { x0, y0, dx, dy } = gestureState; // eslint-disable-line object-curly-newline 80 | 81 | const nextPoint = { 82 | x: x0 + dx, 83 | y: y0 + dy, 84 | }; 85 | 86 | if (Math.abs(dx) >= RESPOND_THRESHHOLD) { 87 | if (dx > 0) { 88 | this.onSwipe('right', nextPoint); 89 | } else { 90 | this.onSwipe('left', nextPoint); 91 | } 92 | } 93 | return true; 94 | }, 95 | }), 96 | }; 97 | } 98 | 99 | onSwipe(swipeDirection, nextPoint) { 100 | const { currentScreen } = this.state; 101 | const nextIndex = this.getNextScreenIndex(swipeDirection); 102 | 103 | const isSwipeDirectionLeft = swipeDirection === 'left'; 104 | 105 | this.nextBackground = isSwipeDirectionLeft 106 | ? this.props.screens[nextIndex].backgroundColor 107 | : this.props.screens[currentScreen].backgroundColor; 108 | 109 | this.startBackgroundAnimation( 110 | currentScreen, 111 | nextIndex, 112 | nextPoint, 113 | isSwipeDirectionLeft, 114 | ); 115 | } 116 | 117 | getNextScreenIndex(direction) { 118 | const { currentScreen, routes } = this.state; 119 | let directionModifier = 0; 120 | if (direction === 'left') { 121 | directionModifier = 1; 122 | } else if (direction === 'right') { 123 | directionModifier = -1; 124 | } 125 | 126 | let nextIndex = currentScreen + directionModifier; 127 | if (nextIndex < 0) { 128 | nextIndex = routes.length - 1; 129 | } else if (nextIndex >= routes.length) { 130 | nextIndex = 0; 131 | } 132 | return nextIndex; 133 | } 134 | 135 | callAnimations = (currentScreen, nextIndex) => { 136 | const { backgroundAnimation } = this.state; 137 | const { screens } = this.props; 138 | Animated.timing( 139 | backgroundAnimation, 140 | { toValue: 1, duration: 900 }, 141 | ).start(() => { 142 | backgroundAnimation.setValue(0); 143 | this.nextBackground = screens[currentScreen].backgroundColor; 144 | 145 | this.setState({ 146 | nextIndex: null, 147 | animationFinish: true, 148 | currentScreen: nextIndex, 149 | rootBackground: screens[nextIndex].backgroundColor, 150 | nextPoint: { x: 0, y: 0 }, 151 | }); 152 | }); 153 | } 154 | 155 | startBackgroundAnimation = (currentScreen, nextIndex, nextPoint, isSwipeDirectionLeft) => { 156 | this.setState( 157 | { 158 | nextIndex, 159 | nextPoint, 160 | isSwipeDirectionLeft, 161 | animationFinish: false, 162 | rootBackground: isSwipeDirectionLeft 163 | ? this.props.screens[currentScreen].backgroundColor 164 | : this.props.screens[nextIndex].backgroundColor, 165 | }, 166 | () => this.callAnimations(currentScreen, nextIndex), 167 | ); 168 | } 169 | 170 | renderRippleBackground(screen, backgroundColor, isSwipeDirectionLeft = true) { 171 | const { backgroundAnimation, nextPoint, animationFinish } = this.state; 172 | const radius = isSwipeDirectionLeft ? viewRadiusInterpolationR : viewRadiusInterpolation; 173 | const scale = isSwipeDirectionLeft ? viewScaleInterpolation : viewScaleInterpolationR; 174 | if (!animationFinish) { 175 | return ( 176 | 188 | ); 189 | } 190 | return null; 191 | } 192 | 193 | fadeInStyle = () => { 194 | const { backgroundAnimation } = this.state; 195 | return { 196 | opacity: backgroundAnimation.interpolate(fadeInInterpolation), 197 | }; 198 | } 199 | 200 | fadeOutStyle = () => { 201 | const { backgroundAnimation } = this.state; 202 | return { 203 | opacity: backgroundAnimation.interpolate(fadeOutInterpolation), 204 | }; 205 | } 206 | 207 | renderTabIndicators(isSwipeDirectionLeft) { 208 | const { screens } = this.props; 209 | const { currentScreen, backgroundAnimation } = this.state; 210 | const translate = isSwipeDirectionLeft ? tabpanelInterpolation : tabpanelInterpolationR; 211 | const leftSide = []; 212 | const rightSide = []; 213 | let passActiveScreenFlag = false; 214 | screens.forEach((item, index) => { 215 | if (currentScreen === index) { 216 | passActiveScreenFlag = true; 217 | } 218 | if (passActiveScreenFlag && index !== currentScreen) { 219 | leftSide.push( 220 | , 227 | ); 228 | } else if (index !== currentScreen) { 229 | rightSide.push( 230 | , 237 | ); 238 | } 239 | }); 240 | 241 | return ( 242 | 248 | 249 | {rightSide} 250 | 251 | 252 | 259 | 260 | 261 | {leftSide} 262 | 263 | 264 | ); 265 | } 266 | 267 | getScreensArray = () => { 268 | const { 269 | routes, 270 | nextIndex, 271 | currentScreen, 272 | } = this.state; 273 | return [ 274 | 278 | {routes[currentScreen]} 279 | , 280 | nextIndex !== undefined 281 | ? ( 282 | 286 | {routes[nextIndex]} 287 | 288 | ) 289 | : null, 290 | ]; 291 | } 292 | 293 | render() { 294 | const { 295 | routes, 296 | isSwipeDirectionLeft, 297 | currentScreen, 298 | rootBackground, 299 | } = this.state; 300 | const screensArray = this.getScreensArray(); 301 | 302 | return ( 303 | 310 | {this.renderRippleBackground(routes[currentScreen], this.nextBackground, isSwipeDirectionLeft)} 311 | {isSwipeDirectionLeft ? screensArray : screensArray.reverse()} 312 | 313 | {this.renderTabIndicators(isSwipeDirectionLeft)} 314 | 315 | 316 | ); 317 | } 318 | } 319 | 320 | export default PaperOnboardingContainer; 321 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | backgroundColor: 'rgb(255, 255, 255)', 7 | }, 8 | indicator: { 9 | height: 8, 10 | width: 8, 11 | borderWidth: 1, 12 | borderRadius: 5, 13 | marginHorizontal: 4, 14 | }, 15 | activeIndicator: { 16 | borderColor: 'white', 17 | backgroundColor: 'white', 18 | borderRadius: 6, 19 | height: 11, 20 | width: 11, 21 | }, 22 | inactiveIndicator: { 23 | borderColor: 'white', 24 | backgroundColor: 'transparent', 25 | }, 26 | indicatorContainer: { 27 | flexDirection: 'row', 28 | position: 'absolute', 29 | bottom: 30, 30 | width: '100%', 31 | alignItems: 'center', 32 | justifyContent: 'center', 33 | backgroundColor: 'transparent', 34 | }, 35 | tabnabIndicatorContainer: { 36 | flexDirection: 'row', 37 | }, 38 | tabIndicatorRight: { 39 | flex: 1, 40 | flexDirection: 'row', 41 | justifyContent: 'flex-end', 42 | alignItems: 'center', 43 | }, 44 | tabActiveContainer: { 45 | alignItems: 'center', 46 | }, 47 | tabIndicatorLeft: { 48 | flex: 1, 49 | flexDirection: 'row', 50 | alignItems: 'center', 51 | }, 52 | nextScreenContainer: { 53 | flex: 1, 54 | position: 'absolute', 55 | height: '100%', 56 | width: '100%', 57 | }, 58 | screenAnimatedContainer: { 59 | flex: 1, 60 | }, 61 | rippleView: { 62 | position: 'absolute', 63 | width: 10, 64 | height: 10, 65 | }, 66 | }); 67 | 68 | export default styles; 69 | --------------------------------------------------------------------------------