react-native-app-onboard 0.1.9 → 0.2.1
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 -55
- package/lib/commonjs/components/CustomPages.js.map +1 -1
- package/lib/commonjs/components/OnboardingPages.js +63 -79
- 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/color.js +308 -0
- package/lib/commonjs/utils/color.js.map +1 -0
- package/lib/commonjs/utils/persistence.js +51 -0
- package/lib/commonjs/utils/persistence.js.map +1 -0
- package/lib/module/components/CustomPages.js +31 -55
- package/lib/module/components/CustomPages.js.map +1 -1
- package/lib/module/components/OnboardingPages.js +64 -79
- 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/color.js +299 -0
- package/lib/module/utils/color.js.map +1 -0
- 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/color.d.ts +18 -0
- package/lib/typescript/src/utils/color.d.ts.map +1 -0
- package/lib/typescript/src/utils/persistence.d.ts +31 -0
- package/lib/typescript/src/utils/persistence.d.ts.map +1 -0
- package/package.json +12 -6
- package/src/components/CustomPages.tsx +62 -69
- package/src/components/OnboardingPages.tsx +86 -89
- package/src/components/Page.tsx +8 -2
- package/src/components/Pagination.tsx +117 -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/color.ts +284 -0
- package/src/utils/persistence.ts +58 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal color helpers — a tiny, dependency-free replacement for the subset of
|
|
3
|
+
* tinycolor2 this library used: perceived brightness, HSL-based lighten/darken,
|
|
4
|
+
* and producing an `rgba()` string with a custom alpha.
|
|
5
|
+
*
|
|
6
|
+
* Accepts hex (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), `rgb()`/`rgba()`, and
|
|
7
|
+
* CSS named colors. Invalid input resolves to black, matching tinycolor's
|
|
8
|
+
* behavior of treating unparseable colors as having zero brightness.
|
|
9
|
+
*/
|
|
10
|
+
/** Perceived brightness on a 0–255 scale (W3C formula, as tinycolor uses). */
|
|
11
|
+
export declare function getBrightness(input: string): number;
|
|
12
|
+
/** Lighten by `amount` percent (HSL lightness), returning a hex string. */
|
|
13
|
+
export declare function lighten(input: string, amount?: number): string;
|
|
14
|
+
/** Darken by `amount` percent (HSL lightness), returning a hex string. */
|
|
15
|
+
export declare function darken(input: string, amount?: number): string;
|
|
16
|
+
/** Return an `rgba()` string for `input` with the given alpha. */
|
|
17
|
+
export declare function setAlpha(input: string, alpha: number): string;
|
|
18
|
+
//# sourceMappingURL=color.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../../src/utils/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyPH,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAQD,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAE1D;AAED,0EAA0E;AAC1E,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAEzD;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG7D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal AsyncStorage-compatible interface. Any storage that implements these
|
|
3
|
+
* three methods works (e.g. @react-native-async-storage/async-storage,
|
|
4
|
+
* expo-secure-store wrappers, or an in-memory mock in tests). Keeping the
|
|
5
|
+
* dependency injected means this library does not have to ship a storage peer
|
|
6
|
+
* dependency of its own.
|
|
7
|
+
*/
|
|
8
|
+
export type OnboardingStorageAdapter = {
|
|
9
|
+
getItem: (key: string) => Promise<string | null>;
|
|
10
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
11
|
+
removeItem: (key: string) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Returns whether the user has previously completed onboarding stored under
|
|
15
|
+
* `key`. Defaults to a namespaced key so callers usually only pass the storage.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasCompletedOnboarding(storage: OnboardingStorageAdapter, key?: string): Promise<boolean>;
|
|
18
|
+
/** Marks onboarding as completed so it can be skipped on subsequent launches. */
|
|
19
|
+
export declare function markOnboardingComplete(storage: OnboardingStorageAdapter, key?: string): Promise<void>;
|
|
20
|
+
/** Clears the stored completion flag (useful for "replay onboarding" actions). */
|
|
21
|
+
export declare function resetOnboarding(storage: OnboardingStorageAdapter, key?: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Convenience factory that binds a storage adapter (and optional key) once and
|
|
24
|
+
* returns ready-to-call helpers, so app code doesn't repeat the storage arg.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createOnboardingStorage(storage: OnboardingStorageAdapter, key?: string): {
|
|
27
|
+
hasCompleted: () => Promise<boolean>;
|
|
28
|
+
markComplete: () => Promise<void>;
|
|
29
|
+
reset: () => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../../src/utils/persistence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAKF;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,wBAAwB,EACjC,GAAG,GAAE,MAAoB,GACxB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,iFAAiF;AACjF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,wBAAwB,EACjC,GAAG,GAAE,MAAoB,GACxB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,kFAAkF;AAClF,wBAAsB,eAAe,CACnC,OAAO,EAAE,wBAAwB,EACjC,GAAG,GAAE,MAAoB,GACxB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,EACjC,GAAG,GAAE,MAAoB;;;;EAO1B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-app-onboard",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "React Native App Onboard is a customizable, easy-to-use, and efficient library for creating compelling onboarding experiences for your React Native applications. It provides smooth, fluid transitions and animations, with a focus on simplicity and usability.",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"!**/.*"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"example": "yarn workspace react-native-app-onboard-example",
|
|
29
|
+
"example": "yarn workspace react-native-app-onboard-example start",
|
|
30
30
|
"test": "jest",
|
|
31
31
|
"typecheck": "tsc --noEmit",
|
|
32
32
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
@@ -66,11 +66,13 @@
|
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@commitlint/config-conventional": "^17.0.2",
|
|
68
68
|
"@evilmartians/lefthook": "^1.5.0",
|
|
69
|
+
"@react-native/babel-preset": "0.74.84",
|
|
69
70
|
"@react-native/eslint-config": "^0.73.1",
|
|
70
71
|
"@release-it/conventional-changelog": "^5.0.0",
|
|
71
72
|
"@types/jest": "^29.5.5",
|
|
72
73
|
"@types/react": "^18.2.44",
|
|
73
|
-
"@types/
|
|
74
|
+
"@types/react-test-renderer": "^18.0.0",
|
|
75
|
+
"@typescript/native-preview": "^7.0.0-dev.20260606.1",
|
|
74
76
|
"commitlint": "^17.0.2",
|
|
75
77
|
"del-cli": "^5.1.0",
|
|
76
78
|
"eslint": "^8.51.0",
|
|
@@ -81,6 +83,7 @@
|
|
|
81
83
|
"react": "18.2.0",
|
|
82
84
|
"react-native": "0.74.2",
|
|
83
85
|
"react-native-builder-bob": "^0.20.0",
|
|
86
|
+
"react-test-renderer": "18.2.0",
|
|
84
87
|
"release-it": "^15.0.0",
|
|
85
88
|
"typescript": "^5.2.2"
|
|
86
89
|
},
|
|
@@ -94,6 +97,9 @@
|
|
|
94
97
|
"packageManager": "yarn@3.6.1",
|
|
95
98
|
"jest": {
|
|
96
99
|
"preset": "react-native",
|
|
100
|
+
"moduleNameMapper": {
|
|
101
|
+
"^react-native-app-onboard$": "<rootDir>/src/index"
|
|
102
|
+
},
|
|
97
103
|
"modulePathIgnorePatterns": [
|
|
98
104
|
"<rootDir>/example/node_modules",
|
|
99
105
|
"<rootDir>/lib/"
|
|
@@ -165,7 +171,7 @@
|
|
|
165
171
|
]
|
|
166
172
|
]
|
|
167
173
|
},
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
|
|
174
|
+
"workspaces": [
|
|
175
|
+
"example"
|
|
176
|
+
]
|
|
171
177
|
}
|
|
@@ -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 && (
|
|
@@ -43,53 +96,22 @@ export const CustomPages = ({
|
|
|
43
96
|
<>
|
|
44
97
|
{props.customFooter &&
|
|
45
98
|
props.customFooter({ nextPage: props.nextPage })}
|
|
46
|
-
{!props.customFooter &&
|
|
47
|
-
<Pagination
|
|
48
|
-
color={'#fff'}
|
|
49
|
-
backgroundColor={'#333'}
|
|
50
|
-
width={width}
|
|
51
|
-
onNext={props.nextPage}
|
|
52
|
-
onSkip={props.onSkip}
|
|
53
|
-
onDone={props.onDone}
|
|
54
|
-
showDone={props.showDone}
|
|
55
|
-
animatedValue={props.scrollX}
|
|
56
|
-
showSkip={props.showSkip}
|
|
57
|
-
numberOfScreens={props.numberOfScreens}
|
|
58
|
-
skipLabel={props.skipLabel}
|
|
59
|
-
showNext={showNext}
|
|
60
|
-
nextLabel={props.nextLabel}
|
|
61
|
-
doneLabel={props.doneLabel}
|
|
62
|
-
hasSkipPosition={!!props.skipButtonPosition}
|
|
63
|
-
paginationContainerStyle={props.paginationContainerStyle}
|
|
64
|
-
buttonRightContainerStyle={props.buttonRightContainerStyle}
|
|
65
|
-
buttonLeftContainerStyle={props.buttonLeftContainerStyle}
|
|
66
|
-
dotsContainerStyle={props.dotsContainerStyle}
|
|
67
|
-
doneLabelStyle={props.doneLabelStyle}
|
|
68
|
-
skipButtonContainerStyle={props.skipButtonContainerStyle}
|
|
69
|
-
nextButtonContainerStyle={props.nextButtonContainerStyle}
|
|
70
|
-
doneButtonContainerStyle={props.doneButtonContainerStyle}
|
|
71
|
-
skipLabelStyle={props.skipLabelStyle}
|
|
72
|
-
nextLabelStyle={props.nextLabelStyle}
|
|
73
|
-
paginationPosition={props.paginationPosition}
|
|
74
|
-
/>
|
|
75
|
-
)}
|
|
99
|
+
{!props.customFooter && <Pagination {...paginationProps} />}
|
|
76
100
|
</>
|
|
77
101
|
)}
|
|
78
102
|
<Animated.FlatList
|
|
79
|
-
ref={props.
|
|
103
|
+
ref={(node) => props.setFlatListRef(node as FlatList | null)}
|
|
80
104
|
data={React.Children.toArray(props.children)}
|
|
81
105
|
horizontal
|
|
82
106
|
pagingEnabled
|
|
83
107
|
showsHorizontalScrollIndicator={false}
|
|
84
108
|
scrollEnabled={props.scrollEnabled}
|
|
85
|
-
onScroll={
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)}
|
|
89
|
-
scrollEventThrottle={1}
|
|
109
|
+
onScroll={props.onScroll}
|
|
110
|
+
onScrollBeginDrag={props.onScrollBeginDrag}
|
|
111
|
+
scrollEventThrottle={16}
|
|
90
112
|
onMomentumScrollEnd={(event) => {
|
|
91
113
|
const pageIndex = Math.round(
|
|
92
|
-
event.nativeEvent.contentOffset.x /
|
|
114
|
+
event.nativeEvent.contentOffset.x / pageWidth
|
|
93
115
|
);
|
|
94
116
|
props.setPage(pageIndex || 0);
|
|
95
117
|
}}
|
|
@@ -111,36 +133,7 @@ export const CustomPages = ({
|
|
|
111
133
|
<>
|
|
112
134
|
{props.customFooter &&
|
|
113
135
|
props.customFooter({ nextPage: props.nextPage })}
|
|
114
|
-
{!props.customFooter &&
|
|
115
|
-
<Pagination
|
|
116
|
-
color={'#fff'}
|
|
117
|
-
backgroundColor={'#333'}
|
|
118
|
-
width={width}
|
|
119
|
-
onNext={props.nextPage}
|
|
120
|
-
onSkip={props.onSkip}
|
|
121
|
-
onDone={props.onDone}
|
|
122
|
-
showDone={props.showDone}
|
|
123
|
-
animatedValue={props.scrollX}
|
|
124
|
-
showSkip={props.showSkip}
|
|
125
|
-
numberOfScreens={props.numberOfScreens}
|
|
126
|
-
skipLabel={props.skipLabel}
|
|
127
|
-
nextLabel={props.nextLabel}
|
|
128
|
-
showNext={showNext}
|
|
129
|
-
doneLabel={props.doneLabel}
|
|
130
|
-
paginationContainerStyle={props.paginationContainerStyle}
|
|
131
|
-
buttonRightContainerStyle={props.buttonRightContainerStyle}
|
|
132
|
-
buttonLeftContainerStyle={props.buttonLeftContainerStyle}
|
|
133
|
-
dotsContainerStyle={props.dotsContainerStyle}
|
|
134
|
-
doneLabelStyle={props.doneLabelStyle}
|
|
135
|
-
skipButtonContainerStyle={props.skipButtonContainerStyle}
|
|
136
|
-
nextButtonContainerStyle={props.nextButtonContainerStyle}
|
|
137
|
-
doneButtonContainerStyle={props.doneButtonContainerStyle}
|
|
138
|
-
skipLabelStyle={props.skipLabelStyle}
|
|
139
|
-
hasSkipPosition={!!props.skipButtonPosition}
|
|
140
|
-
paginationPosition={props.paginationPosition}
|
|
141
|
-
nextLabelStyle={props.nextLabelStyle}
|
|
142
|
-
/>
|
|
143
|
-
)}
|
|
136
|
+
{!props.customFooter && <Pagination {...paginationProps} />}
|
|
144
137
|
</>
|
|
145
138
|
)}
|
|
146
139
|
</View>
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { getBrightness, lighten, darken } from '../utils/color';
|
|
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,40 +33,74 @@ 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
|
|
29
|
-
const isLight =
|
|
38
|
+
const currentBackgroundColor = currentPage_?.backgroundColor ?? 'white';
|
|
39
|
+
const isLight = getBrightness(currentBackgroundColor) > 180;
|
|
30
40
|
const footerBackgroundColor = isLight
|
|
31
|
-
?
|
|
32
|
-
:
|
|
41
|
+
? darken(currentBackgroundColor, 30)
|
|
42
|
+
: lighten(currentBackgroundColor, 30);
|
|
33
43
|
const color =
|
|
34
|
-
|
|
35
|
-
?
|
|
36
|
-
:
|
|
44
|
+
getBrightness(footerBackgroundColor) > 180
|
|
45
|
+
? darken(footerBackgroundColor, 60)
|
|
46
|
+
: lighten(footerBackgroundColor, 60);
|
|
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
|
});
|