@rocapine/react-native-onboarding-ui 1.1.5 → 1.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.
Files changed (115) hide show
  1. package/README.md +193 -0
  2. package/dist/UI/ErrorBoundary/ErrorBoundary.d.ts +1 -1
  3. package/dist/UI/OnboardingPage.d.ts.map +1 -1
  4. package/dist/UI/OnboardingPage.js +2 -0
  5. package/dist/UI/OnboardingPage.js.map +1 -1
  6. package/dist/UI/Pages/Carousel/Renderer.d.ts.map +1 -1
  7. package/dist/UI/Pages/Carousel/Renderer.js +5 -1
  8. package/dist/UI/Pages/Carousel/Renderer.js.map +1 -1
  9. package/dist/UI/Pages/Carousel/types.d.ts +4 -0
  10. package/dist/UI/Pages/Carousel/types.d.ts.map +1 -1
  11. package/dist/UI/Pages/Carousel/types.js +1 -0
  12. package/dist/UI/Pages/Carousel/types.js.map +1 -1
  13. package/dist/UI/Pages/Commitment/Renderer.d.ts.map +1 -1
  14. package/dist/UI/Pages/Commitment/Renderer.js +3 -1
  15. package/dist/UI/Pages/Commitment/Renderer.js.map +1 -1
  16. package/dist/UI/Pages/Commitment/types.d.ts +4 -0
  17. package/dist/UI/Pages/Commitment/types.d.ts.map +1 -1
  18. package/dist/UI/Pages/Commitment/types.js +1 -0
  19. package/dist/UI/Pages/Commitment/types.js.map +1 -1
  20. package/dist/UI/Pages/ComposableScreen/Renderer.d.ts +13 -0
  21. package/dist/UI/Pages/ComposableScreen/Renderer.d.ts.map +1 -0
  22. package/dist/UI/Pages/ComposableScreen/Renderer.js +96 -0
  23. package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -0
  24. package/dist/UI/Pages/ComposableScreen/index.d.ts +3 -0
  25. package/dist/UI/Pages/ComposableScreen/index.d.ts.map +1 -0
  26. package/dist/UI/Pages/ComposableScreen/index.js +19 -0
  27. package/dist/UI/Pages/ComposableScreen/index.js.map +1 -0
  28. package/dist/UI/Pages/ComposableScreen/types.d.ts +75 -0
  29. package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -0
  30. package/dist/UI/Pages/ComposableScreen/types.js +80 -0
  31. package/dist/UI/Pages/ComposableScreen/types.js.map +1 -0
  32. package/dist/UI/Pages/Loader/Renderer.d.ts.map +1 -1
  33. package/dist/UI/Pages/Loader/Renderer.js +7 -1
  34. package/dist/UI/Pages/Loader/Renderer.js.map +1 -1
  35. package/dist/UI/Pages/Loader/types.d.ts +4 -0
  36. package/dist/UI/Pages/Loader/types.d.ts.map +1 -1
  37. package/dist/UI/Pages/Loader/types.js +1 -0
  38. package/dist/UI/Pages/Loader/types.js.map +1 -1
  39. package/dist/UI/Pages/MediaContent/Renderer.d.ts.map +1 -1
  40. package/dist/UI/Pages/MediaContent/Renderer.js +5 -1
  41. package/dist/UI/Pages/MediaContent/Renderer.js.map +1 -1
  42. package/dist/UI/Pages/MediaContent/types.d.ts +4 -0
  43. package/dist/UI/Pages/MediaContent/types.d.ts.map +1 -1
  44. package/dist/UI/Pages/MediaContent/types.js +1 -0
  45. package/dist/UI/Pages/MediaContent/types.js.map +1 -1
  46. package/dist/UI/Pages/Picker/Renderer.d.ts.map +1 -1
  47. package/dist/UI/Pages/Picker/Renderer.js +11 -5
  48. package/dist/UI/Pages/Picker/Renderer.js.map +1 -1
  49. package/dist/UI/Pages/Picker/types.d.ts +4 -0
  50. package/dist/UI/Pages/Picker/types.d.ts.map +1 -1
  51. package/dist/UI/Pages/Picker/types.js +1 -0
  52. package/dist/UI/Pages/Picker/types.js.map +1 -1
  53. package/dist/UI/Pages/Question/Renderer.d.ts.map +1 -1
  54. package/dist/UI/Pages/Question/Renderer.js +5 -1
  55. package/dist/UI/Pages/Question/Renderer.js.map +1 -1
  56. package/dist/UI/Pages/Question/types.d.ts +5 -0
  57. package/dist/UI/Pages/Question/types.d.ts.map +1 -1
  58. package/dist/UI/Pages/Question/types.js +2 -0
  59. package/dist/UI/Pages/Question/types.js.map +1 -1
  60. package/dist/UI/Pages/Ratings/Renderer.d.ts.map +1 -1
  61. package/dist/UI/Pages/Ratings/Renderer.js +3 -1
  62. package/dist/UI/Pages/Ratings/Renderer.js.map +1 -1
  63. package/dist/UI/Pages/Ratings/types.d.ts +4 -0
  64. package/dist/UI/Pages/Ratings/types.d.ts.map +1 -1
  65. package/dist/UI/Pages/Ratings/types.js +1 -0
  66. package/dist/UI/Pages/Ratings/types.js.map +1 -1
  67. package/dist/UI/Pages/index.d.ts +1 -0
  68. package/dist/UI/Pages/index.d.ts.map +1 -1
  69. package/dist/UI/Pages/index.js +1 -0
  70. package/dist/UI/Pages/index.js.map +1 -1
  71. package/dist/UI/Pages/types.d.ts +4 -0
  72. package/dist/UI/Pages/types.d.ts.map +1 -1
  73. package/dist/UI/Pages/types.js +5 -1
  74. package/dist/UI/Pages/types.js.map +1 -1
  75. package/dist/UI/Templates/OnboardingTemplate.d.ts +1 -0
  76. package/dist/UI/Templates/OnboardingTemplate.d.ts.map +1 -1
  77. package/dist/UI/Templates/OnboardingTemplate.js +86 -2
  78. package/dist/UI/Templates/OnboardingTemplate.js.map +1 -1
  79. package/dist/UI/types.d.ts +2 -2
  80. package/dist/UI/types.d.ts.map +1 -1
  81. package/package.json +1 -1
  82. package/src/UI/OnboardingPage.tsx +3 -1
  83. package/src/UI/Pages/Carousel/Renderer.tsx +4 -1
  84. package/src/UI/Pages/Carousel/types.ts +2 -1
  85. package/src/UI/Pages/Commitment/Renderer.tsx +2 -1
  86. package/src/UI/Pages/Commitment/types.ts +2 -1
  87. package/src/UI/Pages/ComposableScreen/Renderer.tsx +146 -0
  88. package/src/UI/Pages/ComposableScreen/index.ts +2 -0
  89. package/src/UI/Pages/ComposableScreen/types.ts +145 -0
  90. package/src/UI/Pages/Loader/Renderer.tsx +6 -1
  91. package/src/UI/Pages/Loader/types.ts +2 -1
  92. package/src/UI/Pages/MediaContent/Renderer.tsx +4 -1
  93. package/src/UI/Pages/MediaContent/types.ts +2 -0
  94. package/src/UI/Pages/Picker/Renderer.tsx +6 -5
  95. package/src/UI/Pages/Picker/types.ts +2 -1
  96. package/src/UI/Pages/Question/Renderer.tsx +4 -1
  97. package/src/UI/Pages/Question/types.ts +3 -1
  98. package/src/UI/Pages/Ratings/Renderer.tsx +2 -1
  99. package/src/UI/Pages/Ratings/types.ts +2 -1
  100. package/src/UI/Pages/index.ts +1 -0
  101. package/src/UI/Pages/types.ts +5 -0
  102. package/src/UI/Templates/OnboardingTemplate.tsx +73 -10
  103. package/src/UI/types.ts +2 -0
  104. package/dist/provider/CustomComponentsContext.d.ts +0 -57
  105. package/dist/provider/CustomComponentsContext.d.ts.map +0 -1
  106. package/dist/provider/CustomComponentsContext.js +0 -19
  107. package/dist/provider/CustomComponentsContext.js.map +0 -1
  108. package/dist/provider/OnboardingUIProvider.d.ts +0 -44
  109. package/dist/provider/OnboardingUIProvider.d.ts.map +0 -1
  110. package/dist/provider/OnboardingUIProvider.js +0 -33
  111. package/dist/provider/OnboardingUIProvider.js.map +0 -1
  112. package/dist/provider/OnboardingUIProvider.old.d.ts +0 -60
  113. package/dist/provider/OnboardingUIProvider.old.d.ts.map +0 -1
  114. package/dist/provider/OnboardingUIProvider.old.js +0 -53
  115. package/dist/provider/OnboardingUIProvider.old.js.map +0 -1
