@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.
- package/README.md +193 -0
- package/dist/UI/ErrorBoundary/ErrorBoundary.d.ts +1 -1
- package/dist/UI/OnboardingPage.d.ts.map +1 -1
- package/dist/UI/OnboardingPage.js +2 -0
- package/dist/UI/OnboardingPage.js.map +1 -1
- package/dist/UI/Pages/Carousel/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/Carousel/Renderer.js +5 -1
- package/dist/UI/Pages/Carousel/Renderer.js.map +1 -1
- package/dist/UI/Pages/Carousel/types.d.ts +4 -0
- package/dist/UI/Pages/Carousel/types.d.ts.map +1 -1
- package/dist/UI/Pages/Carousel/types.js +1 -0
- package/dist/UI/Pages/Carousel/types.js.map +1 -1
- package/dist/UI/Pages/Commitment/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/Commitment/Renderer.js +3 -1
- package/dist/UI/Pages/Commitment/Renderer.js.map +1 -1
- package/dist/UI/Pages/Commitment/types.d.ts +4 -0
- package/dist/UI/Pages/Commitment/types.d.ts.map +1 -1
- package/dist/UI/Pages/Commitment/types.js +1 -0
- package/dist/UI/Pages/Commitment/types.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.d.ts +13 -0
- package/dist/UI/Pages/ComposableScreen/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/Renderer.js +96 -0
- package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/index.d.ts +3 -0
- package/dist/UI/Pages/ComposableScreen/index.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/index.js +19 -0
- package/dist/UI/Pages/ComposableScreen/index.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/types.d.ts +75 -0
- package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/types.js +80 -0
- package/dist/UI/Pages/ComposableScreen/types.js.map +1 -0
- package/dist/UI/Pages/Loader/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/Loader/Renderer.js +7 -1
- package/dist/UI/Pages/Loader/Renderer.js.map +1 -1
- package/dist/UI/Pages/Loader/types.d.ts +4 -0
- package/dist/UI/Pages/Loader/types.d.ts.map +1 -1
- package/dist/UI/Pages/Loader/types.js +1 -0
- package/dist/UI/Pages/Loader/types.js.map +1 -1
- package/dist/UI/Pages/MediaContent/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/MediaContent/Renderer.js +5 -1
- package/dist/UI/Pages/MediaContent/Renderer.js.map +1 -1
- package/dist/UI/Pages/MediaContent/types.d.ts +4 -0
- package/dist/UI/Pages/MediaContent/types.d.ts.map +1 -1
- package/dist/UI/Pages/MediaContent/types.js +1 -0
- package/dist/UI/Pages/MediaContent/types.js.map +1 -1
- package/dist/UI/Pages/Picker/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/Picker/Renderer.js +11 -5
- package/dist/UI/Pages/Picker/Renderer.js.map +1 -1
- package/dist/UI/Pages/Picker/types.d.ts +4 -0
- package/dist/UI/Pages/Picker/types.d.ts.map +1 -1
- package/dist/UI/Pages/Picker/types.js +1 -0
- package/dist/UI/Pages/Picker/types.js.map +1 -1
- package/dist/UI/Pages/Question/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/Question/Renderer.js +5 -1
- package/dist/UI/Pages/Question/Renderer.js.map +1 -1
- package/dist/UI/Pages/Question/types.d.ts +5 -0
- package/dist/UI/Pages/Question/types.d.ts.map +1 -1
- package/dist/UI/Pages/Question/types.js +2 -0
- package/dist/UI/Pages/Question/types.js.map +1 -1
- package/dist/UI/Pages/Ratings/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/Ratings/Renderer.js +3 -1
- package/dist/UI/Pages/Ratings/Renderer.js.map +1 -1
- package/dist/UI/Pages/Ratings/types.d.ts +4 -0
- package/dist/UI/Pages/Ratings/types.d.ts.map +1 -1
- package/dist/UI/Pages/Ratings/types.js +1 -0
- package/dist/UI/Pages/Ratings/types.js.map +1 -1
- package/dist/UI/Pages/index.d.ts +1 -0
- package/dist/UI/Pages/index.d.ts.map +1 -1
- package/dist/UI/Pages/index.js +1 -0
- package/dist/UI/Pages/index.js.map +1 -1
- package/dist/UI/Pages/types.d.ts +4 -0
- package/dist/UI/Pages/types.d.ts.map +1 -1
- package/dist/UI/Pages/types.js +5 -1
- package/dist/UI/Pages/types.js.map +1 -1
- package/dist/UI/Templates/OnboardingTemplate.d.ts +1 -0
- package/dist/UI/Templates/OnboardingTemplate.d.ts.map +1 -1
- package/dist/UI/Templates/OnboardingTemplate.js +86 -2
- package/dist/UI/Templates/OnboardingTemplate.js.map +1 -1
- package/dist/UI/types.d.ts +2 -2
- package/dist/UI/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/UI/OnboardingPage.tsx +3 -1
- package/src/UI/Pages/Carousel/Renderer.tsx +4 -1
- package/src/UI/Pages/Carousel/types.ts +2 -1
- package/src/UI/Pages/Commitment/Renderer.tsx +2 -1
- package/src/UI/Pages/Commitment/types.ts +2 -1
- package/src/UI/Pages/ComposableScreen/Renderer.tsx +146 -0
- package/src/UI/Pages/ComposableScreen/index.ts +2 -0
- package/src/UI/Pages/ComposableScreen/types.ts +145 -0
- package/src/UI/Pages/Loader/Renderer.tsx +6 -1
- package/src/UI/Pages/Loader/types.ts +2 -1
- package/src/UI/Pages/MediaContent/Renderer.tsx +4 -1
- package/src/UI/Pages/MediaContent/types.ts +2 -0
- package/src/UI/Pages/Picker/Renderer.tsx +6 -5
- package/src/UI/Pages/Picker/types.ts +2 -1
- package/src/UI/Pages/Question/Renderer.tsx +4 -1
- package/src/UI/Pages/Question/types.ts +3 -1
- package/src/UI/Pages/Ratings/Renderer.tsx +2 -1
- package/src/UI/Pages/Ratings/types.ts +2 -1
- package/src/UI/Pages/index.ts +1 -0
- package/src/UI/Pages/types.ts +5 -0
- package/src/UI/Templates/OnboardingTemplate.tsx +73 -10
- package/src/UI/types.ts +2 -0
- package/dist/provider/CustomComponentsContext.d.ts +0 -57
- package/dist/provider/CustomComponentsContext.d.ts.map +0 -1
- package/dist/provider/CustomComponentsContext.js +0 -19
- package/dist/provider/CustomComponentsContext.js.map +0 -1
- package/dist/provider/OnboardingUIProvider.d.ts +0 -44
- package/dist/provider/OnboardingUIProvider.d.ts.map +0 -1
- package/dist/provider/OnboardingUIProvider.js +0 -33
- package/dist/provider/OnboardingUIProvider.js.map +0 -1
- package/dist/provider/OnboardingUIProvider.old.d.ts +0 -60
- package/dist/provider/OnboardingUIProvider.old.d.ts.map +0 -1
- package/dist/provider/OnboardingUIProvider.old.js +0 -53
- 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,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
|
|
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={{
|
|
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 ? {
|
|
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
|
|
package/src/UI/Pages/index.ts
CHANGED
package/src/UI/Pages/types.ts
CHANGED
|
@@ -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
|
-
|
|
53
|
-
style={
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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"}
|