react-native-app-intro-slider
Advanced tools
+218
| "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 | ||
| ``` | ||
| | | | | ||
| | ------------------------------------------------ | ------------------------------------------------------- | | ||
| |  |  | | ||
| ## 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` | | ||
| | ---------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------ | | ||
| |  |  |  | | ||
| 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 | ||
|  | ||
| ```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, | ||
| }, | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
23485
-12.75%12
Infinity%4
-50%219
-31.35%1
Infinity%192
-40.19%3
Infinity%1
Infinity%