@@ -0,0 +1,146 @@
1
+ import React from "react";
2
+ import { View, Text, ScrollView, StyleSheet, TouchableOpacity } from "react-native";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import { ComposableScreenStepType, ComposableScreenStepTypeSchema, UIElement } from "./types";
5
+ import { Theme } from "../../Theme/types";
6
+ import { defaultTheme } from "../../Theme/defaultTheme";
7
+ import { getTextStyle } from "../../Theme/helpers";
8
+
9
+ import { withErrorBoundary } from "../../ErrorBoundary";
10
+ import { OnboardingTemplate } from "../../Templates/OnboardingTemplate";
11
+
12
+ type ContentProps = {
13
+ step: ComposableScreenStepType;
14
+ onContinue: () => void;
15
+ theme?: Theme;
16
+ };
17
+
18
+ const renderElement = (element: UIElement, theme: Theme, parentType?: "XStack" | "YStack"): React.ReactNode => {
19
+ if (element.type === "YStack" || element.type === "XStack") {
20
+ return (
21
+ <View
22
+ key={element.id}
23
+ style={{
24
+ flexDirection: element.type === "XStack" ? "row" : "column",
25
+ gap: element.props.gap,
26
+ padding: element.props.padding,
27
+ paddingHorizontal: element.props.paddingHorizontal,
28
+ paddingVertical: element.props.paddingVertical,
29
+ margin: element.props.margin,
30
+ marginHorizontal: element.props.marginHorizontal,
31
+ marginVertical: element.props.marginVertical,
32
+ flex: element.props.flex,
33
+ width: element.props.width,
34
+ height: element.props.height,
35
+ minWidth: element.props.minWidth,
36
+ maxWidth: element.props.maxWidth,
37
+ minHeight: element.props.minHeight,
38
+ maxHeight: element.props.maxHeight,
39
+ flexShrink: element.props.flexShrink ?? (parentType === "XStack" ? 1 : undefined),
40
+ flexWrap: element.props.flexWrap,
41
+ alignItems: element.props.alignItems,
42
+ justifyContent: element.props.justifyContent,
43
+ backgroundColor: element.props.backgroundColor,
44
+ borderWidth: element.props.borderWidth,
45
+ borderRadius: element.props.borderRadius,
46
+ borderColor: element.props.borderColor,
47
+ overflow: element.props.overflow,
48
+ opacity: element.props.opacity,
49
+ }}
50
+ >
51
+ {element.children.map((child) => renderElement(child, theme, element.type))}
52
+ </View>
53
+ );
54
+ }
55
+
56
+ if (element.type === "Text") {
57
+ return (
58
+ <Text
59
+ key={element.id}
60
+ style={{
61
+ fontSize: element.props.fontSize,
62
+ fontWeight: element.props.fontWeight as any,
63
+ color: element.props.color ?? theme.colors.text.primary,
64
+ textAlign: element.props.textAlign,
65
+ letterSpacing: element.props.letterSpacing,
66
+ lineHeight: element.props.lineHeight,
67
+ backgroundColor: element.props.backgroundColor,
68
+ padding: element.props.padding,
69
+ paddingHorizontal: element.props.paddingHorizontal,
70
+ paddingVertical: element.props.paddingVertical,
71
+ margin: element.props.margin,
72
+ marginHorizontal: element.props.marginHorizontal,
73
+ marginVertical: element.props.marginVertical,
74
+ borderWidth: element.props.borderWidth,
75
+ borderRadius: element.props.borderRadius,
76
+ borderColor: element.props.borderColor,
77
+ opacity: element.props.opacity,
78
+ flexShrink: parentType === "XStack" ? 1 : undefined,
79
+ }}
80
+ >
81
+ {element.props.content}
82
+ </Text>
83
+ );
84
+ }
85
+
86
+ return null;
87
+ };
88
+
89
+ const ComposableScreenRendererBase = ({ step, onContinue, theme = defaultTheme }: ContentProps) => {
90
+ const { top, bottom } = useSafeAreaInsets();
91
+ const validatedData = ComposableScreenStepTypeSchema.parse(step);
92
+ const { elements } = validatedData.payload;
93
+ return (
94
+ <OnboardingTemplate
95
+ step={step}
96
+ onContinue={onContinue}
97
+ theme={theme}
98
+ >
99
+ <ScrollView
100
+ contentContainerStyle={styles.scrollContent}
101
+ showsVerticalScrollIndicator={false}
102
+ alwaysBounceVertical={false}
103
+ >
104
+ {elements.map((element) => renderElement(element, theme))}
105
+ <View style={styles.bottomSection}>
106
+ <TouchableOpacity
107
+ style={[styles.ctaButton, { backgroundColor: theme.colors.primary }]}
108
+ onPress={onContinue}
109
+ activeOpacity={0.8}
110
+ >
111
+ <Text
112
+ style={[
113
+ getTextStyle(theme, "button"),
114
+ { color: theme.colors.text.opposite },
115
+ ]}
116
+ >
117
+ {validatedData.continueButtonLabel}
118
+ </Text>
119
+ </TouchableOpacity>
120
+ </View>
121
+ </ScrollView>
122
+ </OnboardingTemplate>
123
+ )
124
+ };
125
+
126
+ const styles = StyleSheet.create({
127
+ container: {
128
+ flex: 1,
129
+ },
130
+ scrollContent: {
131
+ flexGrow: 1,
132
+ },
133
+ bottomSection: {
134
+ paddingHorizontal: 32,
135
+ alignItems: "center",
136
+ },
137
+ ctaButton: {
138
+ borderRadius: 90,
139
+ paddingVertical: 18,
140
+ paddingHorizontal: 24,
141
+ minWidth: 234,
142
+ alignItems: "center",
143
+ },
144
+ });
145
+
146
+ export const ComposableScreenRenderer = withErrorBoundary(ComposableScreenRendererBase, "ComposableScreen");
@@ -0,0 +1,2 @@
1
+ export * from "./Renderer";
2
+ export * from "./types";
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ import { CustomPayloadSchema } from "../types";
3
+
4
+ export type UIElement =
5
+ | {
6
+ id: string;
7
+ name?: string;
8
+ type: "YStack" | "XStack";
9
+ props: {
10
+ gap?: number;
11
+ padding?: number;
12
+ paddingHorizontal?: number;
13
+ paddingVertical?: number;
14
+ margin?: number;
15
+ marginHorizontal?: number;
16
+ marginVertical?: number;
17
+ flex?: number;
18
+ width?: number;
19
+ height?: number;
20
+ minWidth?: number;
21
+ maxWidth?: number;
22
+ minHeight?: number;
23
+ maxHeight?: number;
24
+ alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
25
+ justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-around";
26
+ backgroundColor?: string;
27
+ flexWrap?: "wrap" | "nowrap";
28
+ flexShrink?: number;
29
+ borderWidth?: number;
30
+ borderRadius?: number;
31
+ borderColor?: string;
32
+ overflow?: "hidden" | "visible" | "scroll";
33
+ opacity?: number;
34
+ };
35
+ children: UIElement[];
36
+ }
37
+ | {
38
+ id: string;
39
+ name?: string;
40
+ type: "Text";
41
+ props: {
42
+ content: string;
43
+ fontSize?: number;
44
+ fontWeight?: string;
45
+ color?: string;
46
+ textAlign?: "left" | "center" | "right";
47
+ letterSpacing?: number;
48
+ lineHeight?: number;
49
+ backgroundColor?: string;
50
+ padding?: number;
51
+ paddingHorizontal?: number;
52
+ paddingVertical?: number;
53
+ margin?: number;
54
+ marginHorizontal?: number;
55
+ marginVertical?: number;
56
+ borderWidth?: number;
57
+ borderRadius?: number;
58
+ borderColor?: string;
59
+ opacity?: number;
60
+ };
61
+ };
62
+
63
+ const StackElementPropsSchema = z.object({
64
+ gap: z.number().optional(),
65
+ padding: z.number().optional(),
66
+ paddingHorizontal: z.number().optional(),
67
+ paddingVertical: z.number().optional(),
68
+ margin: z.number().optional(),
69
+ marginHorizontal: z.number().optional(),
70
+ marginVertical: z.number().optional(),
71
+ flex: z.number().optional(),
72
+ width: z.number().optional(),
73
+ height: z.number().optional(),
74
+ minWidth: z.number().optional(),
75
+ maxWidth: z.number().optional(),
76
+ minHeight: z.number().optional(),
77
+ maxHeight: z.number().optional(),
78
+ alignItems: z.enum(["flex-start", "center", "flex-end", "stretch"]).optional(),
79
+ justifyContent: z.enum(["flex-start", "center", "flex-end", "space-between", "space-around"]).optional(),
80
+ backgroundColor: z.string().optional(),
81
+ flexWrap: z.enum(["wrap", "nowrap"]).optional(),
82
+ flexShrink: z.number().optional(),
83
+ borderWidth: z.number().optional(),
84
+ borderRadius: z.number().optional(),
85
+ borderColor: z.string().optional(),
86
+ overflow: z.enum(["hidden", "visible", "scroll"]).optional(),
87
+ opacity: z.number().min(0).max(1).optional(),
88
+ });
89
+
90
+ const TextElementPropsSchema = z.object({
91
+ content: z.string(),
92
+ fontSize: z.number().optional(),
93
+ fontWeight: z.string().optional(),
94
+ color: z.string().optional(),
95
+ textAlign: z.enum(["left", "center", "right"]).optional(),
96
+ letterSpacing: z.number().optional(),
97
+ lineHeight: z.number().optional(),
98
+ backgroundColor: z.string().optional(),
99
+ padding: z.number().optional(),
100
+ paddingHorizontal: z.number().optional(),
101
+ paddingVertical: z.number().optional(),
102
+ margin: z.number().optional(),
103
+ marginHorizontal: z.number().optional(),
104
+ marginVertical: z.number().optional(),
105
+ borderWidth: z.number().optional(),
106
+ borderRadius: z.number().optional(),
107
+ borderColor: z.string().optional(),
108
+ opacity: z.number().min(0).max(1).optional(),
109
+ });
110
+
111
+ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
112
+ z.union([
113
+ z.object({
114
+ id: z.string(),
115
+ name: z.string().optional(),
116
+ type: z.union([z.literal("YStack"), z.literal("XStack")]),
117
+ props: StackElementPropsSchema,
118
+ children: z.array(UIElementSchema),
119
+ }),
120
+ z.object({
121
+ id: z.string(),
122
+ name: z.string().optional(),
123
+ type: z.literal("Text"),
124
+ props: TextElementPropsSchema,
125
+ }),
126
+ ])
127
+ );
128
+
129
+
130
+ export const ComposableScreenStepPayloadSchema = z.object({
131
+ elements: z.array(UIElementSchema),
132
+ });
133
+
134
+ export const ComposableScreenStepTypeSchema = z.object({
135
+ id: z.string(),
136
+ type: z.literal("ComposableScreen"),
137
+ name: z.string(),
138
+ displayProgressHeader: z.boolean(),
139
+ payload: ComposableScreenStepPayloadSchema,
140
+ customPayload: CustomPayloadSchema,
141
+ continueButtonLabel: z.string().optional().default("Continue"),
142
+ figmaUrl: z.string().nullable(),
143
+ });
144
+
145
+ export type ComposableScreenStepType = z.infer<typeof ComposableScreenStepTypeSchema>;
@@ -102,7 +102,12 @@ const BarsVariant = ({
102
102
  onContinue={onContinue}
103
103
  theme={theme}
104
104
  button={
105
- isComplete ? { text: validatedData.continueButtonLabel } : undefined
105
+ isComplete
106
+ ? {
107
+ text: validatedData.buttonSection?.label?.trim() || validatedData.continueButtonLabel,
108
+ icon: validatedData.buttonSection?.icon?.trim() || null,
109
+ }
110
+ : undefined
106
111
  }
107
112
  >
108
113
  <ScrollView
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { CustomPayloadSchema, MediaSourceSchema } from "../types";
2
+ import { ButtonSectionSchema, CustomPayloadSchema, MediaSourceSchema } from "../types";
3
3
 
4
4
  export const LoaderStepSchema = z.object({
5
5
  label: z.string(),
@@ -25,6 +25,7 @@ export const LoaderStepTypeSchema = z.object({
25
25
  payload: LoaderStepPayloadSchema,
26
26
  customPayload: CustomPayloadSchema,
27
27
  continueButtonLabel: z.string().optional().default("Continue"),
28
+ buttonSection: ButtonSectionSchema.optional(),
28
29
  figmaUrl: z.string().nullish(),
29
30
  });
30
31
 
@@ -119,7 +119,10 @@ const MediaContentRendererBase = ({ step, onContinue, theme = defaultTheme }: Co
119
119
  step={step}
120
120
  onContinue={onContinue}
121
121
  theme={theme}
122
- button={{ text: validatedData.continueButtonLabel }}
122
+ button={{
123
+ text: validatedData.buttonSection?.label?.trim() || validatedData.continueButtonLabel,
124
+ icon: validatedData.buttonSection?.icon?.trim() || null,
125
+ }}
123
126
  >
124
127
  {renderContent()}
125
128
  </OnboardingTemplate>
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import {
3
+ ButtonSectionSchema,
3
4
  CustomPayloadSchema,
4
5
  MediaSourceSchema,
5
6
  SocialProofSchema,
@@ -25,6 +26,7 @@ export const MediaContentStepTypeSchema = z.object({
25
26
  payload: MediaContentStepPayloadSchema,
26
27
  customPayload: CustomPayloadSchema,
27
28
  continueButtonLabel: z.string().optional().default("Continue"),
29
+ buttonSection: ButtonSectionSchema.optional(),
28
30
  figmaUrl: z.string().nullish(),
29
31
  });
30
32
 
@@ -90,7 +90,7 @@ const PickerRendererBase = ({ step, onContinue, theme = defaultTheme }: ContentP
90
90
  step={step}
91
91
  onContinue={() => onContinue()}
92
92
  theme={theme}
93
- button={{ text: validatedData.continueButtonLabel }}
93
+ button={{ text: validatedData.buttonSection?.label?.trim() || validatedData.continueButtonLabel, icon: validatedData.buttonSection?.icon?.trim() || null }}
94
94
  >
95
95
  <View style={styles.container}>
96
96
  <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>{title}</Text>
@@ -149,7 +149,7 @@ const WeightPicker = ({
149
149
  step={step}
150
150
  onContinue={handleContinue}
151
151
  theme={theme}
152
- button={{ text: step.continueButtonLabel }}
152
+ button={{ text: step.buttonSection?.label?.trim() || step.continueButtonLabel, icon: step.buttonSection?.icon?.trim() || null }}
153
153
  >
154
154
  <View style={styles.container}>
155
155
  <View style={styles.textContainer}>
@@ -265,7 +265,7 @@ const HeightPicker = ({
265
265
  step={step}
266
266
  onContinue={handleContinue}
267
267
  theme={theme}
268
- button={{ text: step.continueButtonLabel }}
268
+ button={{ text: step.buttonSection?.label?.trim() || step.continueButtonLabel, icon: step.buttonSection?.icon?.trim() || null }}
269
269
  >
270
270
  <View style={styles.container}>
271
271
  <View style={styles.textContainer}>
@@ -372,7 +372,8 @@ const NamePicker = ({
372
372
  onContinue={handleContinue}
373
373
  theme={theme}
374
374
  button={{
375
- text: step.continueButtonLabel,
375
+ text: step.buttonSection?.label?.trim() || step.continueButtonLabel,
376
+ icon: step.buttonSection?.icon?.trim() || null,
376
377
  disabled: !name.trim(),
377
378
  }}
378
379
  >
@@ -479,7 +480,7 @@ const DatePicker = ({
479
480
  step={step}
480
481
  onContinue={handleContinue}
481
482
  theme={theme}
482
- button={{ text: step.continueButtonLabel }}
483
+ button={{ text: step.buttonSection?.label?.trim() || step.continueButtonLabel, icon: step.buttonSection?.icon?.trim() || null }}
483
484
  >
484
485
  <View style={styles.container}>
485
486
  <View style={styles.textContainer}>
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { CustomPayloadSchema } from "../types";
2
+ import { ButtonSectionSchema, CustomPayloadSchema } from "../types";
3
3
 
4
4
  export const PickerTypeEnum = z.enum([
5
5
  "height",
@@ -25,6 +25,7 @@ export const PickerStepTypeSchema = z.object({
25
25
  payload: PickerStepPayloadSchema,
26
26
  customPayload: CustomPayloadSchema,
27
27
  continueButtonLabel: z.string().optional().default("Continue"),
28
+ buttonSection: ButtonSectionSchema.optional(),
28
29
  figmaUrl: z.string().nullish(),
29
30
  });
30
31
 
@@ -90,7 +90,10 @@ const QuestionRendererBase = ({ step, onContinue, theme = defaultTheme, customCo
90
90
  step={step}
91
91
  onContinue={handleContinue || (() => { })}
92
92
  theme={theme}
93
- button={multipleAnswer && isAnySelected ? { text: "Continue" } : undefined}
93
+ button={multipleAnswer && isAnySelected ? {
94
+ text: validatedData.buttonSection?.label?.trim() || validatedData.continueButtonLabel,
95
+ icon: validatedData.buttonSection?.icon?.trim() || null,
96
+ } : undefined}
94
97
  >
95
98
  <View style={styles.container}>
96
99
  {/* Main Content */}
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { CustomPayloadSchema, InfoBoxSchema } from "../types";
2
+ import { ButtonSectionSchema, CustomPayloadSchema, InfoBoxSchema } from "../types";
3
3
 
4
4
  export const AnswerSchema = z.object({
5
5
  label: z.string(),
@@ -23,6 +23,8 @@ export const QuestionStepTypeSchema = z.object({
23
23
  displayProgressHeader: z.boolean(),
24
24
  payload: QuestionStepPayloadSchema,
25
25
  customPayload: CustomPayloadSchema,
26
+ continueButtonLabel: z.string().optional().default("Continue"),
27
+ buttonSection: ButtonSectionSchema.optional(),
26
28
  figmaUrl: z.string().nullish(),
27
29
  });
28
30
 
@@ -83,7 +83,8 @@ const RatingsRendererBase = ({ step, onContinue, theme = defaultTheme }: Ratings
83
83
  button={{
84
84
  text: !hasOpenedRequestReview
85
85
  ? rateTheAppButtonLabel
86
- : validatedData.continueButtonLabel,
86
+ : (validatedData.buttonSection?.label?.trim() || validatedData.continueButtonLabel),
87
+ icon: hasOpenedRequestReview ? (validatedData.buttonSection?.icon?.trim() || null) : null,
87
88
  }}
88
89
  >
89
90
  <View style={styles.container}>
@@ -1,5 +1,5 @@
1
1
  import z from "zod";
2
- import { CustomPayloadSchema, SocialProofSchema } from "../types";
2
+ import { ButtonSectionSchema, CustomPayloadSchema, SocialProofSchema } from "../types";
3
3
 
4
4
  export const RatingsStepPayloadSchema = z.object({
5
5
  title: z.string(),
@@ -16,6 +16,7 @@ export const RatingsStepTypeSchema = z.object({
16
16
  payload: RatingsStepPayloadSchema,
17
17
  customPayload: CustomPayloadSchema,
18
18
  continueButtonLabel: z.string().optional().default("Continue"),
19
+ buttonSection: ButtonSectionSchema.optional(),
19
20
  figmaUrl: z.string().nullish(),
20
21
  });
21
22
 
@@ -2,6 +2,7 @@ export * from "./Carousel";
2
2
  export * from "./Commitment";
3
3
  export * from "./Loader";
4
4
  export * from "./MediaContent";
5
+ export * from "./ComposableScreen";
5
6
  export * from "./Picker";
6
7
  export * from "./Question";
7
8
  export * from "./Ratings";
@@ -23,3 +23,8 @@ export const InfoBoxSchema = z.object({
23
23
  title: z.string(),
24
24
  content: z.string(),
25
25
  });
26
+
27
+ export const ButtonSectionSchema = z.object({
28
+ label: z.string().optional(),
29
+ icon: z.string().nullish(),
30
+ });
@@ -4,6 +4,42 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
4
  import { getTextStyle } from "../Theme/helpers";
5
5
  import { Theme } from "../Theme/types";
6
6
  import { defaultTheme } from "../Theme/defaultTheme";
7
+ import * as LucideIcons from "lucide-react-native";
8
+
9
+ function lucideIconLookupKeys(raw: string): string[] {
10
+ const s = raw.trim();
11
+ if (!s) return [];
12
+ const keys: string[] = [s];
13
+ if (/[-_\s]/.test(s)) {
14
+ const pascal = s
15
+ .split(/[-_\s]+/)
16
+ .filter(Boolean)
17
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
18
+ .join("");
19
+ if (pascal && !keys.includes(pascal)) keys.push(pascal);
20
+ }
21
+ if (/^[a-z][a-z0-9]*$/.test(s)) {
22
+ const cap = s.charAt(0).toUpperCase() + s.slice(1);
23
+ if (!keys.includes(cap)) keys.push(cap);
24
+ }
25
+ if (/^[A-Z0-9]+$/.test(s) && s.length > 1) {
26
+ const title = s.charAt(0) + s.slice(1).toLowerCase();
27
+ if (!keys.includes(title)) keys.push(title);
28
+ }
29
+ return keys;
30
+ }
31
+
32
+ function resolveIcon(iconName: string, color: string): React.ReactElement | null {
33
+ if (!iconName?.trim()) return null;
34
+ const mod = LucideIcons as Record<string, any>;
35
+ for (const key of lucideIconLookupKeys(iconName)) {
36
+ const IconComponent = mod[key];
37
+ if (IconComponent != null) {
38
+ return <IconComponent size={20} color={color} strokeWidth={2} />;
39
+ }
40
+ }
41
+ return null;
42
+ }
7
43
 
8
44
  type OnboardingTemplateProps = {
9
45
  children: React.ReactNode;
@@ -11,6 +47,7 @@ type OnboardingTemplateProps = {
11
47
  button?: {
12
48
  text: string;
13
49
  disabled?: boolean;
50
+ icon?: string | null;
14
51
  };
15
52
  step: OnboardingStepType;
16
53
  theme?: Theme;
@@ -49,16 +86,37 @@ export const OnboardingTemplate = ({
49
86
  activeOpacity={0.8}
50
87
  disabled={button.disabled}
51
88
  >
52
- <Text
53
- style={[
54
- getTextStyle(theme, "button"),
55
- styles.ctaButtonText,
56
- { color: theme.colors.text.opposite },
57
- button.disabled && { color: theme.colors.text.disable },
58
- ]}
59
- >
60
- {button.text}
61
- </Text>
89
+ {button.icon ? (
90
+ <View style={styles.ctaButtonContent}>
91
+ {resolveIcon(
92
+ button.icon,
93
+ button.disabled
94
+ ? theme.colors.text.disable
95
+ : theme.colors.text.opposite
96
+ )}
97
+ <Text
98
+ style={[
99
+ getTextStyle(theme, "button"),
100
+ styles.ctaButtonText,
101
+ { color: theme.colors.text.opposite },
102
+ button.disabled && { color: theme.colors.text.disable },
103
+ ]}
104
+ >
105
+ {button.text}
106
+ </Text>
107
+ </View>
108
+ ) : (
109
+ <Text
110
+ style={[
111
+ getTextStyle(theme, "button"),
112
+ styles.ctaButtonText,
113
+ { color: theme.colors.text.opposite },
114
+ button.disabled && { color: theme.colors.text.disable },
115
+ ]}
116
+ >
117
+ {button.text}
118
+ </Text>
119
+ )}
62
120
  </TouchableOpacity>
63
121
  </View>
64
122
  )}
@@ -83,4 +141,9 @@ const styles = StyleSheet.create({
83
141
  alignItems: "center",
84
142
  },
85
143
  ctaButtonText: {},
144
+ ctaButtonContent: {
145
+ flexDirection: "row",
146
+ alignItems: "center",
147
+ gap: 8,
148
+ },
86
149
  });
package/src/UI/types.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  CommitmentStepType,
4
4
  LoaderStepType,
5
5
  MediaContentStepType,
6
+ ComposableScreenStepType,
6
7
  PickerStepType,
7
8
  RatingsStepType,
8
9
  QuestionStepType,
@@ -33,6 +34,7 @@ export type BaseStepType = {
33
34
  export type OnboardingStepType =
34
35
  | RatingsStepType
35
36
  | MediaContentStepType
37
+ | ComposableScreenStepType
36
38
  | PickerStepType
37
39
  | CommitmentStepType
38
40
  | CarouselStepType
@@ -1,57 +0,0 @@
1
- import React from "react";
2
- import { QuestionAnswerButtonProps, QuestionAnswersListProps } from "../UI/Pages/Question/components";
3
- /**
4
- * Custom components that can be provided to override default implementations.
5
- * Allows full UI customization for specific parts of the onboarding flow.
6
- */
7
- export interface CustomComponents {
8
- /**
9
- * Custom component for individual Question answer buttons.
10
- * Replaces the default button styling while keeping list logic.
11
- *
12
- * @example
13
- * ```tsx
14
- * const MyButton = ({ answer, selected, onPress, theme }) => (
15
- * <TouchableOpacity onPress={onPress} style={{ height: 96 }}>
16
- * <Text>{answer.label}</Text>
17
- * </TouchableOpacity>
18
- * );
19
- *
20
- * <OnboardingProvider customComponents={{ QuestionAnswerButton: MyButton }} />
21
- * ```
22
- */
23
- QuestionAnswerButton?: React.ComponentType<QuestionAnswerButtonProps>;
24
- /**
25
- * Custom component for the entire Question answers list.
26
- * Provides full control over list rendering, animations, and layout.
27
- * Takes priority over QuestionAnswerButton.
28
- *
29
- * @example
30
- * ```tsx
31
- * const MyList = ({ answers, selected, onAnswerPress, theme }) => (
32
- * <Animated.View>
33
- * {answers.map(answer => (
34
- * <MyButton key={answer.value} answer={answer} />
35
- * ))}
36
- * </Animated.View>
37
- * );
38
- *
39
- * <OnboardingProvider customComponents={{ QuestionAnswersList: MyList }} />
40
- * ```
41
- */
42
- QuestionAnswersList?: React.ComponentType<QuestionAnswersListProps>;
43
- }
44
- /**
45
- * Provider for custom components.
46
- * Wraps the app to make custom components available to all renderers.
47
- */
48
- export declare const CustomComponentsProvider: React.FC<{
49
- children: React.ReactNode;
50
- components?: CustomComponents;
51
- }>;
52
- /**
53
- * Hook to access custom components from context.
54
- * Returns empty object if no custom components are provided.
55
- */
56
- export declare const useCustomComponents: () => CustomComponents;
57
- //# sourceMappingURL=CustomComponentsContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CustomComponentsContext.d.ts","sourceRoot":"","sources":["../../src/provider/CustomComponentsContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,iCAAiC,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC;IAEtE;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAAmB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAC;CAMrE;AAID;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC;IAC9C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAIA,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wBAA4C,CAAC"}