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.
Files changed (80) hide show
  1. package/README.md +89 -7
  2. package/lib/commonjs/components/CustomPages.js +31 -55
  3. package/lib/commonjs/components/CustomPages.js.map +1 -1
  4. package/lib/commonjs/components/OnboardingPages.js +63 -79
  5. package/lib/commonjs/components/OnboardingPages.js.map +1 -1
  6. package/lib/commonjs/components/Page.js +8 -3
  7. package/lib/commonjs/components/Page.js.map +1 -1
  8. package/lib/commonjs/components/Pagination.js +75 -13
  9. package/lib/commonjs/components/Pagination.js.map +1 -1
  10. package/lib/commonjs/components/Swiper.js +58 -85
  11. package/lib/commonjs/components/Swiper.js.map +1 -1
  12. package/lib/commonjs/components/button.js +3 -1
  13. package/lib/commonjs/components/button.js.map +1 -1
  14. package/lib/commonjs/context/OnboardingContext.js +101 -21
  15. package/lib/commonjs/context/OnboardingContext.js.map +1 -1
  16. package/lib/commonjs/hooks/useOnboarding.js +1 -1
  17. package/lib/commonjs/hooks/useOnboarding.js.map +1 -1
  18. package/lib/commonjs/index.js +33 -2
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/utils/color.js +308 -0
  21. package/lib/commonjs/utils/color.js.map +1 -0
  22. package/lib/commonjs/utils/persistence.js +51 -0
  23. package/lib/commonjs/utils/persistence.js.map +1 -0
  24. package/lib/module/components/CustomPages.js +31 -55
  25. package/lib/module/components/CustomPages.js.map +1 -1
  26. package/lib/module/components/OnboardingPages.js +64 -79
  27. package/lib/module/components/OnboardingPages.js.map +1 -1
  28. package/lib/module/components/Page.js +8 -3
  29. package/lib/module/components/Page.js.map +1 -1
  30. package/lib/module/components/Pagination.js +76 -14
  31. package/lib/module/components/Pagination.js.map +1 -1
  32. package/lib/module/components/Swiper.js +59 -86
  33. package/lib/module/components/Swiper.js.map +1 -1
  34. package/lib/module/components/button.js +3 -1
  35. package/lib/module/components/button.js.map +1 -1
  36. package/lib/module/context/OnboardingContext.js +102 -22
  37. package/lib/module/context/OnboardingContext.js.map +1 -1
  38. package/lib/module/hooks/useOnboarding.js +1 -1
  39. package/lib/module/hooks/useOnboarding.js.map +1 -1
  40. package/lib/module/index.js +8 -1
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/utils/color.js +299 -0
  43. package/lib/module/utils/color.js.map +1 -0
  44. package/lib/module/utils/persistence.js +42 -0
  45. package/lib/module/utils/persistence.js.map +1 -0
  46. package/lib/typescript/src/components/CustomPages.d.ts +6 -2
  47. package/lib/typescript/src/components/CustomPages.d.ts.map +1 -1
  48. package/lib/typescript/src/components/OnboardingPages.d.ts +6 -2
  49. package/lib/typescript/src/components/OnboardingPages.d.ts.map +1 -1
  50. package/lib/typescript/src/components/Page.d.ts +2 -0
  51. package/lib/typescript/src/components/Page.d.ts.map +1 -1
  52. package/lib/typescript/src/components/Pagination.d.ts +9 -0
  53. package/lib/typescript/src/components/Pagination.d.ts.map +1 -1
  54. package/lib/typescript/src/components/Swiper.d.ts.map +1 -1
  55. package/lib/typescript/src/components/button.d.ts.map +1 -1
  56. package/lib/typescript/src/context/OnboardingContext.d.ts +9 -0
  57. package/lib/typescript/src/context/OnboardingContext.d.ts.map +1 -1
  58. package/lib/typescript/src/hooks/useOnboarding.d.ts +3 -0
  59. package/lib/typescript/src/hooks/useOnboarding.d.ts.map +1 -1
  60. package/lib/typescript/src/index.d.ts +4 -0
  61. package/lib/typescript/src/index.d.ts.map +1 -1
  62. package/lib/typescript/src/types/index.d.ts +13 -0
  63. package/lib/typescript/src/types/index.d.ts.map +1 -1
  64. package/lib/typescript/src/utils/color.d.ts +18 -0
  65. package/lib/typescript/src/utils/color.d.ts.map +1 -0
  66. package/lib/typescript/src/utils/persistence.d.ts +31 -0
  67. package/lib/typescript/src/utils/persistence.d.ts.map +1 -0
  68. package/package.json +12 -6
  69. package/src/components/CustomPages.tsx +62 -69
  70. package/src/components/OnboardingPages.tsx +86 -89
  71. package/src/components/Page.tsx +8 -2
  72. package/src/components/Pagination.tsx +117 -29
  73. package/src/components/Swiper.tsx +65 -87
  74. package/src/components/button.tsx +6 -1
  75. package/src/context/OnboardingContext.tsx +145 -26
  76. package/src/hooks/useOnboarding.tsx +1 -3
  77. package/src/index.tsx +16 -1
  78. package/src/types/index.ts +13 -0
  79. package/src/utils/color.ts +284 -0
  80. 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.9",
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/tinycolor2": "^1.4.6",
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
- "dependencies": {
169
- "tinycolor2": "^1.6.0"
170
- }
174
+ "workspaces": [
175
+ "example"
176
+ ]
171
177
  }
