├── .gitignore ├── CHANGELOG.md ├── README.md ├── components ├── Buttons.js ├── PageData.js ├── PageDots.js └── Paginator.js ├── images ├── 1.png ├── 2.png └── 3.png ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (October 11, 2016) 2 | 3 | * Detect light background and adapt the text and controls to it. 4 | * Allow to disable the bottom bar overlay via the `bottomOverlay` prop. 5 | * Allow to disable either of the skip, next, or done buttons. 6 | 7 | ## 0.1.0 (October 10, 2016) 8 | 9 | Initial release. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `` 2 | 3 | Onboarding experience made a breeze. 4 | 5 | Originally inspired by [AndroidOnboarder](https://github.com/chyrta/AndroidOnboarder). 6 | 7 | ## Quick demo 8 | 9 | | ![](images/1.png) | ![](images/2.png) | ![](images/3.png) | 10 | | --- | --- | --- | 11 | | Adapts to bright backgrounds | and dark, too | shows the Done button | 12 | 13 | ```javascript 14 | , title: 'Simple Messenger UI', subtitle: 'Implemented in React Native' }, 17 | { backgroundColor: "#fe6e58", image: , title: 'Welcome', subtitle: 'To Earth' }, 18 | { backgroundColor: "#999", image: , title: 'Also', subtitle: 'Mars is nice' }, 19 | ]} 20 | onEnd={} 21 | /> 22 | ``` 23 | 24 | ## Install 25 | 26 | ``` 27 | npm install --save react-native-simple-onboarding 28 | ``` 29 | 30 | ```javascript 31 | import Onboarding from 'react-native-simple-onboarding'; 32 | ``` 33 | 34 | ## Usage 35 | 36 | ## `` component 37 | 38 | Props: 39 | 40 | * `pages` (required): an array of onboarding pages. A page is an object of shape: 41 | * `backgroundColor` (required): a background color for the page 42 | * `image` (required): a component instance displayed at the top of the page 43 | * `title` (required): a string title 44 | * `subtitle` (required): a string subtitle 45 | * `onEnd` (optional): a callback that is fired after the onboarding is complete 46 | * `bottomOverlay` (optional): a bool flag indicating whether the bottom bar overlay should be shown. Defaults to `true`. 47 | * `showSkip` (optional): a bool flag indicating whether the Skip button should be show. Defaults to `true`. 48 | * `showNext` (optional): a bool flag indicating whether the Next arrow button should be show. Defaults to `true`. 49 | * `showDone` (optional): a bool flag indicating whether the Done checkmark button should be show. Defaults to `true`. 50 | 51 | ## To Do 52 | 53 | * animations 54 | * accessibility 55 | 56 | ## License 57 | 58 | MIT. 59 | -------------------------------------------------------------------------------- /components/Buttons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, TouchableOpacity, Text } from 'react-native'; 3 | 4 | const SymbolButton = ({ isLight, size, onPress, style, textStyle, children }) => ( 5 | 6 | 7 | {children} 8 | 9 | 10 | ); 11 | 12 | const TextButton = ({ isLight, size, onPress, textStyle, children }) => ( 13 | 14 | 15 | {children} 16 | 17 | 18 | ); 19 | 20 | export { SymbolButton, TextButton }; 21 | -------------------------------------------------------------------------------- /components/PageData.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text } from 'react-native'; 3 | 4 | const Page = ({ width, height, children }) => ( 5 | 6 | {children} 7 | 8 | ); 9 | 10 | const PageContent = ({ children }) => ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ); 17 | 18 | const PageData = ({ isLight, image, title, subtitle, titleStyles, subtitleStyles, ...rest }) => ( 19 | 20 | 21 | 22 | {image} 23 | 24 | 25 | {title} 26 | 27 | 28 | {subtitle} 29 | 30 | 31 | 32 | ); 33 | 34 | const styles = { 35 | content: { 36 | flex: 1, 37 | flexDirection: 'column', 38 | alignItems: 'center', 39 | justifyContent: 'center', 40 | }, 41 | image: { 42 | flex: 0, 43 | paddingBottom: 60, 44 | alignItems: 'center', 45 | }, 46 | title: { 47 | textAlign: 'center', 48 | fontSize: 26, 49 | color: '#fff', 50 | paddingBottom: 15, 51 | }, 52 | titleLight: { 53 | color: '#000', 54 | }, 55 | subtitle: { 56 | textAlign: 'center', 57 | fontSize: 16, 58 | color: 'rgba(255, 255, 255, 0.7)', 59 | }, 60 | subtitleLight: { 61 | color: 'rgba(0, 0, 0, 0.7)', 62 | }, 63 | }; 64 | 65 | export default PageData; 66 | -------------------------------------------------------------------------------- /components/PageDots.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text } from 'react-native'; 3 | 4 | const PageCheckmark = ({ style }) => ( 5 | 6 | ); 7 | 8 | const PageDot = ({ isLight, selected }) => ( 9 | 16 | ); 17 | 18 | const PageDots = ({ isLight, pages, currentPage }) => ( 19 | 20 | 21 | {Array.from(new Array(pages), (x, i) => i).map(page => ( 22 | 23 | ))} 24 | 25 | 26 | ); 27 | 28 | const styles = { 29 | container: { 30 | flex: 0, 31 | flexDirection: 'row', 32 | alignItems: 'center', 33 | }, 34 | element: { 35 | marginHorizontal: 3, 36 | }, 37 | elementCheck: { 38 | textAlign: 'center', 39 | fontSize: 12, 40 | fontWeight: '900', 41 | }, 42 | elementDot: { 43 | width: 6, 44 | height: 6, 45 | borderRadius: 3, 46 | }, 47 | }; 48 | 49 | export default PageDots; 50 | -------------------------------------------------------------------------------- /components/Paginator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, TouchableOpacity, Text } from 'react-native'; 3 | 4 | import PageDots from './PageDots'; 5 | import { SymbolButton, TextButton } from './Buttons'; 6 | 7 | const getDefaultStyle = (isLight) => ({ 8 | color: isLight ? 'rgba(0, 0, 0, 0.8)' : '#fff', 9 | }); 10 | 11 | const SkipButton = ({ isLight, ...props }) => ( 12 | 13 | Skip 14 | 15 | ); 16 | 17 | const NextButton = ({ isLight, ...props }) => ( 18 | 19 | → 20 | 21 | ); 22 | const DoneButton = ({ isLight, size, ...props }) => ( 23 | 24 | ✓ 25 | 26 | ); 27 | 28 | const BUTTON_SIZE = 40; 29 | const Paginator = ({ isLight, overlay, showSkip, showNext, showDone, pages, currentPage, onEnd, onNext }) => ( 30 | 31 | 32 | {showSkip && currentPage + 1 !== pages ? 33 | : 34 | null 35 | } 36 | 37 | 38 | 39 | {currentPage + 1 === pages ? 40 | (showDone ? : null) : 41 | (showNext ? : null) 42 | } 43 | 44 | 45 | ); 46 | 47 | const styles = { 48 | container: { 49 | height: 60, 50 | paddingHorizontal: 0, 51 | flexDirection: 'row', 52 | justifyContent: 'space-between', 53 | alignItems: 'center', 54 | }, 55 | containerOverlay: { 56 | backgroundColor: 'rgba(0, 0, 0, 0.1)', 57 | }, 58 | buttonLeft: { 59 | width: 70, 60 | justifyContent: 'flex-start', 61 | alignItems: 'center', 62 | }, 63 | buttonRight: { 64 | width: 70, 65 | justifyContent: 'flex-end', 66 | alignItems: 'center', 67 | } 68 | }; 69 | 70 | export default Paginator; 71 | -------------------------------------------------------------------------------- /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goshacmd/react-native-simple-onboarding/8b7c1f981eeb4b7bd499f8fab0ad636c6686deec/images/1.png -------------------------------------------------------------------------------- /images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goshacmd/react-native-simple-onboarding/8b7c1f981eeb4b7bd499f8fab0ad636c6686deec/images/2.png -------------------------------------------------------------------------------- /images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goshacmd/react-native-simple-onboarding/8b7c1f981eeb4b7bd499f8fab0ad636c6686deec/images/3.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, ScrollView, Dimensions, Text, TouchableOpacity, StyleSheet } from 'react-native'; 4 | import tinycolor from 'tinycolor2'; 5 | 6 | import PageData from './components/PageData'; 7 | import Paginator from './components/Paginator'; 8 | 9 | export default class Onboarding extends Component { 10 | constructor() { 11 | super(); 12 | 13 | this.state = { 14 | currentPage: 0, 15 | }; 16 | } 17 | 18 | updatePosition = (event) => { 19 | const { contentOffset, layoutMeasurement } = event.nativeEvent; 20 | const pageFraction = contentOffset.x / layoutMeasurement.width; 21 | const page = Math.round(pageFraction); 22 | const isLastPage = this.props.pages.length === page + 1; 23 | if (isLastPage && pageFraction - page > 0.3) { 24 | this.props.onEnd(); 25 | } else { 26 | this.setState({ currentPage: page }); 27 | } 28 | }; 29 | 30 | goNext = () => { 31 | const { width } = Dimensions.get('window'); 32 | const { currentPage } = this.state; 33 | const nextPage = currentPage + 1; 34 | const offsetX = nextPage * width; 35 | this.refs.scroll.scrollTo({ 36 | x: offsetX, 37 | animated: true 38 | }, () => { 39 | this.setState({ currentPage: nextPage }); 40 | }); 41 | }; 42 | 43 | render() { 44 | const { width, height } = Dimensions.get('window'); 45 | const { pages, bottomOverlay, showSkip, showNext, showDone } = this.props; 46 | const currentPage = pages[this.state.currentPage] || pages[0]; 47 | const { backgroundColor } = currentPage; 48 | const isLight = tinycolor(backgroundColor).getBrightness() > 180; 49 | 50 | return ( 51 | 52 | 60 | {pages.map(({ image, title, subtitle, titleStyles, subtitleStyles }, idx) => ( 61 | 72 | ))} 73 | 74 | 85 | 86 | ); 87 | } 88 | } 89 | 90 | Onboarding.propTypes = { 91 | pages: PropTypes.arrayOf(PropTypes.shape({ 92 | backgroundColor: PropTypes.string.isRequired, 93 | image: PropTypes.element.isRequired, 94 | title: PropTypes.string.isRequired, 95 | subtitle: PropTypes.string.isRequired, 96 | })).isRequired, 97 | bottomOverlay: PropTypes.bool, 98 | showSkip: PropTypes.bool, 99 | showNext: PropTypes.bool, 100 | showDone: PropTypes.bool, 101 | }; 102 | 103 | Onboarding.defaultProps = { 104 | bottomOverlay: true, 105 | showSkip: true, 106 | showNext: true, 107 | showDone: true, 108 | }; 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-simple-onboarding", 3 | "version": "0.1.1", 4 | "description": "A simple onboarding component for React Native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 0" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/goshakkk/react-native-simple-onboarding.git" 12 | }, 13 | "keywords": [ 14 | "React", 15 | "React", 16 | "Native", 17 | "React-Native", 18 | "Component", 19 | "Onboarding", 20 | "iOS", 21 | "Android" 22 | ], 23 | "author": "Gosha Arinich", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/goshakkk/react-native-simple-onboarding/issues" 27 | }, 28 | "homepage": "https://github.com/goshakkk/react-native-simple-onboarding#readme", 29 | "dependencies": { 30 | "tinycolor2": "^1.4.1" 31 | } 32 | } 33 | --------------------------------------------------------------------------------