Skip to content

Function for the Next button added. #220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,4 @@ experimental/
# Expo
.expo/

# Typescript
dist/
# Typescript
76 changes: 36 additions & 40 deletions README.md

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { FlatList, FlatListProps, ViewStyle, NativeScrollEvent, GestureResponderEvent, LayoutChangeEvent, ListRenderItemInfo } from 'react-native';
declare type Props<ItemT> = {
data: ItemT[];
renderItem: (info: ListRenderItemInfo<ItemT> & {
dimensions: {
width: number;
height: number;
};
}) => React.ReactNode;
renderSkipButton?: () => React.ReactNode;
renderNextButton?: () => React.ReactNode;
renderDoneButton?: () => React.ReactNode;
renderPrevButton?: () => React.ReactNode;
onSlideChange?: (a: number, b: number) => void;
onSkip?: () => void;
onDone?: () => void;
onNext?: () => void;
renderPagination?: (activeIndex: number) => React.ReactNode;
activeDotStyle: ViewStyle;
dotStyle: ViewStyle;
dotClickEnabled: boolean;
skipLabel: string;
doneLabel: string;
nextLabel: string;
prevLabel: string;
showDoneButton: boolean;
showNextButton: boolean;
showPrevButton: boolean;
showSkipButton: boolean;
bottomButton: boolean;
} & FlatListProps<ItemT>;
declare type State = {
width: number;
height: number;
activeIndex: number;
};
export default class AppIntroSlider<ItemT = any> extends React.Component<Props<ItemT>, State> {
static defaultProps: {
activeDotStyle: {
backgroundColor: string;
};
dotStyle: {
backgroundColor: string;
};
dotClickEnabled: boolean;
skipLabel: string;
doneLabel: string;
nextLabel: string;
prevLabel: string;
showDoneButton: boolean;
showNextButton: boolean;
showPrevButton: boolean;
showSkipButton: boolean;
bottomButton: boolean;
};
state: {
width: number;
height: number;
activeIndex: number;
};
flatList: FlatList<ItemT> | undefined;
goToSlide: (pageNum: number, triggerOnSlideChange?: boolean | undefined) => void;
getListRef: () => FlatList<ItemT> | undefined;
_rtlSafeIndex: (i: number) => number;
_renderItem: (flatListArgs: any) => JSX.Element;
_renderButton: (name: string, label: string, onPress?: (() => void) | undefined, render?: (() => React.ReactNode) | undefined) => JSX.Element;
_renderDefaultButton: (name: string, label: string) => JSX.Element;
_renderOuterButton: (content: React.ReactNode, name: string, onPress?: ((e: GestureResponderEvent) => void) | undefined) => JSX.Element;
_renderNextButton: () => false | JSX.Element;
_renderPrevButton: () => false | JSX.Element;
_renderDoneButton: () => false | JSX.Element;
_renderSkipButton: () => false | JSX.Element;
_renderPagination: () => JSX.Element;
_onMomentumScrollEnd: (e: {
nativeEvent: NativeScrollEvent;
}) => void;
_onLayout: ({ nativeEvent }: LayoutChangeEvent) => void;
render(): JSX.Element;
}
export {};
249 changes: 249 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const React = __importStar(require("react"));
const react_native_1 = require("react-native");
const merge_extradata_1 = __importDefault(require("./merge-extradata"));
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.props.onNext
? this.props.onNext()
: 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) => this.props.dotClickEnabled ? (<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 key={i} style={[
styles.dot,
this._rtlSafeIndex(i) === this.state.activeIndex
? this.props.activeDotStyle
: this.props.dotStyle,
]}/>))}
</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, extraData, ...otherProps } = this.props;
/* eslint-enable @typescript-eslint/no-unused-vars */
// Merge component width and user-defined extraData
const extra = merge_extradata_1.default(extraData, this.state.width);
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={extra} 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)',
},
dotClickEnabled: true,
skipLabel: 'Skip',
doneLabel: 'Done',
nextLabel: 'Next',
prevLabel: 'Back',
showDoneButton: true,
showNextButton: true,
showPrevButton: false,
showSkipButton: false,
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,
justifyContent: 'center',
},
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,
},
});
1 change: 1 addition & 0 deletions dist/merge-extradata.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function mergeExtraData(...newArgs: unknown[]): number;
25 changes: 25 additions & 0 deletions dist/merge-extradata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function areInputsEqual(newInputs, lastInputs) {
// Using for loop for speed. It generally performs better than array.every
// https://github.com/alexreardon/memoize-one/pull/59
for (let i = 0; i < newInputs.length; i++) {
// using shallow equality check
if (newInputs[i] !== lastInputs[i]) {
return false;
}
}
return true;
}
let lastArgs = [];
let lastResult = 0;
function mergeExtraData(...newArgs) {
if (areInputsEqual(newArgs, lastArgs)) {
return lastResult;
}
// Something shallowly changed - return a new number from [0-10]
lastResult = lastResult === 10 ? 0 : lastResult + 1;
lastArgs = newArgs;
return lastResult;
}
exports.default = mergeExtraData;
Loading