├── .gitignore ├── .npmrc ├── App.js ├── LICENSE ├── README.md ├── app.json ├── assets ├── ArrowRight.png ├── ArrowRight@2x.png ├── ArrowRight@3x.png ├── Placeholder.png ├── example.gif ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── component ├── OnboardingScreen.js └── example │ └── Sample.js ├── index.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import Sample from './component/example/Sample'; 4 | 5 | export default function App() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David Okonji 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rn-onboarding 2 | 3 | example 4 | 5 | An easy to use package to handle user onboarding flow to a react native app. 6 | 7 | ## How to install and use this package 8 | 9 | To install this package you can run the following command; 10 | 11 | > npm i rn-onboarding 12 | 13 | or 14 | 15 | > yarn add rn-onboarding 16 | 17 | ```javascript 18 | import {View} from 'react-native'; 19 | import RNonboarding from 'rn-onboarding'; 20 | 21 | const App = () => { 22 | const pages = { 23 | first: { 24 | showSkip: true, 25 | headerText: 'First Screen', 26 | bodyText: 'Lorem ipsum dolor sit amet', 27 | }, 28 | second: { 29 | showSkip: true, 30 | headerText: 'Second Screen', 31 | bodyText: 'Lorem ipsum dolor sit amet.', 32 | }, 33 | third: { 34 | showSkip: false, 35 | headerText: 'Third Screen', 36 | bodyText: 'Lorem ipsum dolor sit amet.', 37 | } 38 | }; 39 | return ( 40 | 41 | 42 | 43 | ); 44 | } 45 | ``` 46 | > [example code](/component/example/Sample.js) 47 | 48 | ## Component Props 49 | 50 | 51 | | Prop Name | Required| default | Example | 52 | |------------|-------------|------------|---------- 53 | | pages | true | null| ``` const pages = { first: {showSkip: false, headerText: 'Lorem'}}; ``` | 54 | | firstPageKey | true | null|```'first'``` 55 | | containerStyle | false | {} | 56 | | bodyStyle | false | {} | 57 | | topBarStyle| false | {} | 58 | | arrowTopRightStyle| false | {} | 59 | | centerImageStyle | false | {} | 60 | | titleStyle | false | {} | 61 | | circleStyle | false | {} | 62 | | topBarRightTextStyle | false | {} | 63 | | activeCircleColor | false | ```#F58F99``` | 64 | | circleContainer | false | {} | 65 | | inActiveCircleColor | false | ```rgba(38, 0, 87, 0.2)``` | 66 | | customTopBar | false | ```React.Component``` | 67 | | onPressNext | false | null 68 | | onPageChange | false | () => {} | returns the current page displayed 69 | 70 | ## Author 71 | 72 | **David Okonji** @davidokonji 73 | 74 | ## Contribution 75 | 76 | Open to contributions, please raise a pull request to contribute. 77 | 78 | ## Open Source Licence 79 | 80 | [MIT License](/LICENSE) 81 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "RNOnboard", 4 | "slug": "RNOnboard", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "web": { 23 | "favicon": "./assets/favicon.png" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /assets/ArrowRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/ArrowRight.png -------------------------------------------------------------------------------- /assets/ArrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/ArrowRight@2x.png -------------------------------------------------------------------------------- /assets/ArrowRight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/ArrowRight@3x.png -------------------------------------------------------------------------------- /assets/Placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/Placeholder.png -------------------------------------------------------------------------------- /assets/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/example.gif -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidokonji/react-native-onboarding/2d0b2345c7b1032bff636f60b840bd2afe356fd9/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /component/OnboardingScreen.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect, useMemo } from 'react'; 3 | import { StyleSheet, SafeAreaView, View, Image, TouchableOpacity, Text } from 'react-native'; 4 | import GestureRecognizer, {swipeDirections} from 'react-native-swipe-gestures'; 5 | 6 | export default function OnboardingScreen(props) { 7 | const [page, setPage] = useState(props.firstPageKey); 8 | 9 | useEffect(() => { 10 | if (props.pages) { 11 | const allPage = Object.keys(props.pages); 12 | if (allPage.length > 0) { 13 | setPage(allPage[0]); 14 | } 15 | } 16 | }, []); 17 | 18 | useMemo(() => { 19 | props.onPageChange(page) 20 | }, [page, setPage]); 21 | 22 | const changePage = () => { 23 | if (props.onPressNext) { 24 | props.onPressNext({ 25 | nextPage: (value) => { 26 | if (Object.keys(props.pages).includes(value)) { 27 | setPage(value); 28 | } else { 29 | console.warn('Next page value need to be a page key'); 30 | } 31 | }, 32 | }); 33 | } else { 34 | const currentIndex = allPages.findIndex(value => value == page); 35 | if (currentIndex != allPages.length) { 36 | setPage(allPages[currentIndex + 1]); 37 | } 38 | } 39 | } 40 | 41 | const onCirclePress = (value) => { 42 | if (value) { 43 | setPage(value); 44 | } 45 | } 46 | 47 | if (!props.pages) { 48 | return null; 49 | } 50 | 51 | 52 | if (!props.firstPageKey) { 53 | return null; 54 | } 55 | 56 | if (!Object.keys(props.pages).includes(props.firstPageKey)) { 57 | return null; 58 | } 59 | 60 | const handleSwipe = (direction) => { 61 | const {SWIPE_LEFT, SWIPE_RIGHT} = swipeDirections; 62 | if (direction == SWIPE_RIGHT) { 63 | if (page != props.firstPageKey) { 64 | const currentIndex = allPages.findIndex(value => value == page); 65 | if (currentIndex != allPages.length) { 66 | setPage(allPages[currentIndex - 1]); 67 | } 68 | } 69 | } 70 | 71 | if (direction == SWIPE_LEFT) { 72 | const currentIndex = allPages.findIndex(value => value == page); 73 | const next = allPages[currentIndex + 1]; 74 | if (currentIndex != allPages.length && next) { 75 | setPage(next); 76 | } 77 | } 78 | } 79 | 80 | const content = props.pages[page]; 81 | const allPages = Object.keys(props.pages); 82 | const showSkip = !!content.showSkip || false; 83 | const topBarRightText = content.topBarRightText || 'Next'; 84 | const centerImage = content.image || require('../assets/Placeholder.png'); 85 | const bodyText = content.bodyText || ''; 86 | const headerText = content.headerText || ''; 87 | const bottomContent = content.bottomContent || null; 88 | const arrowTopRight = content.arrowTopRight || require('../assets/ArrowRight.png'); 89 | 90 | const config = { 91 | velocityThreshold: 0.3, 92 | directionalOffsetThreshold: 80 93 | }; 94 | 95 | return ( 96 | handleSwipe(direction, state)} 98 | config={config} 99 | style={{ flex: 1 }} 100 | > 101 | 102 | 103 | { (showSkip && !props.customTopBar) && 104 | <> 105 | 106 | 107 | {topBarRightText} 108 | 109 | 110 | 111 | 112 | } 113 | { 114 | (!showSkip && props.customTopBar) && props.customTopBar 115 | } 116 | 117 | 118 | 119 | 120 | {headerText} 121 | 122 | 123 | {bodyText} 124 | 125 | 126 | { 127 | allPages.map((value, i) => 128 | onCirclePress(value)} 132 | style={[ 133 | styles.circle, 134 | (value == page) 135 | ? { backgroundColor: props.activeCircleColor } 136 | : { backgroundColor: props.inActiveCircleColor}, 137 | props.circleStyle 138 | ]} 139 | /> 140 | ) 141 | } 142 | 143 | {bottomContent} 144 | 145 | 146 | 147 | ); 148 | } 149 | 150 | OnboardingScreen.defaultProps = { 151 | containerStyle: {}, 152 | bodyStyle: {}, 153 | pages: null, 154 | topBarStyle: {}, 155 | arrowTopRightStyle: {}, 156 | centerImageStyle: {}, 157 | titleStyle: {}, 158 | circleStyle: {}, 159 | topBarRightTextStyle: {}, 160 | activeCircleColor: '#F58F99', 161 | circleContainerStyle: {}, 162 | inActiveCircleColor: 'rgba(38, 0, 87, 0.2)', 163 | customTopBar: null, 164 | firstPageKey: null, 165 | onPressNext: null, 166 | onPageChange: () => {}, 167 | } 168 | 169 | const styles = StyleSheet.create({ 170 | container: { 171 | flex: 1, 172 | backgroundColor: '#fff' 173 | }, 174 | body: { 175 | flex: 2, 176 | alignItems: 'center', 177 | justifyContent: 'center' 178 | }, 179 | bodyText: { 180 | textAlign: 'center', 181 | width: '70%' 182 | }, 183 | topBar: { 184 | flexDirection: 'row', 185 | justifyContent: 'flex-end', 186 | alignItems: 'center', 187 | paddingRight: 20.58, 188 | paddingTop: 20, 189 | }, 190 | regularText: { 191 | color: '#757E90', 192 | fontSize: 14, 193 | fontStyle: 'normal', 194 | fontWeight: '600', 195 | lineHeight: 21, 196 | }, 197 | headerText: { 198 | color: '#000', 199 | fontWeight: '600', 200 | lineHeight: 40, 201 | fontSize: 22 202 | }, 203 | circle: { 204 | width: 8, 205 | height: 8, 206 | backgroundColor: '#000', 207 | borderRadius: 50, 208 | marginHorizontal: 2.5 209 | }, 210 | circleContainer: { 211 | marginTop: 30, 212 | flexDirection: 'row' 213 | } 214 | }); 215 | -------------------------------------------------------------------------------- /component/example/Sample.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SafeAreaView } from 'react-native'; 3 | import OnboardingScreen from '../OnboardingScreen'; 4 | 5 | export default function Sample() { 6 | const pages = { 7 | first: { 8 | showSkip: true, 9 | image: require('../../assets/Placeholder.png'), 10 | headerText: 'First Screen', 11 | bodyText: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', 12 | bottomContent: null 13 | }, 14 | second: { 15 | showSkip: true, 16 | image: require('../../assets/Placeholder.png'), 17 | headerText: 'Second Screen', 18 | bodyText: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', 19 | bottomContent: null 20 | }, 21 | third: { 22 | showSkip: false, 23 | image: require('../../assets/Placeholder.png'), 24 | headerText: 'Third Screen', 25 | bodyText: 'Lorem ipsum dolor sit amet.', 26 | bottomContent: null 27 | } 28 | }; 29 | 30 | return ( 31 | 32 | { 36 | nextPage('third'); 37 | }} 38 | onPageChange={(page) => { 39 | // console.log(page); 40 | // handle logic with current page 41 | }} 42 | /> 43 | 44 | ); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import RNOnboarding from './component/OnboardingScreen'; 2 | 3 | export default RNOnboarding; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js", 3 | "name": "rn-onboarding", 4 | "keywords": ["react-native", "onboarding", "first page", "welcome screen"], 5 | "version": "1.0.4", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start:serve": "expo start", 9 | "android": "expo start --android", 10 | "ios": "expo start --ios", 11 | "web": "expo start --web", 12 | "eject": "expo eject" 13 | }, 14 | "dependencies": { 15 | "react-native-swipe-gestures": "1.0.5" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "~7.9.0" 19 | }, 20 | "private": false, 21 | "license": "MIT", 22 | "repository": { 23 | "type" : "git", 24 | "url": "git://github.com/davidokonji/react-native-onboarding.git" 25 | }, 26 | "publishConfig": { 27 | "registry": "https://registry.npmjs.com" 28 | } 29 | } 30 | --------------------------------------------------------------------------------