Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

react-native-app-intro-slider

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-native-app-intro-slider - npm Package Compare versions

Comparing version
3.0.0
to
4.0.0
+218
dist/index.js
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const React = __importStar(require("react"));
const react_native_1 = require("react-native");
const isAndroidRTL = react_native_1.I18nManager.isRTL && react_native_1.Platform.OS === 'android';
class AppIntroSlider extends React.Component {
constructor() {
super(...arguments);
this.state = {
width: 0,
height: 0,
activeIndex: 0,
};
this.goToSlide = (pageNum, triggerOnSlideChange) => {
const prevNum = this.state.activeIndex;
this.setState({ activeIndex: pageNum });
this.flatList?.scrollToOffset({
offset: this._rtlSafeIndex(pageNum) * this.state.width,
});
if (triggerOnSlideChange && this.props.onSlideChange) {
this.props.onSlideChange(pageNum, prevNum);
}
};
// Get the list ref
this.getListRef = () => this.flatList;
// Index that works across Android's weird rtl bugs
this._rtlSafeIndex = (i) => isAndroidRTL ? this.props.data.length - 1 - i : i;
// Render a slide
this._renderItem = (flatListArgs) => {
const { width, height } = this.state;
const props = { ...flatListArgs, dimensions: { width, height } };
// eslint-disable-next-line react-native/no-inline-styles
return <react_native_1.View style={{ width, flex: 1 }}>{this.props.renderItem(props)}</react_native_1.View>;
};
this._renderButton = (name, label, onPress, render) => {
const content = render ? render() : this._renderDefaultButton(name, label);
return this._renderOuterButton(content, name, onPress);
};
this._renderDefaultButton = (name, label) => {
let content = <react_native_1.Text style={styles.buttonText}>{label}</react_native_1.Text>;
if (this.props.bottomButton) {
content = (<react_native_1.View style={[
name === 'Skip' || name === 'Prev'
? styles.transparentBottomButton
: styles.bottomButton,
]}>
{content}
</react_native_1.View>);
}
return content;
};
this._renderOuterButton = (content, name, onPress) => {
const style = name === 'Skip' || name === 'Prev'
? styles.leftButtonContainer
: styles.rightButtonContainer;
return (<react_native_1.View style={!this.props.bottomButton && style}>
<react_native_1.TouchableOpacity onPress={onPress} style={this.props.bottomButton && styles.flexOne}>
{content}
</react_native_1.TouchableOpacity>
</react_native_1.View>);
};
this._renderNextButton = () => this.props.showNextButton &&
this._renderButton('Next', this.props.nextLabel, () => this.goToSlide(this.state.activeIndex + 1, true), this.props.renderNextButton);
this._renderPrevButton = () => this.props.showPrevButton &&
this._renderButton('Prev', this.props.prevLabel, () => this.goToSlide(this.state.activeIndex - 1, true), this.props.renderPrevButton);
this._renderDoneButton = () => this.props.showDoneButton &&
this._renderButton('Done', this.props.doneLabel, this.props.onDone, this.props.renderDoneButton);
this._renderSkipButton = () =>
// scrollToEnd does not work in RTL so use goToSlide instead
this.props.showSkipButton &&
this._renderButton('Skip', this.props.skipLabel, () => this.props.onSkip
? this.props.onSkip()
: this.goToSlide(this.props.data.length - 1), this.props.renderSkipButton);
this._renderPagination = () => {
const isLastSlide = this.state.activeIndex === this.props.data.length - 1;
const isFirstSlide = this.state.activeIndex === 0;
const secondaryButton = (!isFirstSlide && this._renderPrevButton()) ||
(!isLastSlide && this._renderSkipButton());
const primaryButton = isLastSlide
? this._renderDoneButton()
: this._renderNextButton();
return (<react_native_1.View style={styles.paginationContainer}>
<react_native_1.SafeAreaView>
<react_native_1.View style={styles.paginationDots}>
{this.props.data.length > 1 &&
this.props.data.map((_, i) => (<react_native_1.TouchableOpacity key={i} style={[
styles.dot,
this._rtlSafeIndex(i) === this.state.activeIndex
? this.props.activeDotStyle
: this.props.dotStyle,
]} onPress={() => this.goToSlide(i, true)}/>))}
</react_native_1.View>
{primaryButton}
{secondaryButton}
</react_native_1.SafeAreaView>
</react_native_1.View>);
};
this._onMomentumScrollEnd = (e) => {
const offset = e.nativeEvent.contentOffset.x;
// Touching very very quickly and continuous brings about
// a variation close to - but not quite - the width.
// That's why we round the number.
// Also, Android phones and their weird numbers
const newIndex = this._rtlSafeIndex(Math.round(offset / this.state.width));
if (newIndex === this.state.activeIndex) {
// No page change, don't do anything
return;
}
const lastIndex = this.state.activeIndex;
this.setState({ activeIndex: newIndex });
this.props.onSlideChange && this.props.onSlideChange(newIndex, lastIndex);
};
this._onLayout = ({ nativeEvent }) => {
const { width, height } = nativeEvent.layout;
if (width !== this.state.width || height !== this.state.height) {
// Set new width to update rendering of pages
this.setState({ width, height });
// Set new scroll position
const func = () => {
this.flatList?.scrollToOffset({
offset: this._rtlSafeIndex(this.state.activeIndex) * width,
animated: false,
});
};
setTimeout(func, 0); // Must be called like this to avoid bugs :/
}
};
}
render() {
// Separate props used by the component to props passed to FlatList
/* eslint-disable @typescript-eslint/no-unused-vars */
const { renderPagination, activeDotStyle, dotStyle, skipLabel, doneLabel, nextLabel, prevLabel, renderItem, data, ...otherProps } = this.props;
/* eslint-enable @typescript-eslint/no-unused-vars */
return (<react_native_1.View style={styles.flexOne}>
<react_native_1.FlatList ref={(ref) => (this.flatList = ref)} data={this.props.data} horizontal pagingEnabled showsHorizontalScrollIndicator={false} bounces={false} style={styles.flatList} renderItem={this._renderItem} onMomentumScrollEnd={this._onMomentumScrollEnd} extraData={this.state.width} onLayout={this._onLayout}
// make sure all slides are rendered so we can use dots to navigate to them
initialNumToRender={data.length} {...otherProps}/>
{renderPagination
? renderPagination(this.state.activeIndex)
: this._renderPagination()}
</react_native_1.View>);
}
}
exports.default = AppIntroSlider;
AppIntroSlider.defaultProps = {
activeDotStyle: {
backgroundColor: 'rgba(255, 255, 255, .9)',
},
dotStyle: {
backgroundColor: 'rgba(0, 0, 0, .2)',
},
skipLabel: 'Skip',
doneLabel: 'Done',
nextLabel: 'Next',
prevLabel: 'Back',
showDoneButton: true,
showNextButton: true,
bottomButton: false,
};
const styles = react_native_1.StyleSheet.create({
flexOne: {
flex: 1,
},
flatList: {
flex: 1,
flexDirection: isAndroidRTL ? 'row-reverse' : 'row',
},
paginationContainer: {
position: 'absolute',
bottom: 16,
left: 16,
right: 16,
},
paginationDots: {
height: 16,
margin: 16,
flexDirection: isAndroidRTL ? 'row-reverse' : 'row',
justifyContent: 'center',
alignItems: 'center',
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
marginHorizontal: 4,
},
leftButtonContainer: {
position: 'absolute',
left: 0,
},
rightButtonContainer: {
position: 'absolute',
right: 0,
},
bottomButton: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, .3)',
alignItems: 'center',
justifyContent: 'center',
},
transparentBottomButton: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: 'white',
fontSize: 18,
padding: 12,
},
});
+30
-3
{
"name": "react-native-app-intro-slider",
"version": "3.0.0",
"version": "4.0.0",
"description": "Simple and configurable app introduction slider for react native",
"main": "AppIntroSlider.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"lint": "eslint 'src/**/*.{ts,tsx}'",
"format": "prettier --write src",
"test": "echo 'no tests yet'",
"prepublishOnly": "yarn run lint && yarn run format",
"version": "npm run format && git add -A src",
"postversion": "git push && git push --tags"
},
"repository": {

@@ -25,3 +35,20 @@ "type": "git",

},
"homepage": "https://github.com/jacse/react-native-app-intro-slider"
"homepage": "https://github.com/jacse/react-native-app-intro-slider",
"files": [
"dist/**/*"
],
"devDependencies": {
"@react-native-community/eslint-config": "^1.0.0",
"@types/jest": "^24.0.24",
"@types/react": "^16.9.31",
"@types/react-native": "^0.62.0",
"@types/react-test-renderer": "16.9.2",
"@typescript-eslint/eslint-plugin": "^2.25.0",
"@typescript-eslint/parser": "^2.25.0",
"eslint": "^6.5.1",
"jest": "^24.9.0",
"prettier": "^2.0.2",
"react-test-renderer": "16.11.0",
"typescript": "^3.8.3"
}
}
+49
-178

