├── .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 |
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 |
--------------------------------------------------------------------------------