react-native-app-onboard 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -7
- package/lib/commonjs/components/CustomPages.js +31 -54
- package/lib/commonjs/components/CustomPages.js.map +1 -1
- package/lib/commonjs/components/OnboardingPages.js +59 -74
- package/lib/commonjs/components/OnboardingPages.js.map +1 -1
- package/lib/commonjs/components/Page.js +8 -3
- package/lib/commonjs/components/Page.js.map +1 -1
- package/lib/commonjs/components/Pagination.js +75 -13
- package/lib/commonjs/components/Pagination.js.map +1 -1
- package/lib/commonjs/components/Swiper.js +58 -85
- package/lib/commonjs/components/Swiper.js.map +1 -1
- package/lib/commonjs/components/button.js +3 -1
- package/lib/commonjs/components/button.js.map +1 -1
- package/lib/commonjs/context/OnboardingContext.js +101 -21
- package/lib/commonjs/context/OnboardingContext.js.map +1 -1
- package/lib/commonjs/hooks/useOnboarding.js +1 -1
- package/lib/commonjs/hooks/useOnboarding.js.map +1 -1
- package/lib/commonjs/index.js +33 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/persistence.js +51 -0
- package/lib/commonjs/utils/persistence.js.map +1 -0
- package/lib/module/components/CustomPages.js +31 -54
- package/lib/module/components/CustomPages.js.map +1 -1
- package/lib/module/components/OnboardingPages.js +60 -75
- package/lib/module/components/OnboardingPages.js.map +1 -1
- package/lib/module/components/Page.js +8 -3
- package/lib/module/components/Page.js.map +1 -1
- package/lib/module/components/Pagination.js +76 -14
- package/lib/module/components/Pagination.js.map +1 -1
- package/lib/module/components/Swiper.js +59 -86
- package/lib/module/components/Swiper.js.map +1 -1
- package/lib/module/components/button.js +3 -1
- package/lib/module/components/button.js.map +1 -1
- package/lib/module/context/OnboardingContext.js +102 -22
- package/lib/module/context/OnboardingContext.js.map +1 -1
- package/lib/module/hooks/useOnboarding.js +1 -1
- package/lib/module/hooks/useOnboarding.js.map +1 -1
- package/lib/module/index.js +8 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/persistence.js +42 -0
- package/lib/module/utils/persistence.js.map +1 -0
- package/lib/typescript/src/components/CustomPages.d.ts +6 -2
- package/lib/typescript/src/components/CustomPages.d.ts.map +1 -1
- package/lib/typescript/src/components/OnboardingPages.d.ts +6 -2
- package/lib/typescript/src/components/OnboardingPages.d.ts.map +1 -1
- package/lib/typescript/src/components/Page.d.ts +2 -0
- package/lib/typescript/src/components/Page.d.ts.map +1 -1
- package/lib/typescript/src/components/Pagination.d.ts +9 -0
- package/lib/typescript/src/components/Pagination.d.ts.map +1 -1
- package/lib/typescript/src/components/Swiper.d.ts.map +1 -1
- package/lib/typescript/src/components/button.d.ts.map +1 -1
- package/lib/typescript/src/context/OnboardingContext.d.ts +9 -0
- package/lib/typescript/src/context/OnboardingContext.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useOnboarding.d.ts +3 -0
- package/lib/typescript/src/hooks/useOnboarding.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +13 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/persistence.d.ts +31 -0
- package/lib/typescript/src/utils/persistence.d.ts.map +1 -0
- package/package.json +25 -4
- package/src/components/CustomPages.tsx +63 -69
- package/src/components/OnboardingPages.tsx +79 -82
- package/src/components/Page.tsx +8 -2
- package/src/components/Pagination.tsx +121 -29
- package/src/components/Swiper.tsx +65 -87
- package/src/components/button.tsx +6 -1
- package/src/context/OnboardingContext.tsx +145 -26
- package/src/hooks/useOnboarding.tsx +1 -3
- package/src/index.tsx +16 -1
- package/src/types/index.ts +13 -0
- package/src/utils/persistence.ts +58 -0
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
View,
|
|
3
|
+
Dimensions,
|
|
4
|
+
Animated,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
FlatList,
|
|
7
|
+
type NativeSyntheticEvent,
|
|
8
|
+
type NativeScrollEvent,
|
|
9
|
+
} from 'react-native';
|
|
2
10
|
import React from 'react';
|
|
3
11
|
import { Pagination } from './Pagination';
|
|
4
12
|
import type { OnboardingProps } from '../types';
|
|
@@ -10,10 +18,16 @@ type CustomPagesProps = OnboardingProps & {
|
|
|
10
18
|
children?: React.ReactNode[];
|
|
11
19
|
currentPage: number;
|
|
12
20
|
setPage: (newPageIndex: number) => void;
|
|
13
|
-
|
|
21
|
+
setFlatListRef: (node: FlatList | null) => void;
|
|
14
22
|
scrollX: Animated.Value;
|
|
23
|
+
dotsAnimatedValue: Animated.Value;
|
|
24
|
+
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
25
|
+
onScrollBeginDrag: () => void;
|
|
15
26
|
nextPage: () => void;
|
|
16
27
|
numberOfScreens: number;
|
|
28
|
+
// Accepted for prop-spread compatibility. Forced-`rtl` mirroring applies to
|
|
29
|
+
// the declarative `pages` API; custom children follow the device direction.
|
|
30
|
+
mirror?: boolean;
|
|
17
31
|
};
|
|
18
32
|
|
|
19
33
|
export type SliderProps = {
|
|
@@ -28,6 +42,45 @@ export const CustomPages = ({
|
|
|
28
42
|
showNext = true,
|
|
29
43
|
...props
|
|
30
44
|
}: CustomPagesProps) => {
|
|
45
|
+
const pageWidth = props.width || width;
|
|
46
|
+
|
|
47
|
+
const paginationProps = {
|
|
48
|
+
color: '#fff',
|
|
49
|
+
backgroundColor: '#333',
|
|
50
|
+
width: pageWidth,
|
|
51
|
+
onNext: props.nextPage,
|
|
52
|
+
onSkip: props.onSkip,
|
|
53
|
+
onDone: props.onDone,
|
|
54
|
+
showDone: props.showDone,
|
|
55
|
+
showPrevious: props.showPrevious,
|
|
56
|
+
animatedValue: props.dotsAnimatedValue,
|
|
57
|
+
showSkip: props.showSkip,
|
|
58
|
+
numberOfScreens: props.numberOfScreens,
|
|
59
|
+
skipLabel: props.skipLabel,
|
|
60
|
+
showNext,
|
|
61
|
+
nextLabel: props.nextLabel,
|
|
62
|
+
previousLabel: props.previousLabel,
|
|
63
|
+
doneLabel: props.doneLabel,
|
|
64
|
+
hasSkipPosition: !!props.skipButtonPosition,
|
|
65
|
+
paginationStyle: props.paginationStyle,
|
|
66
|
+
progressBarStyle: props.progressBarStyle,
|
|
67
|
+
progressBarFillStyle: props.progressBarFillStyle,
|
|
68
|
+
dotsAreTappable: props.dotsAreTappable,
|
|
69
|
+
paginationContainerStyle: props.paginationContainerStyle,
|
|
70
|
+
buttonRightContainerStyle: props.buttonRightContainerStyle,
|
|
71
|
+
buttonLeftContainerStyle: props.buttonLeftContainerStyle,
|
|
72
|
+
dotsContainerStyle: props.dotsContainerStyle,
|
|
73
|
+
doneLabelStyle: props.doneLabelStyle,
|
|
74
|
+
skipButtonContainerStyle: props.skipButtonContainerStyle,
|
|
75
|
+
nextButtonContainerStyle: props.nextButtonContainerStyle,
|
|
76
|
+
doneButtonContainerStyle: props.doneButtonContainerStyle,
|
|
77
|
+
previousButtonContainerStyle: props.previousButtonContainerStyle,
|
|
78
|
+
skipLabelStyle: props.skipLabelStyle,
|
|
79
|
+
previousLabelStyle: props.previousLabelStyle,
|
|
80
|
+
nextLabelStyle: props.nextLabelStyle,
|
|
81
|
+
paginationPosition: props.paginationPosition,
|
|
82
|
+
};
|
|
83
|
+
|
|
31
84
|
return (
|
|
32
85
|
<View style={[styles.container]}>
|
|
33
86
|
{props.skipButtonPosition && props.showSkip && (
|
|
@@ -36,59 +89,29 @@ export const CustomPages = ({
|
|
|
36
89
|
buttonStyle={props.skipButtonContainerStyle}
|
|
37
90
|
position={props.skipButtonPosition}
|
|
38
91
|
onPress={props.onSkip}
|
|
92
|
+
label={props.skipLabel}
|
|
39
93
|
/>
|
|
40
94
|
)}
|
|
41
95
|
{showPagination && props.paginationPosition === 'top' && (
|
|
42
96
|
<>
|
|
43
97
|
{props.customFooter &&
|
|
44
98
|
props.customFooter({ nextPage: props.nextPage })}
|
|
45
|
-
{!props.customFooter &&
|
|
46
|
-
<Pagination
|
|
47
|
-
color={'#fff'}
|
|
48
|
-
backgroundColor={'#333'}
|
|
49
|
-
width={width}
|
|
50
|
-
onNext={props.nextPage}
|
|
51
|
-
onSkip={props.onSkip}
|
|
52
|
-
onDone={props.onDone}
|
|
53
|
-
showDone={props.showDone}
|
|
54
|
-
animatedValue={props.scrollX}
|
|
55
|
-
showSkip={props.showSkip}
|
|
56
|
-
numberOfScreens={props.numberOfScreens}
|
|
57
|
-
skipLabel={props.skipLabel}
|
|
58
|
-
showNext={showNext}
|
|
59
|
-
nextLabel={props.nextLabel}
|
|
60
|
-
doneLabel={props.doneLabel}
|
|
61
|
-
hasSkipPosition={!!props.skipButtonPosition}
|
|
62
|
-
paginationContainerStyle={props.paginationContainerStyle}
|
|
63
|
-
buttonRightContainerStyle={props.buttonRightContainerStyle}
|
|
64
|
-
buttonLeftContainerStyle={props.buttonLeftContainerStyle}
|
|
65
|
-
dotsContainerStyle={props.dotsContainerStyle}
|
|
66
|
-
doneLabelStyle={props.doneLabelStyle}
|
|
67
|
-
skipButtonContainerStyle={props.skipButtonContainerStyle}
|
|
68
|
-
nextButtonContainerStyle={props.nextButtonContainerStyle}
|
|
69
|
-
doneButtonContainerStyle={props.doneButtonContainerStyle}
|
|
70
|
-
skipLabelStyle={props.skipLabelStyle}
|
|
71
|
-
nextLabelStyle={props.nextLabelStyle}
|
|
72
|
-
paginationPosition={props.paginationPosition}
|
|
73
|
-
/>
|
|
74
|
-
)}
|
|
99
|
+
{!props.customFooter && <Pagination {...paginationProps} />}
|
|
75
100
|
</>
|
|
76
101
|
)}
|
|
77
102
|
<Animated.FlatList
|
|
78
|
-
ref={props.
|
|
103
|
+
ref={(node) => props.setFlatListRef(node as FlatList | null)}
|
|
79
104
|
data={React.Children.toArray(props.children)}
|
|
80
105
|
horizontal
|
|
81
106
|
pagingEnabled
|
|
82
107
|
showsHorizontalScrollIndicator={false}
|
|
83
108
|
scrollEnabled={props.scrollEnabled}
|
|
84
|
-
onScroll={
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)}
|
|
88
|
-
scrollEventThrottle={1}
|
|
109
|
+
onScroll={props.onScroll}
|
|
110
|
+
onScrollBeginDrag={props.onScrollBeginDrag}
|
|
111
|
+
scrollEventThrottle={16}
|
|
89
112
|
onMomentumScrollEnd={(event) => {
|
|
90
113
|
const pageIndex = Math.round(
|
|
91
|
-
event.nativeEvent.contentOffset.x /
|
|
114
|
+
event.nativeEvent.contentOffset.x / pageWidth
|
|
92
115
|
);
|
|
93
116
|
props.setPage(pageIndex || 0);
|
|
94
117
|
}}
|
|
@@ -110,36 +133,7 @@ export const CustomPages = ({
|
|
|
110
133
|
<>
|
|
111
134
|
{props.customFooter &&
|
|
112
135
|
props.customFooter({ nextPage: props.nextPage })}
|
|
113
|
-
{!props.customFooter &&
|
|
114
|
-
<Pagination
|
|
115
|
-
color={'#fff'}
|
|
116
|
-
backgroundColor={'#333'}
|
|
117
|
-
width={width}
|
|
118
|
-
onNext={props.nextPage}
|
|
119
|
-
onSkip={props.onSkip}
|
|
120
|
-
onDone={props.onDone}
|
|
121
|
-
showDone={props.showDone}
|
|
122
|
-
animatedValue={props.scrollX}
|
|
123
|
-
showSkip={props.showSkip}
|
|
124
|
-
numberOfScreens={props.numberOfScreens}
|
|
125
|
-
skipLabel={props.skipLabel}
|
|
126
|
-
nextLabel={props.nextLabel}
|
|
127
|
-
showNext={showNext}
|
|
128
|
-
doneLabel={props.doneLabel}
|
|
129
|
-
paginationContainerStyle={props.paginationContainerStyle}
|
|
130
|
-
buttonRightContainerStyle={props.buttonRightContainerStyle}
|
|
131
|
-
buttonLeftContainerStyle={props.buttonLeftContainerStyle}
|
|
132
|
-
dotsContainerStyle={props.dotsContainerStyle}
|
|
133
|
-
doneLabelStyle={props.doneLabelStyle}
|
|
134
|
-
skipButtonContainerStyle={props.skipButtonContainerStyle}
|
|
135
|
-
nextButtonContainerStyle={props.nextButtonContainerStyle}
|
|
136
|
-
doneButtonContainerStyle={props.doneButtonContainerStyle}
|
|
137
|
-
skipLabelStyle={props.skipLabelStyle}
|
|
138
|
-
hasSkipPosition={!!props.skipButtonPosition}
|
|
139
|
-
paginationPosition={props.paginationPosition}
|
|
140
|
-
nextLabelStyle={props.nextLabelStyle}
|
|
141
|
-
/>
|
|
142
|
-
)}
|
|
136
|
+
{!props.customFooter && <Pagination {...paginationProps} />}
|
|
143
137
|
</>
|
|
144
138
|
)}
|
|
145
139
|
</View>
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import tinycolor from 'tinycolor2';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Animated,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Dimensions,
|
|
7
|
+
FlatList,
|
|
8
|
+
type NativeSyntheticEvent,
|
|
9
|
+
type NativeScrollEvent,
|
|
10
|
+
} from 'react-native';
|
|
4
11
|
import { Pagination } from './Pagination';
|
|
5
12
|
import { OnboardingPage, type Page } from './Page';
|
|
6
13
|
import type { OnboardingProps } from '../types';
|
|
@@ -12,9 +19,13 @@ type Props = OnboardingProps & {
|
|
|
12
19
|
pages: Page[];
|
|
13
20
|
currentPage: number;
|
|
14
21
|
setPage: (newPageIndex: number) => void;
|
|
15
|
-
|
|
22
|
+
setFlatListRef: (node: FlatList | null) => void;
|
|
16
23
|
scrollX: Animated.Value;
|
|
24
|
+
dotsAnimatedValue: Animated.Value;
|
|
25
|
+
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
26
|
+
onScrollBeginDrag: () => void;
|
|
17
27
|
nextPage: () => void;
|
|
28
|
+
mirror?: boolean;
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
export const OnboardingPages = ({
|
|
@@ -22,10 +33,9 @@ export const OnboardingPages = ({
|
|
|
22
33
|
showNext = true,
|
|
23
34
|
...props
|
|
24
35
|
}: Props) => {
|
|
25
|
-
const
|
|
26
|
-
const [previousPage, setPreviousPage] = React.useState(0);
|
|
36
|
+
const pageWidth = props.width || width;
|
|
27
37
|
const currentPage_ = props.pages[props.currentPage];
|
|
28
|
-
const currentBackgroundColor = currentPage_?.backgroundColor
|
|
38
|
+
const currentBackgroundColor = currentPage_?.backgroundColor ?? 'white';
|
|
29
39
|
const isLight = tinycolor(currentBackgroundColor).getBrightness() > 180;
|
|
30
40
|
const footerBackgroundColor = isLight
|
|
31
41
|
? tinycolor(currentBackgroundColor).darken(30).toString()
|
|
@@ -35,27 +45,62 @@ export const OnboardingPages = ({
|
|
|
35
45
|
? tinycolor(footerBackgroundColor).darken(60).toString()
|
|
36
46
|
: tinycolor(footerBackgroundColor).lighten(60).toString();
|
|
37
47
|
|
|
38
|
-
const
|
|
39
|
-
props.pages
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
const interpolatedBackgroundColor = useMemo(() => {
|
|
49
|
+
const pages = props.pages;
|
|
50
|
+
// interpolate() requires at least 2 stops; for 0 or 1 pages there is
|
|
51
|
+
// nothing to animate between, so use the solid current color.
|
|
52
|
+
if (pages.length < 2) return currentBackgroundColor;
|
|
53
|
+
const inputRange = pages.map((_, i) => i * pageWidth);
|
|
54
|
+
const outputRange = pages.map((p) => p.backgroundColor ?? 'white');
|
|
55
|
+
return props.scrollX.interpolate({
|
|
56
|
+
inputRange,
|
|
57
|
+
outputRange,
|
|
58
|
+
extrapolate: 'clamp',
|
|
59
|
+
});
|
|
60
|
+
}, [props.pages, props.scrollX, pageWidth, currentBackgroundColor]);
|
|
46
61
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
props.
|
|
62
|
+
const paginationProps = {
|
|
63
|
+
width: pageWidth,
|
|
64
|
+
onNext: props.nextPage,
|
|
65
|
+
onSkip: props.onSkip,
|
|
66
|
+
color,
|
|
67
|
+
showNext,
|
|
68
|
+
onDone: props.onDone,
|
|
69
|
+
showDone: props.showDone,
|
|
70
|
+
showPrevious: props.showPrevious,
|
|
71
|
+
backgroundColor: footerBackgroundColor,
|
|
72
|
+
animatedValue: props.dotsAnimatedValue,
|
|
73
|
+
showSkip: props.showSkip,
|
|
74
|
+
numberOfScreens: props.pages.length,
|
|
75
|
+
skipLabel: props.skipLabel,
|
|
76
|
+
nextLabel: props.nextLabel,
|
|
77
|
+
previousLabel: props.previousLabel,
|
|
78
|
+
hasSkipPosition: !!props.skipButtonPosition,
|
|
79
|
+
doneLabel: props.doneLabel,
|
|
80
|
+
paginationStyle: props.paginationStyle,
|
|
81
|
+
progressBarStyle: props.progressBarStyle,
|
|
82
|
+
progressBarFillStyle: props.progressBarFillStyle,
|
|
83
|
+
dotsAreTappable: props.dotsAreTappable,
|
|
84
|
+
mirror: props.mirror,
|
|
85
|
+
paginationContainerStyle: props.paginationContainerStyle,
|
|
86
|
+
buttonRightContainerStyle: props.buttonRightContainerStyle,
|
|
87
|
+
buttonLeftContainerStyle: props.buttonLeftContainerStyle,
|
|
88
|
+
dotsContainerStyle: props.dotsContainerStyle,
|
|
89
|
+
doneLabelStyle: props.doneLabelStyle,
|
|
90
|
+
skipLabelStyle: props.skipLabelStyle,
|
|
91
|
+
previousLabelStyle: props.previousLabelStyle,
|
|
92
|
+
nextLabelStyle: props.nextLabelStyle,
|
|
93
|
+
skipButtonContainerStyle: props.skipButtonContainerStyle,
|
|
94
|
+
nextButtonContainerStyle: props.nextButtonContainerStyle,
|
|
95
|
+
doneButtonContainerStyle: props.doneButtonContainerStyle,
|
|
96
|
+
previousButtonContainerStyle: props.previousButtonContainerStyle,
|
|
50
97
|
};
|
|
51
98
|
|
|
52
99
|
return (
|
|
53
100
|
<Animated.View
|
|
54
101
|
style={[
|
|
55
102
|
styles.container,
|
|
56
|
-
{
|
|
57
|
-
backgroundColor: interpolatedBackgroundColor,
|
|
58
|
-
},
|
|
103
|
+
{ backgroundColor: interpolatedBackgroundColor },
|
|
59
104
|
]}
|
|
60
105
|
>
|
|
61
106
|
{props.skipButtonPosition && props.showSkip && (
|
|
@@ -73,62 +118,37 @@ export const OnboardingPages = ({
|
|
|
73
118
|
props.customFooter &&
|
|
74
119
|
props.customFooter({ nextPage: props.nextPage })}
|
|
75
120
|
{!props.customFooter && showPagination && (
|
|
76
|
-
<Pagination
|
|
77
|
-
width={props.width || width}
|
|
78
|
-
onNext={props.nextPage}
|
|
79
|
-
onSkip={props.onSkip}
|
|
80
|
-
color={color}
|
|
81
|
-
showNext={showNext}
|
|
82
|
-
onDone={props.onDone}
|
|
83
|
-
showDone={props.showDone}
|
|
84
|
-
backgroundColor={footerBackgroundColor}
|
|
85
|
-
animatedValue={props.scrollX}
|
|
86
|
-
showSkip={props.showSkip}
|
|
87
|
-
numberOfScreens={props.pages.length}
|
|
88
|
-
skipLabel={props.skipLabel}
|
|
89
|
-
nextLabel={props.nextLabel}
|
|
90
|
-
hasSkipPosition={!!props.skipButtonPosition}
|
|
91
|
-
doneLabel={props.doneLabel}
|
|
92
|
-
paginationContainerStyle={props.paginationContainerStyle}
|
|
93
|
-
buttonRightContainerStyle={props.buttonRightContainerStyle}
|
|
94
|
-
buttonLeftContainerStyle={props.buttonLeftContainerStyle}
|
|
95
|
-
dotsContainerStyle={props.dotsContainerStyle}
|
|
96
|
-
doneLabelStyle={props.doneLabelStyle}
|
|
97
|
-
skipLabelStyle={props.skipLabelStyle}
|
|
98
|
-
nextLabelStyle={props.nextLabelStyle}
|
|
99
|
-
skipButtonContainerStyle={props.skipButtonContainerStyle}
|
|
100
|
-
nextButtonContainerStyle={props.nextButtonContainerStyle}
|
|
101
|
-
doneButtonContainerStyle={props.doneButtonContainerStyle}
|
|
102
|
-
/>
|
|
121
|
+
<Pagination {...paginationProps} />
|
|
103
122
|
)}
|
|
104
123
|
</>
|
|
105
124
|
)}
|
|
106
125
|
<Animated.FlatList
|
|
107
|
-
ref={props.
|
|
126
|
+
ref={(node) => props.setFlatListRef(node as FlatList | null)}
|
|
108
127
|
data={props.pages}
|
|
109
128
|
horizontal
|
|
110
129
|
pagingEnabled
|
|
111
130
|
showsHorizontalScrollIndicator={false}
|
|
131
|
+
scrollEnabled={props.scrollEnabled}
|
|
132
|
+
style={props.mirror ? styles.mirror : undefined}
|
|
112
133
|
keyExtractor={(_, index) => index.toString()}
|
|
113
134
|
renderItem={({ item, index }) => (
|
|
114
135
|
<OnboardingPage
|
|
115
136
|
color={color}
|
|
116
|
-
width={
|
|
137
|
+
width={pageWidth}
|
|
117
138
|
swap={props.swap}
|
|
139
|
+
mirror={props.mirror}
|
|
118
140
|
key={index}
|
|
119
141
|
{...item}
|
|
120
142
|
/>
|
|
121
143
|
)}
|
|
122
|
-
onScroll={
|
|
123
|
-
|
|
124
|
-
{ useNativeDriver: false }
|
|
125
|
-
)}
|
|
144
|
+
onScroll={props.onScroll}
|
|
145
|
+
onScrollBeginDrag={props.onScrollBeginDrag}
|
|
126
146
|
scrollEventThrottle={16}
|
|
127
147
|
onMomentumScrollEnd={(event) => {
|
|
128
148
|
const pageIndex = Math.round(
|
|
129
|
-
event.nativeEvent.contentOffset.x /
|
|
149
|
+
event.nativeEvent.contentOffset.x / pageWidth
|
|
130
150
|
);
|
|
131
|
-
|
|
151
|
+
props.setPage(pageIndex || 0);
|
|
132
152
|
}}
|
|
133
153
|
/>
|
|
134
154
|
{props.paginationPosition !== 'top' && (
|
|
@@ -137,33 +157,7 @@ export const OnboardingPages = ({
|
|
|
137
157
|
props.customFooter &&
|
|
138
158
|
props.customFooter({ nextPage: props.nextPage })}
|
|
139
159
|
{!props.customFooter && showPagination && (
|
|
140
|
-
<Pagination
|
|
141
|
-
width={props.width || width}
|
|
142
|
-
onNext={props.nextPage}
|
|
143
|
-
onSkip={props.onSkip}
|
|
144
|
-
color={color}
|
|
145
|
-
hasSkipPosition={!!props.skipButtonPosition}
|
|
146
|
-
onDone={props.onDone}
|
|
147
|
-
showDone={props.showDone}
|
|
148
|
-
backgroundColor={footerBackgroundColor}
|
|
149
|
-
animatedValue={props.scrollX}
|
|
150
|
-
showSkip={props.showSkip}
|
|
151
|
-
numberOfScreens={props.pages.length}
|
|
152
|
-
skipLabel={props.skipLabel}
|
|
153
|
-
nextLabel={props.nextLabel}
|
|
154
|
-
doneLabel={props.doneLabel}
|
|
155
|
-
paginationContainerStyle={props.paginationContainerStyle}
|
|
156
|
-
buttonRightContainerStyle={props.buttonRightContainerStyle}
|
|
157
|
-
buttonLeftContainerStyle={props.buttonLeftContainerStyle}
|
|
158
|
-
dotsContainerStyle={props.dotsContainerStyle}
|
|
159
|
-
doneLabelStyle={props.doneLabelStyle}
|
|
160
|
-
skipLabelStyle={props.skipLabelStyle}
|
|
161
|
-
skipButtonContainerStyle={props.skipButtonContainerStyle}
|
|
162
|
-
nextButtonContainerStyle={props.nextButtonContainerStyle}
|
|
163
|
-
doneButtonContainerStyle={props.doneButtonContainerStyle}
|
|
164
|
-
showNext={showNext}
|
|
165
|
-
nextLabelStyle={props.nextLabelStyle}
|
|
166
|
-
/>
|
|
160
|
+
<Pagination {...paginationProps} />
|
|
167
161
|
)}
|
|
168
162
|
</>
|
|
169
163
|
)}
|
|
@@ -202,4 +196,7 @@ const styles = StyleSheet.create({
|
|
|
202
196
|
flex: 1,
|
|
203
197
|
justifyContent: 'center',
|
|
204
198
|
},
|
|
199
|
+
mirror: {
|
|
200
|
+
transform: [{ scaleX: -1 }],
|
|
201
|
+
},
|
|
205
202
|
});
|
package/src/components/Page.tsx
CHANGED
|
@@ -22,10 +22,12 @@ export type Page = {
|
|
|
22
22
|
titleStyle?: StyleProp<TextStyle>;
|
|
23
23
|
subtitleStyle?: StyleProp<TextStyle>;
|
|
24
24
|
swap?: boolean;
|
|
25
|
+
/** Internal: counter-flips page content when the slider is mirrored for RTL. */
|
|
26
|
+
mirror?: boolean;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
const { width, height } = Dimensions.get('window');
|
|
28
|
-
const
|
|
30
|
+
const portrait = height > width;
|
|
29
31
|
|
|
30
32
|
export function OnboardingPage(props: Page) {
|
|
31
33
|
return (
|
|
@@ -35,6 +37,7 @@ export function OnboardingPage(props: Page) {
|
|
|
35
37
|
{ width: props.width },
|
|
36
38
|
props.containerStyle,
|
|
37
39
|
props.swap && styles.swapStyle,
|
|
40
|
+
props.mirror && styles.mirror,
|
|
38
41
|
]}
|
|
39
42
|
>
|
|
40
43
|
<View style={[styles.imageContainer, props.imageContainerStyle]}>
|
|
@@ -87,7 +90,7 @@ const styles = StyleSheet.create({
|
|
|
87
90
|
},
|
|
88
91
|
imageContainer: {
|
|
89
92
|
flex: 0,
|
|
90
|
-
paddingBottom:
|
|
93
|
+
paddingBottom: portrait ? 60 : 10,
|
|
91
94
|
alignItems: 'center',
|
|
92
95
|
width: '100%',
|
|
93
96
|
},
|
|
@@ -97,4 +100,7 @@ const styles = StyleSheet.create({
|
|
|
97
100
|
swapStyle: {
|
|
98
101
|
flexDirection: 'column-reverse',
|
|
99
102
|
},
|
|
103
|
+
mirror: {
|
|
104
|
+
transform: [{ scaleX: -1 }],
|
|
105
|
+
},
|
|
100
106
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
View,
|
|
3
|
+
Pressable,
|
|
3
4
|
Animated,
|
|
4
5
|
StyleSheet,
|
|
5
6
|
type StyleProp,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
type TextStyle,
|
|
8
9
|
} from 'react-native';
|
|
9
10
|
import React from 'react';
|
|
11
|
+
import tinycolor from 'tinycolor2';
|
|
10
12
|
import { useOnboarding } from '../hooks/useOnboarding';
|
|
11
13
|
import { Button } from './button';
|
|
12
14
|
|
|
@@ -19,9 +21,11 @@ type FooterProps = {
|
|
|
19
21
|
showDone?: boolean;
|
|
20
22
|
showSkip?: boolean;
|
|
21
23
|
showNext?: boolean;
|
|
24
|
+
showPrevious?: boolean;
|
|
22
25
|
nextLabel?: string | React.ReactNode;
|
|
23
26
|
skipLabel?: string | React.ReactNode;
|
|
24
27
|
doneLabel?: string | React.ReactNode;
|
|
28
|
+
previousLabel?: string | React.ReactNode;
|
|
25
29
|
paginationContainerStyle?: StyleProp<ViewStyle>;
|
|
26
30
|
buttonRightContainerStyle?: StyleProp<ViewStyle>;
|
|
27
31
|
buttonLeftContainerStyle?: StyleProp<ViewStyle>;
|
|
@@ -29,20 +33,30 @@ type FooterProps = {
|
|
|
29
33
|
doneLabelStyle?: StyleProp<TextStyle>;
|
|
30
34
|
hasSkipPosition?: boolean;
|
|
31
35
|
skipLabelStyle?: StyleProp<TextStyle>;
|
|
36
|
+
previousLabelStyle?: StyleProp<TextStyle>;
|
|
32
37
|
skipButtonContainerStyle?: StyleProp<ViewStyle>;
|
|
33
38
|
nextButtonContainerStyle?: StyleProp<ViewStyle>;
|
|
34
39
|
doneButtonContainerStyle?: StyleProp<ViewStyle>;
|
|
40
|
+
previousButtonContainerStyle?: StyleProp<ViewStyle>;
|
|
35
41
|
nextLabelStyle?: StyleProp<TextStyle>;
|
|
36
42
|
paginationPosition?: 'top' | 'bottom';
|
|
43
|
+
paginationStyle?: 'dots' | 'progress';
|
|
44
|
+
progressBarStyle?: StyleProp<ViewStyle>;
|
|
45
|
+
progressBarFillStyle?: StyleProp<ViewStyle>;
|
|
46
|
+
dotsAreTappable?: boolean;
|
|
47
|
+
mirror?: boolean;
|
|
37
48
|
onDone?: () => void;
|
|
38
49
|
onSkip?: () => void;
|
|
39
50
|
onNext?: () => void;
|
|
40
51
|
};
|
|
41
52
|
|
|
42
53
|
export function Pagination(props: FooterProps) {
|
|
43
|
-
const { isDone } =
|
|
54
|
+
const { isDone, currentPage, progress, scrollTo, previousPage } =
|
|
55
|
+
useOnboarding();
|
|
44
56
|
const dots = Array.from({ length: props.numberOfScreens }, (_, i) => i);
|
|
45
57
|
const width = props.width;
|
|
58
|
+
const showPrevious = props.showPrevious && currentPage > 0;
|
|
59
|
+
|
|
46
60
|
return (
|
|
47
61
|
<View
|
|
48
62
|
style={[
|
|
@@ -60,7 +74,15 @@ export function Pagination(props: FooterProps) {
|
|
|
60
74
|
props.buttonLeftContainerStyle,
|
|
61
75
|
]}
|
|
62
76
|
>
|
|
63
|
-
{
|
|
77
|
+
{showPrevious && (
|
|
78
|
+
<Button
|
|
79
|
+
onPress={() => previousPage()}
|
|
80
|
+
buttonTextStyle={props.previousLabelStyle}
|
|
81
|
+
buttonStyle={props.previousButtonContainerStyle}
|
|
82
|
+
label={props.previousLabel || 'Back'}
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
{!showPrevious && props.showSkip && !props.hasSkipPosition && (
|
|
64
86
|
<Button
|
|
65
87
|
onPress={props.onSkip}
|
|
66
88
|
buttonTextStyle={props.skipLabelStyle}
|
|
@@ -69,29 +91,86 @@ export function Pagination(props: FooterProps) {
|
|
|
69
91
|
/>
|
|
70
92
|
)}
|
|
71
93
|
</View>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
|
|
95
|
+
{props.paginationStyle === 'progress' ? (
|
|
96
|
+
<View
|
|
97
|
+
accessible
|
|
98
|
+
accessibilityRole="progressbar"
|
|
99
|
+
accessibilityValue={{ min: 0, max: 100, now: progress }}
|
|
100
|
+
style={[
|
|
101
|
+
styles.progressTrack,
|
|
102
|
+
// Derive a faint track from the (background-aware) fill color so it
|
|
103
|
+
// stays visible on both light and dark pages.
|
|
104
|
+
{
|
|
105
|
+
backgroundColor: tinycolor(props.color)
|
|
106
|
+
.setAlpha(0.25)
|
|
107
|
+
.toRgbString(),
|
|
108
|
+
},
|
|
109
|
+
// Mirror the fill direction so it grows from the trailing edge.
|
|
110
|
+
props.mirror && styles.mirror,
|
|
111
|
+
props.progressBarStyle,
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
<View
|
|
115
|
+
style={[
|
|
116
|
+
styles.progressFill,
|
|
117
|
+
{ backgroundColor: props.color, width: `${progress}%` },
|
|
118
|
+
props.progressBarFillStyle,
|
|
119
|
+
]}
|
|
120
|
+
/>
|
|
121
|
+
</View>
|
|
122
|
+
) : (
|
|
123
|
+
<View
|
|
124
|
+
style={[
|
|
125
|
+
styles.dotsContainer,
|
|
126
|
+
props.mirror && styles.mirror,
|
|
127
|
+
props.dotsContainerStyle,
|
|
128
|
+
]}
|
|
129
|
+
>
|
|
130
|
+
{dots.map((_, i) => {
|
|
131
|
+
const inputRange = [(i - 1) * width, i * width, (i + 1) * width];
|
|
132
|
+
const dotOpacity = props.animatedValue.interpolate({
|
|
133
|
+
inputRange,
|
|
134
|
+
outputRange: [0.3, 1, 0.3],
|
|
135
|
+
extrapolate: 'clamp',
|
|
136
|
+
});
|
|
137
|
+
const dotProps = {
|
|
138
|
+
accessibilityRole: props.dotsAreTappable
|
|
139
|
+
? ('button' as const)
|
|
140
|
+
: ('image' as const),
|
|
141
|
+
accessibilityLabel: `Page ${i + 1} of ${props.numberOfScreens}`,
|
|
142
|
+
accessibilityState: { selected: i === currentPage },
|
|
143
|
+
};
|
|
144
|
+
const dot = (
|
|
145
|
+
<Animated.View
|
|
146
|
+
key={i}
|
|
147
|
+
style={[
|
|
148
|
+
styles.dot,
|
|
149
|
+
{
|
|
150
|
+
backgroundColor: props.color,
|
|
151
|
+
opacity: dotOpacity,
|
|
152
|
+
},
|
|
153
|
+
]}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
return props.dotsAreTappable ? (
|
|
157
|
+
<Pressable
|
|
158
|
+
key={i}
|
|
159
|
+
onPress={() => scrollTo(i)}
|
|
160
|
+
hitSlop={8}
|
|
161
|
+
{...dotProps}
|
|
162
|
+
>
|
|
163
|
+
{dot}
|
|
164
|
+
</Pressable>
|
|
165
|
+
) : (
|
|
166
|
+
<View key={i} {...dotProps}>
|
|
167
|
+
{dot}
|
|
168
|
+
</View>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</View>
|
|
172
|
+
)}
|
|
173
|
+
|
|
95
174
|
<View
|
|
96
175
|
style={[
|
|
97
176
|
styles.buttons,
|
|
@@ -138,21 +217,34 @@ const styles = StyleSheet.create({
|
|
|
138
217
|
},
|
|
139
218
|
dotsContainer: {
|
|
140
219
|
flexDirection: 'row',
|
|
141
|
-
flex:
|
|
220
|
+
flex: 2,
|
|
142
221
|
justifyContent: 'center',
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
},
|
|
224
|
+
progressTrack: {
|
|
225
|
+
flex: 2,
|
|
226
|
+
height: 6,
|
|
227
|
+
borderRadius: 3,
|
|
228
|
+
marginHorizontal: 16,
|
|
229
|
+
overflow: 'hidden',
|
|
230
|
+
},
|
|
231
|
+
progressFill: {
|
|
232
|
+
height: '100%',
|
|
233
|
+
borderRadius: 3,
|
|
143
234
|
},
|
|
144
235
|
text: {
|
|
145
236
|
fontSize: 16,
|
|
146
237
|
},
|
|
147
238
|
buttons: {
|
|
148
|
-
|
|
239
|
+
flex: 1,
|
|
149
240
|
},
|
|
150
241
|
rightButton: {
|
|
151
242
|
alignItems: 'flex-end',
|
|
152
|
-
paddingRight: 30,
|
|
153
243
|
},
|
|
154
244
|
leftButton: {
|
|
155
245
|
alignItems: 'flex-start',
|
|
156
|
-
|
|
246
|
+
},
|
|
247
|
+
mirror: {
|
|
248
|
+
transform: [{ scaleX: -1 }],
|
|
157
249
|
},
|
|
158
250
|
});
|