├── README.md
├── package.json
└── src
├── index.js
└── styles.js
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
--------------------------------------------------------------------------------