@@ -1,33 +0,18 @@

<h1 align="center">react-native-app-intro-slider</h1>
# react-native-app-intro-slider
<p align="center">Easy-to-use yet very configurable app introduction slider/swiper based on FlatList</p>
An easy-to-use yet very configurable app introduction slider/swiper based on FlatList that supports RTL.
```sh
npm i react-native-app-intro-slider --save
yarn add react-native-app-intro-slider
```
| | |
| ------------------------------------------------ | ------------------------------------------------------- |
| ![Button example gif](Images/button-example.gif) | ![Custom layout example gif](Images/custom-example.gif) |
## Usage
## Table of contents
### Simple examples
- [Usage](#usage)
- [Basic Example](#basic-example)
- [Configuring Buttons](#configuring-buttons)
- [Custom Slide Layout](#custom-slide-layout)
- [Props and options](#props-and-options)
- [Configure behaviour](#configure-behaviour)
- [Configure looks](#configure-looks)
- [Example](#example)
<h2 align="center">Usage</h2>
### Basic example
| No configuration | `showSkipButton` | `bottomButton` and `showSkipButton` |
| Basic | `showSkipButton` | `bottomButton` and `showSkipButton` |
| ---------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------ |
| ![Basic example gif](Images/basic-example.gif) | ![showSkipButton example image](Images/skipbutton-example.jpg) | ![bottomButton example image](Images/bottomskipbutton-example.jpg) |
The component is based on FlatList so usage is very similar. Pass a data-array to AppIntroSlider along with a renderItem-function (or you can use the default basic layout).
The component is based on FlatList so usage is very similar. Pass a data-array to AppIntroSlider along with a `renderItem`-function:

@@ -41,3 +26,3 @@ ```javascript

{
key: 'somethun',
key: 1,
title: 'Title 1',

@@ -49,3 +34,3 @@ text: 'Description.\nSay something cool',

{
key: 'somethun-dos',
key: 2,
title: 'Title 2',

@@ -57,3 +42,3 @@ text: 'Other cool stuff',

{
key: 'somethun1',
key: 3,
title: 'Rocket guy',

@@ -70,3 +55,3 @@ text: 'I\'m already out of descriptions\n\nLorem ipsum bla bla bla',

}
_renderItem = (item) => {
_renderItem = ({ item }) => {
return (

@@ -76,3 +61,3 @@ <View style={styles.slide}>

<Image source={item.image} />
<Text style={style.text}>{item.text}</Text>
<Text style={styles.text}>{item.text}</Text>
</View>

@@ -90,3 +75,3 @@ );

} else {
return <AppIntroSlider renderItem={this._renderItem} slides={slides} onDone={this._onDone}/>;
return <AppIntroSlider renderItem={this._renderItem} data={slides} onDone={this._onDone}/>;
}

@@ -103,3 +88,3 @@ }

import React from 'react';
import { Ionicons } from '@expo/vector-icons';
import Icon from 'react-native-vector-icons/Ionicons';
import { StyleSheet, View } from 'react-native';

@@ -117,6 +102,3 @@ import AppIntroSlider from 'react-native-app-intro-slider';

},
image: {
width: 320,
height: 320,
},
//[...]
});

@@ -127,10 +109,18 @@

export default class App extends React.Component {
_renderItem = ({ item }) => {
return (
<View style={styles.slide}>
<Text style={styles.title}>{item.title}</Text>
<Image source={item.image} />
<Text style={styles.text}>{item.text}</Text>
</View>
);
}
_renderNextButton = () => {
return (
<View style={styles.buttonCircle}>
<Ionicons
<Ion
name="md-arrow-round-forward"
color="rgba(255, 255, 255, .9)"
size={24}
style={{ backgroundColor: 'transparent' }}
/>

@@ -143,7 +133,6 @@ </View>

<View style={styles.buttonCircle}>
<Ionicons
<Ion
name="md-checkmark"
color="rgba(255, 255, 255, .9)"
size={24}
style={{ backgroundColor: 'transparent' }}
/>

@@ -156,3 +145,3 @@ </View>

<AppIntroSlider
slides={slides}
data={slides}
renderDoneButton={this._renderDoneButton}

@@ -166,107 +155,22 @@ renderNextButton={this._renderNextButton}

### Custom slide layout
![Custom layout example gif](Images/custom-example.gif)
```js
import React from 'react';
import { Ionicons } from '@expo/vector-icons';
import { StyleSheet, View, Text, Image } from 'react-native';
import { LinearGradient } from 'expo';
import AppIntroSlider from 'react-native-app-intro-slider';
const styles = StyleSheet.create({
mainContent: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
image: {
width: 320,
height: 320,
},
text: {
color: 'rgba(255, 255, 255, 0.8)',
backgroundColor: 'transparent',
textAlign: 'center',
paddingHorizontal: 16,
},
title: {
fontSize: 22,
color: 'white',
backgroundColor: 'transparent',
textAlign: 'center',
marginBottom: 16,
},
});
const slides = [
{
key: 'somethun',
title: 'Quick setup, good defaults',
text:
'React-native-app-intro-slider is easy to setup with a small footprint and no dependencies. And it comes with good default layouts!',
icon: 'ios-images-outline',
colors: ['#63E2FF', '#B066FE'],
},
{
key: 'somethun1',
title: 'Super customizable',
text:
'The component is also super customizable, so you can adapt it to cover your needs and wants.',
icon: 'ios-options-outline',
colors: ['#A3A1FF', '#3A3897'],
},
{
key: 'somethun2',
title: 'No need to buy me beer',
text: 'Usage is all free',
icon: 'ios-beer-outline',
colors: ['#29ABE2', '#4F00BC'],
},
];
export default class App extends React.Component {
_renderItem = props => (
<LinearGradient
style={[
styles.mainContent,
{
width: props.width,
height: props.height,
},
]}
colors={props.colors}
start={{ x: 0, y: 0.1 }}
end={{ x: 0.1, y: 1 }}
>
<Ionicons
style={{ backgroundColor: 'transparent' }}
name={props.icon}
size={200}
color="white"
/>
<View>
<Text style={styles.title}>{props.title}</Text>
<Text style={styles.text}>{props.text}</Text>
</View>
</LinearGradient>
);
render() {
return <AppIntroSlider slides={slides} renderItem={this._renderItem} bottomButton />;
}
}
```
Here a custom `renderItem` is supplied and the `bottomButton`-props has been set to `true`. Notice how the setup of `slides` has been configured to support icons and gradient backgrounds.
<h2 align="center">Props and options</h2>
## Props and methods
The component extends `FlatList` so all FlatList-props are valid.
### Configure looks
### Props
| Name | Type | Default | Description |
| ---------------- | ---------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| data | `object` | None, required | An array of objects (they should either contain a unique `key`-prop or you should pass a `keyExtractor`-function to the component) |
| renderItem | `function` | None, required | [FlatList's renderItem](https://facebook.github.io/react-native/docs/flatlist.html#renderitem). Receives `({item, index, dimensions})` where `dimensions` contains height and width of the slides |
| onSlideChange | `function` | `void` | Called when user goes changes slide (by swiping or pressing next/prev). Function called with arguments `(index: number, lastIndex: number)` |
| renderPagination | `function` | | Function to render a custom pagination/button component on top of slides. Receives the index of the currently active slide |
| onDone | `function` | `void` | Called when user ends the introduction by pressing the done button |
| onSkip | `function` | Scrolls to the end | Called when user presses the skip button |
| bottomButton | `boolean` | `false` | Enable to show a full-width button under pagination |
| dotStyle | `style` | {backgroundColor: 'rgba(0, 0, 0, .2)'} | Style of inactive pagination dots |
| activeDotStyle | `style` | {backgroundColor: 'rgba(255, 255, 255, .9)'} | Style of active pagination dot |
| skipLabel | `string` | `Skip` | Custom label for Skip button |

@@ -276,9 +180,6 @@ | doneLabel | `string` | `Done` | Custom label for Done button |

| prevLabel | `string` | `Back` | Custom label for Prev button |
| bottomButton | `boolean` | `false` | Enable to show a full-width button under pagination |
| buttonStyle | `style` | `null` | Styling of outer button component |
| buttonTextStyle | `style` | `null` | Styling of button text component |
| dotStyle | `style` | {backgroundColor: 'rgba(0, 0, 0, .2)'} | Style of inactive pagination dots |
| activeDotStyle | `style` | {backgroundColor: 'rgba(255, 255, 255, .9)'} | Style of active pagination dot |
| paginationStyle | `style` | `null` | Styling of the pagination dots |
| hidePagination | `boolean` | `false` | Enable to hide the pagination |
| showSkipButton | `boolean` | `false` | Enable to show a skip button to the left of pagination dots. When `bottomButton == true` the skip button is a small text under the full-width next button |
| showPrevButton | `boolean` | `false` | Enable to show a previous button. If `showSkipButton` is true, the skip button will be displayed on the first page and prev button on subsequent one |
| showNextButton | `boolean` | `true` | Disable to hide the next button |
| showDoneButton | `boolean` | `true` | Disable to hide the done button |
| renderNextButton | `function` | renders a Text-component | Use to supply your own next button |

@@ -288,31 +189,3 @@ | renderPrevButton | `function` | renders a Text-component | Use to supply your own prev button |

| renderSkipButton | `function` | renders a Text-component | Use to supply your own skip button |
| renderItem | `function` | renders `DefaultSlide` | (FlatList's renderItem)[https://facebook.github.io/react-native/docs/flatlist.html#renderitem]. Receives `{item, index, dimensions}` where `dimensions` contains height and width of the slides. |
### Configure behavior
| Name | Type | Default | Description |
| -------------- | ---------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| slides | `object` | No default, required | An array of objects (they should either contain a unique `key`-prop or you should pass a `keyExtractor`-function to the component) |
| showSkipButton | `boolean` | `false` | Enable to show a skip button to the left of pagination dots. When `bottomButton == true` the skip button is a small text under the full-width next button |
| showPrevButton | `boolean` | `false` | Enable to show a previous button. If `showSkipButton` is true, the skip button will be displayed on the first page and prev button on subsequent one |
| showNextButton | `boolean` | `true` | Disable to hide the next button |
| showDoneButton | `boolean` | `true` | Disable to hide the done button |
| onSlideChange | `function` | `void` | Called when user goes changes slide (by swiping or pressing next/prev). Function called with arguments `index: number, lastIndex: number` |
| onDone | `function` | `void` | Called when user ends the introduction by pressing the done button |
| onSkip | `function` | Scroll the list to the end | Called when user presses the skip button |
#### Slide object
If you want to use the default slide layout, your slides can contain the following information:
| Name | Type | Note |
| --------------- | ------------------- | ------------------------------------------- |
| title | `string` | The title |
| titleStyle | `Style`-prop | Styling for the title (e.g color, fontSize) |
| text | `string` | Main text of slide |
| textStyle | `Style`-prop | Styling for the text (e.g color, fontSize) |
| image | `Image`-source prop | Slide image |
| imageStyle | `Style`-prop | Styling for the image (e.g. size) |
| backgroundColor | `string` | Slide background color |
### Methods

@@ -325,11 +198,9 @@

<h2 align="center">Example</h2>
You can run the example Expo-app by cloning the repo:
```sh
git clone https://github.com/Jacse/react-native-app-intro-slider.git
cd react-native-app-intro-slider/Example
yarn
yarn start
```
## Examples
* [Basic](https://github.com/Jacse/react-native-app-intro-slider/tree/master/examples/examples/Basic/index.js)
* [Bottom buttons](https://github.com/Jacse/react-native-app-intro-slider/tree/master/examples/examples/BottomButton/index.js)
* [Cstom buttons](https://github.com/Jacse/react-native-app-intro-slider/tree/master/examples/examples/CustomButton/index.js)
* [Custom pagination with log in / sign up buttons](https://github.com/Jacse/react-native-app-intro-slider/tree/master/examples/examples/CustomPagination/index.js)
* [Full-size background Images](https://github.com/Jacse/react-native-app-intro-slider/tree/master/examples/examples/FullBackgroundImage/index.js)
* [RTL](https://github.com/Jacse/react-native-app-intro-slider/tree/master/examples/examples/RTL/index.js)
{
"rules": {
"quotes": ["error", "single"],
"comma-dangle": ["error", "always-multiline"]
}
}
{
"devToolsPort": 19002
}
{
"hostType": "tunnel",
"lanType": "ip",
"dev": true,
"minify": false,
"urlRandomness": null
}
import React from 'react';
import {
StyleSheet,
FlatList,
View,
Dimensions,
Text,
TouchableOpacity,
Platform,
StatusBar,
I18nManager,
} from 'react-native';
import DefaultSlide from './DefaultSlide';
const { width, height } = Dimensions.get('window');
const isIphoneX =
Platform.OS === 'ios' && !Platform.isPad && !Platform.isTVOS && (height === 812 || width === 812);
const isAndroidRTL = I18nManager.isRTL && Platform.OS === 'android';
export default class AppIntroSlider extends React.Component {
static defaultProps = {
activeDotStyle: {
backgroundColor: 'rgba(255, 255, 255, .9)',
},
dotStyle: {
backgroundColor: 'rgba(0, 0, 0, .2)',
},
skipLabel: 'Skip',
doneLabel: 'Done',
nextLabel: 'Next',
prevLabel: 'Back',
buttonStyle: null,
buttonTextStyle: null,
paginationStyle: null,
showDoneButton: true,
showNextButton: true,
};
state = {
width,
height,
activeIndex: 0,
};
goToSlide = pageNum => {
this.setState({ activeIndex: pageNum });
this.flatList.scrollToOffset({
offset: this._rtlSafeIndex(pageNum) * this.state.width,
});
};
// Get the list ref
getListRef = () => this.flatList;
_onNextPress = () => {
this.goToSlide(this.state.activeIndex + 1);
this.props.onSlideChange &&
this.props.onSlideChange(this.state.activeIndex + 1, this.state.activeIndex);
};
_onPrevPress = () => {
this.goToSlide(this.state.activeIndex - 1);
this.props.onSlideChange &&
this.props.onSlideChange(this.state.activeIndex - 1, this.state.activeIndex);
};
_onPaginationPress = index => {
const activeIndexBeforeChange = this.state.activeIndex;
this.goToSlide(index);
this.props.onSlideChange && this.props.onSlideChange(index, activeIndexBeforeChange);
};
_renderItem = flatListArgs => {
const { width, height } = this.state;
const props = { ...flatListArgs, dimensions: { width, height } };
return (
<View style={{ width, flex: 1 }}>
{this.props.renderItem ? (
this.props.renderItem(props)
) : (
<DefaultSlide bottomButton={this.props.bottomButton} {...props} />
)}
</View>
);
};
_renderButton = (name, onPress) => {
const show = this.props[`show${name}Button`];
const content = this.props[`render${name}Button`]
? this.props[`render${name}Button`]()
: this._renderDefaultButton(name);
return show && this._renderOuterButton(content, name, onPress);
};
_renderDefaultButton = name => {
let content = (
<Text style={[styles.buttonText, this.props.buttonTextStyle]}>
{this.props[`${name.toLowerCase()}Label`]}
</Text>
);
if (this.props.bottomButton) {
content = (
<View
style={[
styles.bottomButton,
(name === 'Skip' || name === 'Prev') && {
backgroundColor: 'transparent',
},
this.props.buttonStyle,
]}
>
{content}
</View>
);
}
return content;
};
_renderOuterButton = (content, name, onPress) => {
const style =
name === 'Skip' || name === 'Prev' ? styles.leftButtonContainer : styles.rightButtonContainer;
return (
<View style={!this.props.bottomButton && style}>
<TouchableOpacity
onPress={onPress}
style={this.props.bottomButton ? styles.flexOne : this.props.buttonStyle}
>
{content}
</TouchableOpacity>
</View>
);
};
_renderNextButton = () => this._renderButton('Next', this._onNextPress);
_renderPrevButton = () => this._renderButton('Prev', this._onPrevPress);
_renderDoneButton = () => this._renderButton('Done', this.props.onDone && this.props.onDone);
_renderSkipButton = () =>
// scrollToEnd does not work in RTL so use goToSlide instead
this._renderButton('Skip', () =>
this.props.onSkip ? this.props.onSkip() : this.goToSlide(this.props.slides.length - 1)
);
_renderPagination = () => {
const isLastSlide = this.state.activeIndex === this.props.slides.length - 1;
const isFirstSlide = this.state.activeIndex === 0;
const skipBtn =
(!isFirstSlide && this._renderPrevButton()) || (!isLastSlide && this._renderSkipButton());
const btn = isLastSlide ? this._renderDoneButton() : this._renderNextButton();
return (
<View style={[styles.paginationContainer, this.props.paginationStyle]}>
<View style={styles.paginationDots}>
{this.props.slides.length > 1 &&
this.props.slides.map((_, i) => (
<TouchableOpacity
key={i}
style={[
styles.dot,
this._rtlSafeIndex(i) === this.state.activeIndex
? this.props.activeDotStyle
: this.props.dotStyle,
]}
onPress={() => this._onPaginationPress(i)}
/>
))}
</View>
{btn}
{skipBtn}
</View>
);
};
_rtlSafeIndex = i => (isAndroidRTL ? this.props.slides.length - 1 - i : i);
_onMomentumScrollEnd = e => {
const offset = e.nativeEvent.contentOffset.x;
// Touching very very quickly and continuous brings about
// a variation close to - but not quite - the width.
// That's why we round the number.
// Also, Android phones and their weird numbers
const newIndex = this._rtlSafeIndex(Math.round(offset / this.state.width));
if (newIndex === this.state.activeIndex) {
// No page change, don't do anything
return;
}
const lastIndex = this.state.activeIndex;
this.setState({ activeIndex: newIndex });
this.props.onSlideChange && this.props.onSlideChange(newIndex, lastIndex);
};
_onLayout = () => {
const { width, height } = Dimensions.get('window');
if (width !== this.state.width || height !== this.state.height) {
// Set new width to update rendering of pages
this.setState({ width, height });
// Set new scroll position
const func = () => {
this.flatList.scrollToOffset({
offset: this._rtlSafeIndex(this.state.activeIndex) * width,
animated: false,
});
};
Platform.OS === 'android' ? setTimeout(func, 0) : func();
}
};
render() {
// Separate props used by the component to props passed to FlatList
const {
hidePagination,
activeDotStyle,
dotStyle,
skipLabel,
doneLabel,
nextLabel,
prevLabel,
buttonStyle,
buttonTextStyle,
renderItem,
data,
...otherProps
} = this.props;
return (
<View style={styles.flexOne}>
<FlatList
ref={ref => (this.flatList = ref)}
data={this.props.slides}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
bounces={false}
style={styles.flatList}
renderItem={this._renderItem}
onMomentumScrollEnd={this._onMomentumScrollEnd}
extraData={this.state.width}
onLayout={this._onLayout}
{...otherProps}
/>
{!hidePagination && this._renderPagination()}
</View>
);
}
}
const styles = StyleSheet.create({
flexOne: {
flex: 1,
},
flatList: {
flex: 1,
flexDirection: isAndroidRTL ? 'row-reverse' : 'row',
},
paginationContainer: {
position: 'absolute',
bottom: 16 + (isIphoneX ? 34 : 0),
left: 16,
right: 16,
},
paginationDots: {
height: 16,
margin: 16,
flexDirection: isAndroidRTL ? 'row-reverse' : 'row',
justifyContent: 'center',
alignItems: 'center',
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
marginHorizontal: 4,
},
leftButtonContainer: {
position: 'absolute',
left: 0,
},
rightButtonContainer: {
position: 'absolute',
right: 0,
},
bottomButton: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, .3)',
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
backgroundColor: 'transparent',
color: 'white',
fontSize: 18,
padding: 12,
},
});
import React from 'react';
import { StyleSheet, Text, View, Image, Dimensions, Platform } from 'react-native';
export default class DefaultSlide extends React.PureComponent {
render() {
const { item, dimensions, bottomButton } = this.props;
const style = {
flex: 1,
backgroundColor: item.backgroundColor,
width: dimensions.width,
paddingBottom: bottomButton ? 132 : 64,
};
return (
<View style={[styles.mainContent, style]}>
<Text style={[styles.title, item.titleStyle]}>{item.title}</Text>
<Image source={item.image} style={item.imageStyle} />
<Text style={[styles.text, item.textStyle]}>{item.text}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
mainContent: {
justifyContent: 'space-around',
alignItems: 'center',
},
text: {
color: 'rgba(255, 255, 255, .7)',
fontSize: 16,
textAlign: 'center',
fontWeight: '300',
paddingHorizontal: 16,
},
title: {
fontSize: 26,
color: 'rgba(255, 255, 255, .7)',
fontWeight: '300',
paddingHorizontal: 16,
},
});