react-native-onboarding-steps 1.0.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/lib/commonjs/Button.js +62 -0
  4. package/lib/commonjs/Button.js.map +1 -0
  5. package/lib/commonjs/Onboarding.js +137 -0
  6. package/lib/commonjs/Onboarding.js.map +1 -0
  7. package/lib/commonjs/Step.js +45 -0
  8. package/lib/commonjs/Step.js.map +1 -0
  9. package/lib/commonjs/Steps.js +66 -0
  10. package/lib/commonjs/Steps.js.map +1 -0
  11. package/lib/commonjs/index.js +54 -0
  12. package/lib/commonjs/index.js.map +1 -0
  13. package/lib/commonjs/package.json +1 -0
  14. package/lib/commonjs/theme.js +42 -0
  15. package/lib/commonjs/theme.js.map +1 -0
  16. package/lib/commonjs/types.js +6 -0
  17. package/lib/commonjs/types.js.map +1 -0
  18. package/lib/module/Button.js +58 -0
  19. package/lib/module/Button.js.map +1 -0
  20. package/lib/module/Onboarding.js +131 -0
  21. package/lib/module/Onboarding.js.map +1 -0
  22. package/lib/module/Step.js +40 -0
  23. package/lib/module/Step.js.map +1 -0
  24. package/lib/module/Steps.js +60 -0
  25. package/lib/module/Steps.js.map +1 -0
  26. package/lib/module/index.js +8 -0
  27. package/lib/module/index.js.map +1 -0
  28. package/lib/module/package.json +1 -0
  29. package/lib/module/theme.js +36 -0
  30. package/lib/module/theme.js.map +1 -0
  31. package/lib/module/types.js +4 -0
  32. package/lib/module/types.js.map +1 -0
  33. package/lib/typescript/commonjs/package.json +1 -0
  34. package/lib/typescript/commonjs/src/Button.d.ts +15 -0
  35. package/lib/typescript/commonjs/src/Button.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/src/Onboarding.d.ts +4 -0
  37. package/lib/typescript/commonjs/src/Onboarding.d.ts.map +1 -0
  38. package/lib/typescript/commonjs/src/Step.d.ts +11 -0
  39. package/lib/typescript/commonjs/src/Step.d.ts.map +1 -0
  40. package/lib/typescript/commonjs/src/Steps.d.ts +9 -0
  41. package/lib/typescript/commonjs/src/Steps.d.ts.map +1 -0
  42. package/lib/typescript/commonjs/src/index.d.ts +7 -0
  43. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  44. package/lib/typescript/commonjs/src/theme.d.ts +14 -0
  45. package/lib/typescript/commonjs/src/theme.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/src/types.d.ts +90 -0
  47. package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
  48. package/lib/typescript/module/package.json +1 -0
  49. package/lib/typescript/module/src/Button.d.ts +15 -0
  50. package/lib/typescript/module/src/Button.d.ts.map +1 -0
  51. package/lib/typescript/module/src/Onboarding.d.ts +4 -0
  52. package/lib/typescript/module/src/Onboarding.d.ts.map +1 -0
  53. package/lib/typescript/module/src/Step.d.ts +11 -0
  54. package/lib/typescript/module/src/Step.d.ts.map +1 -0
  55. package/lib/typescript/module/src/Steps.d.ts +9 -0
  56. package/lib/typescript/module/src/Steps.d.ts.map +1 -0
  57. package/lib/typescript/module/src/index.d.ts +7 -0
  58. package/lib/typescript/module/src/index.d.ts.map +1 -0
  59. package/lib/typescript/module/src/theme.d.ts +14 -0
  60. package/lib/typescript/module/src/theme.d.ts.map +1 -0
  61. package/lib/typescript/module/src/types.d.ts +90 -0
  62. package/lib/typescript/module/src/types.d.ts.map +1 -0
  63. package/package.json +100 -0
  64. package/src/Button.tsx +72 -0
  65. package/src/Onboarding.tsx +147 -0
  66. package/src/Step.tsx +38 -0
  67. package/src/Steps.tsx +62 -0
  68. package/src/index.ts +15 -0
  69. package/src/theme.ts +34 -0
  70. package/src/types.ts +99 -0