@@ -1,4 +1,12 @@
1
- import { View, Dimensions, Animated, StyleSheet, FlatList } from 'react-native';
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
- flatListRef: React.RefObject<FlatList>;
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.flatListRef}
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={Animated.event(
86
- [{ nativeEvent: { contentOffset: { x: props.scrollX } } }],
87
- { useNativeDriver: false }
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 / width
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, { useRef } from 'react';
2
- import tinycolor from 'tinycolor2';
3
- import { Animated, StyleSheet, Dimensions, FlatList } from 'react-native';
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
- flatListRef: React.RefObject<FlatList>;
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 backgroundColorAnim = useRef(new Animated.Value(0)).current;
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 = tinycolor(currentBackgroundColor).getBrightness() > 180;
38
+ const currentBackgroundColor = currentPage_?.backgroundColor ?? 'white';
39
+ const isLight = getBrightness(currentBackgroundColor) > 180;
30
40
  const footerBackgroundColor = isLight
31
- ? tinycolor(currentBackgroundColor).darken(30).toString()
32
- : tinycolor(currentBackgroundColor).lighten(30).toString();
41
+ ? darken(currentBackgroundColor, 30)
42
+ : lighten(currentBackgroundColor, 30);
33
43
  const color =
34
- tinycolor(footerBackgroundColor).getBrightness() > 180
35
- ? tinycolor(footerBackgroundColor).darken(60).toString()
36
- : tinycolor(footerBackgroundColor).lighten(60).toString();
44
+ getBrightness(footerBackgroundColor) > 180
45
+ ? darken(footerBackgroundColor, 60)
46
+ : lighten(footerBackgroundColor, 60);
37
47
 
38
- const previousBackgroundColor =
39
- props.pages[previousPage]?.backgroundColor || 'white';
40
- // Interpolating background color based on backgroundColorAnim value
41
- const interpolatedBackgroundColor = backgroundColorAnim.interpolate({
42
- inputRange: [0, 1],
43
- outputRange: [currentBackgroundColor, previousBackgroundColor],
44
- extrapolate: 'clamp',
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 setPage_ = (newPageIndex: number) => {
48
- setPreviousPage(props.currentPage);
49
- props.setPage(newPageIndex);
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.flatListRef}
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={props.width || 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={Animated.event(
123
- [{ nativeEvent: { contentOffset: { x: props.scrollX } } }],
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 / (props.width || width)
149
+ event.nativeEvent.contentOffset.x / pageWidth
130
150
  );
131
- setPage_(pageIndex || 0);
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
  });
@@ -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 potrait = height > width;
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: potrait ? 60 : 10,
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
  });