@@ -0,0 +1,15 @@
1
+ import { type StyleProp, type ViewStyle } from 'react-native';
2
+ type ButtonVariant = 'primary' | 'secondary';
3
+ type ButtonProps = {
4
+ label?: string;
5
+ onPress: () => void;
6
+ variant?: ButtonVariant;
7
+ /** Overrides the variant's background. */
8
+ backgroundColor?: string;
9
+ /** Overrides the variant's label colour. */
10
+ labelColor?: string;
11
+ style?: StyleProp<ViewStyle>;
12
+ };
13
+ declare const Button: ({ label, onPress, variant, backgroundColor, labelColor, style, }: ButtonProps) => import("react").JSX.Element;
14
+ export default Button;
15
+ //# sourceMappingURL=Button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../../src/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3F,KAAK,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC;AAE7C,KAAK,WAAW,GAAG;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,QAAA,MAAM,MAAM,GAAI,kEAOb,WAAW,gCAiBb,CAAC;AAgCF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { OnboardingProps } from './types.js';
2
+ declare const Onboarding: ({ steps, initialStep, onComplete, onStepChange, onBack, stepsTheme, backButtonColor, backButtonLabelColor, backButton, continueButtonColor, continueButtonLabelColor, continueButton, style, contentStyle, }: OnboardingProps) => import("react").JSX.Element | null;
3
+ export default Onboarding;
4
+ //# sourceMappingURL=Onboarding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Onboarding.d.ts","sourceRoot":"","sources":["../../../../src/Onboarding.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAyB,eAAe,EAAE,MAAM,YAAS,CAAC;AAQtE,QAAA,MAAM,UAAU,GAAI,8MAejB,eAAe,uCA8FjB,CAAC;AAuBF,eAAe,UAAU,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type SharedValue } from 'react-native-reanimated';
2
+ import type { StepsTheme } from './types.js';
3
+ type StepProps = {
4
+ index: number;
5
+ /** Animated position of the track's leading edge, in step units. */
6
+ progress: SharedValue<number>;
7
+ theme: StepsTheme;
8
+ };
9
+ declare const Step: ({ index, progress, theme }: StepProps) => import("react").JSX.Element;
10
+ export default Step;
11
+ //# sourceMappingURL=Step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Step.d.ts","sourceRoot":"","sources":["../../../../src/Step.tsx"],"names":[],"mappings":"AACA,OAAiB,EAGf,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAS,CAAC;AAE1C,KAAK,SAAS,GAAG;IACf,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,KAAK,EAAE,UAAU,CAAC;CACnB,CAAC;AAEF,QAAA,MAAM,IAAI,GAAI,4BAA4B,SAAS,gCAoBlD,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { OnboardingStep, StepsTheme } from './types.js';
2
+ type StepsProps = {
3
+ steps: OnboardingStep[];
4
+ currentStep: number;
5
+ theme: StepsTheme;
6
+ };
7
+ declare const Steps: ({ steps, currentStep, theme }: StepsProps) => import("react").JSX.Element;
8
+ export default Steps;
9
+ //# sourceMappingURL=Steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Steps.d.ts","sourceRoot":"","sources":["../../../../src/Steps.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAS,CAAC;AAE1D,KAAK,UAAU,GAAG;IAChB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACnB,CAAC;AAEF,QAAA,MAAM,KAAK,GAAI,+BAA+B,UAAU,gCA0CvD,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { default as Onboarding } from './Onboarding.js';
2
+ export { default as Steps } from './Steps.js';
3
+ export { default as Step } from './Step.js';
4
+ export { default as Button } from './Button.js';
5
+ export { defaultStepsTheme, resolveStepsTheme, trackWidth } from './theme.js';
6
+ export type { OnboardingProps, OnboardingStep, OnboardingButtonProps, OnboardingButtonRenderer, StepsTheme, StepsThemeInput, } from './types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAc,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAU,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAS,CAAC;AAE3E,YAAY,EACV,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,wBAAwB,EACxB,UAAU,EACV,eAAe,GAChB,MAAM,YAAS,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { StepsTheme, StepsThemeInput } from './types.js';
2
+ /** The look the indicator falls back to when a field is not overridden. */
3
+ export declare const defaultStepsTheme: StepsTheme;
4
+ /** Fills a partial theme with the defaults, leaving a fully resolved object. */
5
+ export declare const resolveStepsTheme: (input?: StepsThemeInput) => StepsTheme;
6
+ /**
7
+ * Width of the track once it has swept `progress` steps past the first one.
8
+ *
9
+ * At `progress === 0` this is exactly one slot, so the track reads as a circle
10
+ * behind the first dot; at `progress === 1` it spans two slots plus the gap
11
+ * between them, visually connecting the two steps.
12
+ */
13
+ export declare const trackWidth: (progress: number, stepSize: number, stepGap: number) => number;
14
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAS,CAAC;AAE3D,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,EAAE,UAY/B,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,eAAe,KAAG,UAG1D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,EAAE,UAAU,MAAM,EAAE,SAAS,MAAM,WAG7E,CAAC"}
@@ -0,0 +1,90 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import type { WithSpringConfig } from 'react-native-reanimated';
4
+ /**
5
+ * A single step of the onboarding flow.
6
+ *
7
+ * Everything except `id` is optional so a flow can be as thin as
8
+ * `[{ id: 0 }, { id: 1 }]` and still render.
9
+ */
10
+ export type OnboardingStep = {
11
+ /** Stable identity for this step. Used as the React key. */
12
+ id: string | number;
13
+ /** What fills the area above the step indicator while this step is active. */
14
+ content?: ReactNode;
15
+ /** Overrides the default "Back" label while this step is active. */
16
+ backButtonLabel?: string;
17
+ /** Overrides the default "Continue" label while this step is active. */
18
+ continueButtonLabel?: string;
19
+ /** Overrides the default "Finish" label. Only used on the last step. */
20
+ finishButtonLabel?: string;
21
+ };
22
+ /**
23
+ * Fully resolved look of the animated step indicator. Every field is required
24
+ * here; consumers supply a `StepsThemeInput` and the gaps are filled from
25
+ * `defaultStepsTheme`.
26
+ */
27
+ export type StepsTheme = {
28
+ /** Diameter of a step's slot — also the height of the track. */
29
+ stepSize: number;
30
+ /** Diameter of the dot drawn inside each slot. */
31
+ dotSize: number;
32
+ /** Horizontal space between two step slots. */
33
+ stepGap: number;
34
+ /** Colour of the pill that connects completed steps. */
35
+ trackColor: string;
36
+ /** Dot colour once the track has swept over it. */
37
+ activeDotColor: string;
38
+ /** Dot colour before the track reaches it. */
39
+ inactiveDotColor: string;
40
+ /** Spring driving the track's sweep and the dot colour crossfade. */
41
+ spring: WithSpringConfig;
42
+ };
43
+ /** Partial theme accepted from consumers; merged over `defaultStepsTheme`. */
44
+ export type StepsThemeInput = Partial<StepsTheme>;
45
+ /** Everything a custom button renderer needs to draw itself and act. */
46
+ export type OnboardingButtonProps = {
47
+ /** Label resolved from the step, falling back to the built-in default. */
48
+ label: string;
49
+ /** Advances or rewinds the flow. Call this from your own component. */
50
+ onPress: () => void;
51
+ /** Zero-based index of the active step. */
52
+ currentStep: number;
53
+ /** Total number of steps in the flow. */
54
+ totalSteps: number;
55
+ };
56
+ /** A function returning the React element to use in place of a stock button. */
57
+ export type OnboardingButtonRenderer = (props: OnboardingButtonProps) => ReactNode;
58
+ export type OnboardingProps = {
59
+ /** The flow. Rendering is skipped entirely when this is empty. */
60
+ steps: OnboardingStep[];
61
+ /** Step to open on. Clamped into range. Defaults to 0. */
62
+ initialStep?: number;
63
+ /** Called when the primary button is pressed on the last step. The flow
64
+ * stays on that step — navigating away is the consumer's call. */
65
+ onComplete?: () => void;
66
+ /** Called on every step change, forwards and backwards. */
67
+ onStepChange?: (index: number, step: OnboardingStep) => void;
68
+ /** Called when the back button moves the flow to a previous step.
69
+ * `onStepChange` fires as well. */
70
+ onBack?: (index: number, step: OnboardingStep) => void;
71
+ /** Partial override of the step indicator's look. */
72
+ stepsTheme?: StepsThemeInput;
73
+ /** Background of the stock back button. */
74
+ backButtonColor?: string;
75
+ /** Label colour of the stock back button. */
76
+ backButtonLabelColor?: string;
77
+ /** Replaces the stock back button. Still fades in/out with the flow. */
78
+ backButton?: OnboardingButtonRenderer;
79
+ /** Background of the stock continue/finish button. */
80
+ continueButtonColor?: string;
81
+ /** Label colour of the stock continue/finish button. */
82
+ continueButtonLabelColor?: string;
83
+ /** Replaces the stock continue/finish button. */
84
+ continueButton?: OnboardingButtonRenderer;
85
+ /** Style for the outermost container. */
86
+ style?: StyleProp<ViewStyle>;
87
+ /** Style for the region that hosts the active step's `content`. */
88
+ contentStyle?: StyleProp<ViewStyle>;
89
+ };
90
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,4DAA4D;IAC5D,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAElD,wEAAwE;AACxE,MAAM,MAAM,qBAAqB,GAAG;IAClC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,wBAAwB,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,SAAS,CAAC;AAEnF,MAAM,MAAM,eAAe,GAAG;IAC5B,kEAAkE;IAClE,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;uEACmE;IACnE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7D;wCACoC;IACpC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAEvD,qDAAqD;IACrD,UAAU,CAAC,EAAE,eAAe,CAAC;IAE7B,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,wEAAwE;IACxE,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,iDAAiD;IACjD,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAE1C,yCAAyC;IACzC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACrC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "react-native-onboarding-steps",
3
+ "version": "1.0.0",
4
+ "description": "An animated, fully customizable onboarding flow for React Native and Expo, with a step indicator that grows into a single connected track.",
5
+ "main": "./lib/commonjs/index.js",
6
+ "module": "./lib/module/index.js",
7
+ "types": "./lib/typescript/commonjs/src/index.d.ts",
8
+ "react-native": "./src/index.ts",
9
+ "source": "./src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "react-native": "./src/index.ts",
13
+ "import": {
14
+ "types": "./lib/typescript/module/src/index.d.ts",
15
+ "default": "./lib/module/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./lib/typescript/commonjs/src/index.d.ts",
19
+ "default": "./lib/commonjs/index.js"
20
+ }
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "src",
26
+ "lib",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "build": "bob build",
32
+ "clean": "rm -rf lib",
33
+ "prepare": "bob build",
34
+ "typecheck": "tsc --noEmit",
35
+ "example": "pnpm --filter react-native-onboarding-steps-example"
36
+ },
37
+ "keywords": [
38
+ "react-native",
39
+ "expo",
40
+ "onboarding",
41
+ "steps",
42
+ "stepper",
43
+ "walkthrough",
44
+ "intro",
45
+ "reanimated",
46
+ "animation",
47
+ "ios",
48
+ "android"
49
+ ],
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/ded3zito/react-native-onboarding-steps.git"
53
+ },
54
+ "bugs": {
55
+ "url": "https://github.com/ded3zito/react-native-onboarding-steps/issues"
56
+ },
57
+ "homepage": "https://github.com/ded3zito/react-native-onboarding-steps#readme",
58
+ "author": "Andrew Azevedo",
59
+ "license": "MIT",
60
+ "peerDependencies": {
61
+ "react": "*",
62
+ "react-native": "*",
63
+ "react-native-reanimated": ">=4.0.0",
64
+ "react-native-worklets": ">=0.10.0"
65
+ },
66
+ "devDependencies": {
67
+ "@babel/runtime": "^7.29.0",
68
+ "@types/react": "~19.2.2",
69
+ "react": "19.2.3",
70
+ "react-native": "0.86.3",
71
+ "react-native-builder-bob": "^0.43.0",
72
+ "react-native-reanimated": "^4.5.1",
73
+ "react-native-worklets": "^0.10.1",
74
+ "typescript": "~6.0.3"
75
+ },
76
+ "react-native-builder-bob": {
77
+ "source": "src",
78
+ "output": "lib",
79
+ "targets": [
80
+ [
81
+ "commonjs",
82
+ {
83
+ "esm": true
84
+ }
85
+ ],
86
+ [
87
+ "module",
88
+ {
89
+ "esm": true
90
+ }
91
+ ],
92
+ [
93
+ "typescript",
94
+ {
95
+ "project": "tsconfig.build.json"
96
+ }
97
+ ]
98
+ ]
99
+ }
100
+ }
package/src/Button.tsx ADDED
@@ -0,0 +1,72 @@
1
+ import { Text, Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
2
+
3
+ type ButtonVariant = 'primary' | 'secondary';
4
+
5
+ type ButtonProps = {
6
+ label?: string;
7
+ onPress: () => void;
8
+ variant?: ButtonVariant;
9
+ /** Overrides the variant's background. */
10
+ backgroundColor?: string;
11
+ /** Overrides the variant's label colour. */
12
+ labelColor?: string;
13
+ style?: StyleProp<ViewStyle>;
14
+ };
15
+
16
+ const Button = ({
17
+ label,
18
+ onPress,
19
+ variant = 'primary',
20
+ backgroundColor,
21
+ labelColor,
22
+ style,
23
+ }: ButtonProps) => {
24
+ return (
25
+ <Pressable
26
+ onPress={onPress}
27
+ style={({ pressed }) => [
28
+ styles.container,
29
+ styles[variant],
30
+ backgroundColor ? { backgroundColor } : null,
31
+ style,
32
+ pressed && styles.pressed,
33
+ ]}
34
+ >
35
+ <Text style={[styles.text, styles[`${variant}Text`], labelColor ? { color: labelColor } : null]}>
36
+ {label}
37
+ </Text>
38
+ </Pressable>
39
+ );
40
+ };
41
+
42
+ const styles = StyleSheet.create({
43
+ container: {
44
+ flexDirection: 'row',
45
+ justifyContent: 'center',
46
+ alignItems: 'center',
47
+ height: 45,
48
+ borderRadius: 100,
49
+ paddingHorizontal: 28,
50
+ },
51
+ pressed: {
52
+ opacity: 0.7,
53
+ },
54
+ primary: {
55
+ backgroundColor: '#4382DF',
56
+ },
57
+ secondary: {
58
+ backgroundColor: '#F1F1F1',
59
+ },
60
+ text: {
61
+ fontSize: 18,
62
+ fontWeight: '700',
63
+ },
64
+ primaryText: {
65
+ color: '#F7F4ED',
66
+ },
67
+ secondaryText: {
68
+ color: '#1C1C1E',
69
+ },
70
+ });
71
+
72
+ export default Button;
@@ -0,0 +1,147 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';
4
+ import Button from './Button';
5
+ import Steps from './Steps';
6
+ import { resolveStepsTheme } from './theme';
7
+ import type { OnboardingButtonProps, OnboardingProps } from './types';
8
+
9
+ const DEFAULT_BACK_LABEL = 'Back';
10
+ const DEFAULT_CONTINUE_LABEL = 'Continue';
11
+ const DEFAULT_FINISH_LABEL = 'Finish';
12
+
13
+ const clamp = (value: number, max: number) => Math.min(Math.max(value, 0), max);
14
+
15
+ const Onboarding = ({
16
+ steps,
17
+ initialStep = 0,
18
+ onComplete,
19
+ onStepChange,
20
+ onBack,
21
+ stepsTheme,
22
+ backButtonColor,
23
+ backButtonLabelColor,
24
+ backButton,
25
+ continueButtonColor,
26
+ continueButtonLabelColor,
27
+ continueButton,
28
+ style,
29
+ contentStyle,
30
+ }: OnboardingProps) => {
31
+ const lastIndex = Math.max(steps.length - 1, 0);
32
+ const [currentStep, setCurrentStep] = useState(() => clamp(initialStep, lastIndex));
33
+
34
+ const theme = useMemo(() => resolveStepsTheme(stepsTheme), [stepsTheme]);
35
+
36
+ // Guard after the hooks so the hook order stays stable for every render.
37
+ if (steps.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ const index = clamp(currentStep, lastIndex);
42
+ const step = steps[index];
43
+ const isFirstStep = index === 0;
44
+ const isLastStep = index === lastIndex;
45
+
46
+ const goToStep = (next: number) => {
47
+ setCurrentStep(next);
48
+ onStepChange?.(next, steps[next]);
49
+ };
50
+
51
+ const onPressContinue = () => {
52
+ // The flow deliberately stays on the last step; where to go next is the
53
+ // consumer's decision, made inside onComplete.
54
+ if (isLastStep) {
55
+ onComplete?.();
56
+ return;
57
+ }
58
+ goToStep(index + 1);
59
+ };
60
+
61
+ const onPressBack = () => {
62
+ if (isFirstStep) return;
63
+ const previous = index - 1;
64
+ onBack?.(previous, steps[previous]);
65
+ goToStep(previous);
66
+ };
67
+
68
+ const continueLabel = isLastStep
69
+ ? step.finishButtonLabel ?? DEFAULT_FINISH_LABEL
70
+ : step.continueButtonLabel ?? DEFAULT_CONTINUE_LABEL;
71
+
72
+ const sharedButtonProps = { currentStep: index, totalSteps: steps.length };
73
+
74
+ const backProps: OnboardingButtonProps = {
75
+ ...sharedButtonProps,
76
+ label: step.backButtonLabel ?? DEFAULT_BACK_LABEL,
77
+ onPress: onPressBack,
78
+ };
79
+
80
+ const continueProps: OnboardingButtonProps = {
81
+ ...sharedButtonProps,
82
+ label: continueLabel,
83
+ onPress: onPressContinue,
84
+ };
85
+
86
+ return (
87
+ <View style={[styles.container, style]}>
88
+ <View style={[styles.content, contentStyle]}>{step.content}</View>
89
+
90
+ <Steps steps={steps} currentStep={index} theme={theme} />
91
+
92
+ <View style={styles.footer}>
93
+ {!isFirstStep && (
94
+ <Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)}>
95
+ {backButton ? (
96
+ backButton(backProps)
97
+ ) : (
98
+ <Button
99
+ label={backProps.label}
100
+ onPress={backProps.onPress}
101
+ variant="secondary"
102
+ backgroundColor={backButtonColor}
103
+ labelColor={backButtonLabelColor}
104
+ />
105
+ )}
106
+ </Animated.View>
107
+ )}
108
+
109
+ <Animated.View style={styles.primaryAction} layout={LinearTransition.duration(250)}>
110
+ {continueButton ? (
111
+ continueButton(continueProps)
112
+ ) : (
113
+ <Button
114
+ label={continueProps.label}
115
+ onPress={continueProps.onPress}
116
+ backgroundColor={continueButtonColor}
117
+ labelColor={continueButtonLabelColor}
118
+ />
119
+ )}
120
+ </Animated.View>
121
+ </View>
122
+ </View>
123
+ );
124
+ };
125
+
126
+ const styles = StyleSheet.create({
127
+ container: {
128
+ flex: 1,
129
+ alignItems: 'center',
130
+ },
131
+ content: {
132
+ flex: 1,
133
+ alignSelf: 'stretch',
134
+ },
135
+ footer: {
136
+ flexDirection: 'row',
137
+ alignItems: 'center',
138
+ gap: 10,
139
+ width: '85%',
140
+ marginTop: 20,
141
+ },
142
+ primaryAction: {
143
+ flex: 1,
144
+ },
145
+ });
146
+
147
+ export default Onboarding;
package/src/Step.tsx ADDED
@@ -0,0 +1,38 @@
1
+ import { View } from 'react-native';
2
+ import Animated, {
3
+ interpolateColor,
4
+ useAnimatedStyle,
5
+ type SharedValue,
6
+ } from 'react-native-reanimated';
7
+ import type { StepsTheme } from './types';
8
+
9
+ type StepProps = {
10
+ index: number;
11
+ /** Animated position of the track's leading edge, in step units. */
12
+ progress: SharedValue<number>;
13
+ theme: StepsTheme;
14
+ };
15
+
16
+ const Step = ({ index, progress, theme }: StepProps) => {
17
+ const { stepSize, dotSize, activeDotColor, inactiveDotColor } = theme;
18
+
19
+ // The dot flips colour across the short window where the track's edge is
20
+ // sweeping over it, so the two always look like one movement.
21
+ const dotStyle = useAnimatedStyle(() => ({
22
+ backgroundColor: interpolateColor(
23
+ progress.value,
24
+ [index - 0.65, index - 0.25],
25
+ [inactiveDotColor, activeDotColor],
26
+ ),
27
+ }));
28
+
29
+ return (
30
+ <View style={{ alignItems: 'center', justifyContent: 'center', width: stepSize, height: stepSize }}>
31
+ <Animated.View
32
+ style={[{ width: dotSize, height: dotSize, borderRadius: dotSize / 2 }, dotStyle]}
33
+ />
34
+ </View>
35
+ );
36
+ };
37
+
38
+ export default Step;
package/src/Steps.tsx ADDED
@@ -0,0 +1,62 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { View } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withSpring,
7
+ } from 'react-native-reanimated';
8
+ import Step from './Step';
9
+ import { trackWidth } from './theme';
10
+ import type { OnboardingStep, StepsTheme } from './types';
11
+
12
+ type StepsProps = {
13
+ steps: OnboardingStep[];
14
+ currentStep: number;
15
+ theme: StepsTheme;
16
+ };
17
+
18
+ const Steps = ({ steps, currentStep, theme }: StepsProps) => {
19
+ const { stepSize, stepGap, trackColor, spring } = theme;
20
+
21
+ // Leading edge of the track, in step units. Every visual change — the
22
+ // track's width and each dot's colour — is derived from this one value so
23
+ // the sweep and the dots stay in sync, including on the way back.
24
+ const progress = useSharedValue(currentStep);
25
+
26
+ // Held in a ref so an inline `stepsTheme={{ ... }}` — which hands us a new
27
+ // spring object on every render — cannot restart an in-flight animation.
28
+ // The step change is the only thing that should kick the spring off, and the
29
+ // ref is always current by the time it does.
30
+ const springRef = useRef(spring);
31
+ springRef.current = spring;
32
+
33
+ useEffect(() => {
34
+ progress.value = withSpring(currentStep, springRef.current);
35
+ }, [currentStep]);
36
+
37
+ const trackStyle = useAnimatedStyle(() => ({
38
+ width: trackWidth(progress.value, stepSize, stepGap),
39
+ }));
40
+
41
+ return (
42
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: stepGap }}>
43
+ <Animated.View
44
+ style={[
45
+ {
46
+ position: 'absolute',
47
+ left: 0,
48
+ height: stepSize,
49
+ borderRadius: stepSize / 2,
50
+ backgroundColor: trackColor,
51
+ },
52
+ trackStyle,
53
+ ]}
54
+ />
55
+ {steps.map((item, index) => (
56
+ <Step key={item.id} index={index} progress={progress} theme={theme} />
57
+ ))}
58
+ </View>
59
+ );
60
+ };
61
+
62
+ export default Steps;
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export { default as Onboarding } from './Onboarding';
2
+ export { default as Steps } from './Steps';
3
+ export { default as Step } from './Step';
4
+ export { default as Button } from './Button';
5
+
6
+ export { defaultStepsTheme, resolveStepsTheme, trackWidth } from './theme';
7
+
8
+ export type {
9
+ OnboardingProps,
10
+ OnboardingStep,
11
+ OnboardingButtonProps,
12
+ OnboardingButtonRenderer,
13
+ StepsTheme,
14
+ StepsThemeInput,
15
+ } from './types';
package/src/theme.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { StepsTheme, StepsThemeInput } from './types';
2
+
3
+ /** The look the indicator falls back to when a field is not overridden. */
4
+ export const defaultStepsTheme: StepsTheme = {
5
+ stepSize: 26,
6
+ dotSize: 11,
7
+ stepGap: 9,
8
+ trackColor: '#7ADAA5',
9
+ activeDotColor: '#fff',
10
+ inactiveDotColor: '#696969',
11
+ spring: {
12
+ damping: 22,
13
+ stiffness: 180,
14
+ mass: 1,
15
+ },
16
+ };
17
+
18
+ /** Fills a partial theme with the defaults, leaving a fully resolved object. */
19
+ export const resolveStepsTheme = (input?: StepsThemeInput): StepsTheme => ({
20
+ ...defaultStepsTheme,
21
+ ...input,
22
+ });
23
+
24
+ /**
25
+ * Width of the track once it has swept `progress` steps past the first one.
26
+ *
27
+ * At `progress === 0` this is exactly one slot, so the track reads as a circle
28
+ * behind the first dot; at `progress === 1` it spans two slots plus the gap
29
+ * between them, visually connecting the two steps.
30
+ */
31
+ export const trackWidth = (progress: number, stepSize: number, stepGap: number) => {
32
+ 'worklet';
33
+ return (progress + 1) * stepSize + progress * stepGap;
34
+ };