@rocapine/react-native-onboarding-ui 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/UI/OnboardingPage.js +1 -1
  2. package/dist/UI/OnboardingPage.js.map +1 -1
  3. package/dist/UI/Pages/ComposableScreen/Renderer.d.ts +0 -2
  4. package/dist/UI/Pages/ComposableScreen/Renderer.d.ts.map +1 -1
  5. package/dist/UI/Pages/ComposableScreen/Renderer.js +13 -274
  6. package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
  7. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +39 -0
  8. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -0
  9. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +20 -0
  10. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -0
  11. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +67 -0
  12. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -0
  13. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js +65 -0
  14. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js.map +1 -0
  15. package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +49 -0
  16. package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -0
  17. package/dist/UI/Pages/ComposableScreen/elements/IconElement.js +37 -0
  18. package/dist/UI/Pages/ComposableScreen/elements/IconElement.js.map +1 -0
  19. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +50 -0
  20. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -0
  21. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.js +34 -0
  22. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.js.map +1 -0
  23. package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +110 -0
  24. package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -0
  25. package/dist/UI/Pages/ComposableScreen/elements/InputElement.js +66 -0
  26. package/dist/UI/Pages/ComposableScreen/elements/InputElement.js.map +1 -0
  27. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +47 -0
  28. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -0
  29. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.js +60 -0
  30. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.js.map +1 -0
  31. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +86 -0
  32. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -0
  33. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.js +120 -0
  34. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.js.map +1 -0
  35. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +70 -0
  36. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -0
  37. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.js +68 -0
  38. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.js.map +1 -0
  39. package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +94 -0
  40. package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -0
  41. package/dist/UI/Pages/ComposableScreen/elements/StackElement.js +66 -0
  42. package/dist/UI/Pages/ComposableScreen/elements/StackElement.js.map +1 -0
  43. package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +66 -0
  44. package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -0
  45. package/dist/UI/Pages/ComposableScreen/elements/TextElement.js +59 -0
  46. package/dist/UI/Pages/ComposableScreen/elements/TextElement.js.map +1 -0
  47. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +49 -0
  48. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -0
  49. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.js +84 -0
  50. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.js.map +1 -0
  51. package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts +5 -0
  52. package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -0
  53. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +49 -0
  54. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -0
  55. package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts +13 -0
  56. package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts.map +1 -0
  57. package/dist/UI/Pages/ComposableScreen/elements/shared.js +6 -0
  58. package/dist/UI/Pages/ComposableScreen/elements/shared.js.map +1 -0
  59. package/dist/UI/Pages/ComposableScreen/types.d.ts +40 -114
  60. package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
  61. package/dist/UI/Pages/ComposableScreen/types.js +33 -122
  62. package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
  63. package/dist/UI/Provider/OnboardingProgressProvider.d.ts +6 -2
  64. package/dist/UI/Provider/OnboardingProgressProvider.d.ts.map +1 -1
  65. package/dist/UI/Provider/OnboardingProgressProvider.js +4 -3
  66. package/dist/UI/Provider/OnboardingProgressProvider.js.map +1 -1
  67. package/package.json +2 -2
  68. package/src/UI/OnboardingPage.tsx +1 -1
  69. package/src/UI/Pages/ComposableScreen/Renderer.tsx +22 -431
  70. package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +33 -0
  71. package/src/UI/Pages/ComposableScreen/elements/ButtonElement.tsx +96 -0
  72. package/src/UI/Pages/ComposableScreen/elements/IconElement.tsx +67 -0
  73. package/src/UI/Pages/ComposableScreen/elements/ImageElement.tsx +52 -0
  74. package/src/UI/Pages/ComposableScreen/elements/InputElement.tsx +109 -0
  75. package/src/UI/Pages/ComposableScreen/elements/LottieElement.tsx +97 -0
  76. package/src/UI/Pages/ComposableScreen/elements/RadioGroupElement.tsx +182 -0
  77. package/src/UI/Pages/ComposableScreen/elements/RiveElement.tsx +105 -0
  78. package/src/UI/Pages/ComposableScreen/elements/StackElement.tsx +106 -0
  79. package/src/UI/Pages/ComposableScreen/elements/TextElement.tsx +95 -0
  80. package/src/UI/Pages/ComposableScreen/elements/VideoElement.tsx +113 -0
  81. package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +61 -0
  82. package/src/UI/Pages/ComposableScreen/elements/shared.ts +15 -0
  83. package/src/UI/Pages/ComposableScreen/types.ts +56 -235
  84. package/src/UI/Provider/OnboardingProgressProvider.tsx +8 -5
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import { z } from "zod";
3
+ import { Text } from "react-native";
4
+ import { UIElement } from "../types";
5
+ import { RenderContext, interpolate } from "./shared";
6
+
7
+ export type TextElementProps = {
8
+ content: string;
9
+ mode?: "plain" | "expression";
10
+ fontSize?: number;
11
+ fontWeight?: string;
12
+ fontFamily?: string;
13
+ color?: string;
14
+ textAlign?: "left" | "center" | "right";
15
+ letterSpacing?: number;
16
+ lineHeight?: number;
17
+ backgroundColor?: string;
18
+ padding?: number;
19
+ paddingHorizontal?: number;
20
+ paddingVertical?: number;
21
+ margin?: number;
22
+ marginHorizontal?: number;
23
+ marginVertical?: number;
24
+ borderWidth?: number;
25
+ borderRadius?: number;
26
+ borderColor?: string;
27
+ opacity?: number;
28
+ };
29
+
30
+ export const TextElementPropsSchema = z.object({
31
+ content: z.string(),
32
+ mode: z.enum(["plain", "expression"]).optional(),
33
+ fontSize: z.number().optional(),
34
+ fontWeight: z.string().optional(),
35
+ fontFamily: z.string().optional(),
36
+ color: z.string().optional(),
37
+ textAlign: z.enum(["left", "center", "right"]).optional(),
38
+ letterSpacing: z.number().optional(),
39
+ lineHeight: z.number().optional(),
40
+ backgroundColor: z.string().optional(),
41
+ padding: z.number().optional(),
42
+ paddingHorizontal: z.number().optional(),
43
+ paddingVertical: z.number().optional(),
44
+ margin: z.number().optional(),
45
+ marginHorizontal: z.number().optional(),
46
+ marginVertical: z.number().optional(),
47
+ borderWidth: z.number().optional(),
48
+ borderRadius: z.number().optional(),
49
+ borderColor: z.string().optional(),
50
+ opacity: z.number().min(0).max(1).optional(),
51
+ });
52
+
53
+ type TextUIElement = Extract<UIElement, { type: "Text" }>;
54
+
55
+ type Props = {
56
+ element: TextUIElement;
57
+ ctx: RenderContext;
58
+ parentType?: "XStack" | "YStack";
59
+ };
60
+
61
+ export const TextElementComponent = ({ element, ctx, parentType }: Props): React.ReactElement => {
62
+ const { theme, variables } = ctx;
63
+ const content =
64
+ element.props.mode === "expression"
65
+ ? interpolate(element.props.content, variables)
66
+ : element.props.content;
67
+
68
+ return (
69
+ <Text
70
+ style={{
71
+ fontSize: element.props.fontSize,
72
+ fontWeight: element.props.fontWeight as any,
73
+ fontFamily: element.props.fontFamily,
74
+ color: element.props.color ?? theme.colors.text.primary,
75
+ textAlign: element.props.textAlign,
76
+ letterSpacing: element.props.letterSpacing,
77
+ lineHeight: element.props.lineHeight,
78
+ backgroundColor: element.props.backgroundColor,
79
+ padding: element.props.padding,
80
+ paddingHorizontal: element.props.paddingHorizontal,
81
+ paddingVertical: element.props.paddingVertical,
82
+ margin: element.props.margin,
83
+ marginHorizontal: element.props.marginHorizontal,
84
+ marginVertical: element.props.marginVertical,
85
+ borderWidth: element.props.borderWidth,
86
+ borderRadius: element.props.borderRadius,
87
+ borderColor: element.props.borderColor,
88
+ opacity: element.props.opacity,
89
+ flexShrink: parentType === "XStack" ? 1 : undefined,
90
+ }}
91
+ >
92
+ {content}
93
+ </Text>
94
+ );
95
+ };
@@ -0,0 +1,113 @@
1
+ import React, { useEffect } from "react";
2
+ import { z } from "zod";
3
+ import { View, Text, StyleSheet } from "react-native";
4
+ import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
5
+ import { UIElement } from "../types";
6
+ import { RenderContext } from "./shared";
7
+ import { getTextStyle } from "../../../Theme/helpers";
8
+
9
+ export type VideoElementProps = BaseBoxProps & {
10
+ url: string;
11
+ autoPlay?: boolean;
12
+ loop?: boolean;
13
+ muted?: boolean;
14
+ controls?: boolean;
15
+ };
16
+
17
+ export const VideoElementPropsSchema = BaseBoxPropsSchema.extend({
18
+ url: z.string().min(1, "url must not be empty"),
19
+ autoPlay: z.boolean().optional(),
20
+ loop: z.boolean().optional(),
21
+ muted: z.boolean().optional(),
22
+ controls: z.boolean().optional(),
23
+ });
24
+
25
+ type VideoUIElement = Extract<UIElement, { type: "Video" }>;
26
+
27
+ let VideoElementComponent: React.ComponentType<{ element: VideoUIElement; style: object }> | null = null;
28
+ try {
29
+ const { VideoView, useVideoPlayer } = require("expo-video");
30
+ VideoElementComponent = ({ element, style }: { element: VideoUIElement; style: object }) => {
31
+ const player = useVideoPlayer(element.props.url, (p: any) => {
32
+ p.loop = element.props.loop ?? false;
33
+ p.muted = element.props.muted ?? true;
34
+ if (element.props.autoPlay) p.play();
35
+ });
36
+
37
+ useEffect(() => {
38
+ player.loop = element.props.loop ?? false;
39
+ player.muted = element.props.muted ?? true;
40
+ if (element.props.autoPlay) {
41
+ player.play();
42
+ } else {
43
+ player.pause();
44
+ }
45
+ }, [element.props.loop, element.props.muted, element.props.autoPlay]);
46
+
47
+ return (
48
+ <VideoView
49
+ player={player}
50
+ style={style}
51
+ allowsFullscreen={false}
52
+ nativeControls={element.props.controls ?? false}
53
+ />
54
+ );
55
+ };
56
+ } catch {
57
+ // expo-video not installed
58
+ }
59
+
60
+ type Props = {
61
+ element: VideoUIElement;
62
+ ctx: RenderContext;
63
+ };
64
+
65
+ export const VideoElementRenderer = ({ element, ctx }: Props): React.ReactElement => {
66
+ const { theme } = ctx;
67
+ const wrapperStyle = {
68
+ width: element.props.width ?? ("100%" as `${number}%`),
69
+ height: element.props.height ?? 200,
70
+ opacity: element.props.opacity,
71
+ margin: element.props.margin,
72
+ marginHorizontal: element.props.marginHorizontal,
73
+ marginVertical: element.props.marginVertical,
74
+ padding: element.props.padding,
75
+ paddingHorizontal: element.props.paddingHorizontal,
76
+ paddingVertical: element.props.paddingVertical,
77
+ borderWidth: element.props.borderWidth,
78
+ borderRadius: element.props.borderRadius,
79
+ borderColor: element.props.borderColor,
80
+ overflow: "hidden" as const,
81
+ };
82
+
83
+ if (!VideoElementComponent) {
84
+ return (
85
+ <View style={[wrapperStyle, styles.mediaFallback, { backgroundColor: theme.colors.neutral.lowest }]}>
86
+ <Text style={[styles.mediaFallbackText, getTextStyle(theme, "caption"), { color: theme.colors.text.tertiary }]}>
87
+ Install expo-video to render videos.
88
+ </Text>
89
+ </View>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <View style={wrapperStyle}>
95
+ <VideoElementComponent element={element} style={styles.fill} />
96
+ </View>
97
+ );
98
+ };
99
+
100
+ const styles = StyleSheet.create({
101
+ fill: {
102
+ width: "100%",
103
+ height: "100%",
104
+ },
105
+ mediaFallback: {
106
+ alignItems: "center",
107
+ justifyContent: "center",
108
+ },
109
+ mediaFallbackText: {
110
+ textAlign: "center",
111
+ paddingHorizontal: 16,
112
+ },
113
+ });
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import { UIElement } from "../types";
3
+ import { RenderContext } from "./shared";
4
+ import { StackElementComponent } from "./StackElement";
5
+ import { TextElementComponent } from "./TextElement";
6
+ import { ImageElementComponent } from "./ImageElement";
7
+ import { LottieElementComponent } from "./LottieElement";
8
+ import { RiveElementRenderer } from "./RiveElement";
9
+ import { IconElementComponent } from "./IconElement";
10
+ import { VideoElementRenderer } from "./VideoElement";
11
+ import { InputElementComponent } from "./InputElement";
12
+ import { RadioGroupComponent } from "./RadioGroupElement";
13
+ import { ButtonElementComponent } from "./ButtonElement";
14
+
15
+ export const renderElement = (
16
+ element: UIElement,
17
+ ctx: RenderContext,
18
+ parentType?: "XStack" | "YStack"
19
+ ): React.ReactNode => {
20
+ if (element.type === "YStack" || element.type === "XStack") {
21
+ return <StackElementComponent key={element.id} element={element} ctx={ctx} parentType={parentType} />;
22
+ }
23
+
24
+ if (element.type === "Text") {
25
+ return <TextElementComponent key={element.id} element={element} ctx={ctx} parentType={parentType} />;
26
+ }
27
+
28
+ if (element.type === "Image") {
29
+ return <ImageElementComponent key={element.id} element={element} ctx={ctx} />;
30
+ }
31
+
32
+ if (element.type === "Lottie") {
33
+ return <LottieElementComponent key={element.id} element={element} ctx={ctx} />;
34
+ }
35
+
36
+ if (element.type === "Rive") {
37
+ return <RiveElementRenderer key={element.id} element={element} ctx={ctx} />;
38
+ }
39
+
40
+ if (element.type === "Icon") {
41
+ return <IconElementComponent key={element.id} element={element} ctx={ctx} />;
42
+ }
43
+
44
+ if (element.type === "Video") {
45
+ return <VideoElementRenderer key={element.id} element={element} ctx={ctx} />;
46
+ }
47
+
48
+ if (element.type === "Input") {
49
+ return <InputElementComponent key={element.id} element={element} ctx={ctx} />;
50
+ }
51
+
52
+ if (element.type === "RadioGroup") {
53
+ return <RadioGroupComponent key={element.id} element={element} ctx={ctx} />;
54
+ }
55
+
56
+ if (element.type === "Button") {
57
+ return <ButtonElementComponent key={element.id} element={element} ctx={ctx} />;
58
+ }
59
+
60
+ return null;
61
+ };
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { UIElement } from "../types";
3
+ import { Theme } from "../../../Theme/types";
4
+ import { ComposableVariableEntry } from "../../../Provider/OnboardingProgressProvider";
5
+
6
+ export type RenderContext = {
7
+ theme: Theme;
8
+ variables: Record<string, ComposableVariableEntry>;
9
+ setVariable: (key: string, entry: ComposableVariableEntry) => void;
10
+ onContinue: () => void;
11
+ renderChildren: (elements: UIElement[], parentType: "XStack" | "YStack") => React.ReactNode;
12
+ };
13
+
14
+ export const interpolate = (template: string, variables: Record<string, ComposableVariableEntry>): string =>
15
+ template.replace(/\{\{([^}]+?)\}\}/g, (_, key) => variables[key]?.label ?? variables[key]?.value ?? "");
@@ -1,285 +1,94 @@
1
1
  import { z } from "zod";
2
2
  import { CustomPayloadSchema } from "../types";
3
+ import { type StackElementProps, StackElementPropsSchema } from "./elements/StackElement";
4
+ import { type TextElementProps, TextElementPropsSchema } from "./elements/TextElement";
5
+ import { type ImageElementProps, ImageElementPropsSchema } from "./elements/ImageElement";
6
+ import { type LottieElementProps, LottieElementPropsSchema } from "./elements/LottieElement";
7
+ import { type RiveElementProps, RiveElementPropsSchema } from "./elements/RiveElement";
8
+ import { type IconElementProps, IconElementPropsSchema } from "./elements/IconElement";
9
+ import { type VideoElementProps, VideoElementPropsSchema } from "./elements/VideoElement";
10
+ import { type InputElementProps, InputElementPropsSchema } from "./elements/InputElement";
11
+ import { type ButtonElementProps, ButtonElementPropsSchema } from "./elements/ButtonElement";
12
+ import { type RadioGroupElementProps, RadioGroupElementPropsSchema } from "./elements/RadioGroupElement";
3
13
 
4
- type BaseBoxProps = {
5
- width?: number;
6
- height?: number;
7
- opacity?: number;
8
- margin?: number;
9
- marginHorizontal?: number;
10
- marginVertical?: number;
11
- padding?: number;
12
- paddingHorizontal?: number;
13
- paddingVertical?: number;
14
- borderWidth?: number;
15
- borderRadius?: number;
16
- borderColor?: string;
17
- };
14
+ export type { BaseBoxProps } from "./elements/BaseBoxProps";
15
+ export { BaseBoxPropsSchema } from "./elements/BaseBoxProps";
16
+ export type { StackElementProps } from "./elements/StackElement";
17
+ export type { TextElementProps } from "./elements/TextElement";
18
+ export type { ImageElementProps } from "./elements/ImageElement";
19
+ export type { LottieElementProps } from "./elements/LottieElement";
20
+ export type { RiveElementProps } from "./elements/RiveElement";
21
+ export type { IconElementProps } from "./elements/IconElement";
22
+ export type { VideoElementProps } from "./elements/VideoElement";
23
+ export type { InputElementProps } from "./elements/InputElement";
24
+ export type { ButtonElementProps } from "./elements/ButtonElement";
25
+ export type { RadioGroupElementProps } from "./elements/RadioGroupElement";
18
26
 
27
+ // UIElement union — must live here (not in elements/) to avoid circular deps
28
+ // because the Stack variant's children: UIElement[] references itself.
19
29
  export type UIElement =
20
30
  | {
21
31
  id: string;
22
32
  name?: string;
23
33
  type: "YStack" | "XStack";
24
- props: {
25
- gap?: number;
26
- padding?: number;
27
- paddingHorizontal?: number;
28
- paddingVertical?: number;
29
- margin?: number;
30
- marginHorizontal?: number;
31
- marginVertical?: number;
32
- flex?: number;
33
- width?: number;
34
- height?: number;
35
- minWidth?: number;
36
- maxWidth?: number;
37
- minHeight?: number;
38
- maxHeight?: number;
39
- alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
40
- justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-around";
41
- backgroundColor?: string;
42
- flexWrap?: "wrap" | "nowrap";
43
- flexShrink?: number;
44
- borderWidth?: number;
45
- borderRadius?: number;
46
- borderColor?: string;
47
- overflow?: "hidden" | "visible" | "scroll";
48
- opacity?: number;
49
- };
34
+ props: StackElementProps;
50
35
  children: UIElement[];
51
36
  }
52
37
  | {
53
38
  id: string;
54
39
  name?: string;
55
40
  type: "Text";
56
- props: {
57
- content: string;
58
- mode?: "plain" | "expression";
59
- fontSize?: number;
60
- fontWeight?: string;
61
- fontFamily?: string;
62
- color?: string;
63
- textAlign?: "left" | "center" | "right";
64
- letterSpacing?: number;
65
- lineHeight?: number;
66
- backgroundColor?: string;
67
- padding?: number;
68
- paddingHorizontal?: number;
69
- paddingVertical?: number;
70
- margin?: number;
71
- marginHorizontal?: number;
72
- marginVertical?: number;
73
- borderWidth?: number;
74
- borderRadius?: number;
75
- borderColor?: string;
76
- opacity?: number;
77
- };
41
+ props: TextElementProps;
78
42
  }
79
43
  | {
80
44
  id: string;
81
45
  name?: string;
82
46
  type: "Image";
83
- props: BaseBoxProps & {
84
- url: string;
85
- aspectRatio?: number;
86
- resizeMode?: "cover" | "contain" | "stretch" | "center";
87
- };
47
+ props: ImageElementProps;
88
48
  }
89
49
  | {
90
50
  id: string;
91
51
  name?: string;
92
52
  type: "Lottie";
93
- props: BaseBoxProps & {
94
- source: string;
95
- autoPlay?: boolean;
96
- loop?: boolean;
97
- speed?: number;
98
- };
53
+ props: LottieElementProps;
99
54
  }
100
55
  | {
101
56
  id: string;
102
57
  name?: string;
103
58
  type: "Rive";
104
- props: BaseBoxProps & {
105
- url: string;
106
- autoplay?: boolean;
107
- fit?: "Contain" | "Cover" | "Fill" | "FitWidth" | "FitHeight" | "None" | "ScaleDown" | "Layout";
108
- alignment?: "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
109
- artboardName?: string;
110
- stateMachineName?: string;
111
- };
59
+ props: RiveElementProps;
112
60
  }
113
61
  | {
114
62
  id: string;
115
63
  name?: string;
116
64
  type: "Icon";
117
- props: BaseBoxProps & {
118
- name: string;
119
- size?: number;
120
- color?: string;
121
- strokeWidth?: number;
122
- };
65
+ props: IconElementProps;
123
66
  }
124
67
  | {
125
68
  id: string;
126
69
  name?: string;
127
70
  type: "Video";
128
- props: BaseBoxProps & {
129
- url: string;
130
- autoPlay?: boolean;
131
- loop?: boolean;
132
- muted?: boolean;
133
- controls?: boolean;
134
- };
71
+ props: VideoElementProps;
135
72
  }
136
73
  | {
137
74
  id: string;
138
75
  name?: string;
139
76
  type: "Input";
140
- props: BaseBoxProps & {
141
- variableName?: string;
142
- placeholder?: string;
143
- defaultValue?: string;
144
- keyboardType?: "default" | "email-address" | "numeric" | "phone-pad" | "decimal-pad" | "url" | "number-pad" | "ascii-capable" | "numbers-and-punctuation" | "name-phone-pad" | "twitter" | "web-search" | "visible-password";
145
- returnKeyType?: "done" | "next" | "go" | "search" | "send" | "default" | "emergency-call" | "google" | "join" | "route" | "yahoo" | "none" | "previous";
146
- autoCapitalize?: "none" | "sentences" | "words" | "characters";
147
- secureTextEntry?: boolean;
148
- maxLength?: number;
149
- multiline?: boolean;
150
- numberOfLines?: number;
151
- editable?: boolean;
152
- color?: string;
153
- backgroundColor?: string;
154
- fontSize?: number;
155
- fontWeight?: string;
156
- textAlign?: "left" | "center" | "right";
157
- placeholderColor?: string;
158
- };
77
+ props: InputElementProps;
78
+ }
79
+ | {
80
+ id: string;
81
+ name?: string;
82
+ type: "Button";
83
+ props: ButtonElementProps;
84
+ }
85
+ | {
86
+ id: string;
87
+ name?: string;
88
+ type: "RadioGroup";
89
+ props: RadioGroupElementProps;
159
90
  };
160
91
 
161
- const BaseBoxPropsSchema = z.object({
162
- width: z.number().optional(),
163
- height: z.number().optional(),
164
- opacity: z.number().min(0).max(1).optional(),
165
- margin: z.number().optional(),
166
- marginHorizontal: z.number().optional(),
167
- marginVertical: z.number().optional(),
168
- padding: z.number().optional(),
169
- paddingHorizontal: z.number().optional(),
170
- paddingVertical: z.number().optional(),
171
- borderWidth: z.number().optional(),
172
- borderRadius: z.number().optional(),
173
- borderColor: z.string().optional(),
174
- });
175
-
176
- const StackElementPropsSchema = z.object({
177
- gap: z.number().optional(),
178
- padding: z.number().optional(),
179
- paddingHorizontal: z.number().optional(),
180
- paddingVertical: z.number().optional(),
181
- margin: z.number().optional(),
182
- marginHorizontal: z.number().optional(),
183
- marginVertical: z.number().optional(),
184
- flex: z.number().optional(),
185
- width: z.number().optional(),
186
- height: z.number().optional(),
187
- minWidth: z.number().optional(),
188
- maxWidth: z.number().optional(),
189
- minHeight: z.number().optional(),
190
- maxHeight: z.number().optional(),
191
- alignItems: z.enum(["flex-start", "center", "flex-end", "stretch"]).optional(),
192
- justifyContent: z.enum(["flex-start", "center", "flex-end", "space-between", "space-around"]).optional(),
193
- backgroundColor: z.string().optional(),
194
- flexWrap: z.enum(["wrap", "nowrap"]).optional(),
195
- flexShrink: z.number().optional(),
196
- borderWidth: z.number().optional(),
197
- borderRadius: z.number().optional(),
198
- borderColor: z.string().optional(),
199
- overflow: z.enum(["hidden", "visible", "scroll"]).optional(),
200
- opacity: z.number().min(0).max(1).optional(),
201
- });
202
-
203
- const TextElementPropsSchema = z.object({
204
- content: z.string(),
205
- mode: z.enum(["plain", "expression"]).optional(),
206
- fontSize: z.number().optional(),
207
- fontWeight: z.string().optional(),
208
- fontFamily: z.string().optional(),
209
- color: z.string().optional(),
210
- textAlign: z.enum(["left", "center", "right"]).optional(),
211
- letterSpacing: z.number().optional(),
212
- lineHeight: z.number().optional(),
213
- backgroundColor: z.string().optional(),
214
- padding: z.number().optional(),
215
- paddingHorizontal: z.number().optional(),
216
- paddingVertical: z.number().optional(),
217
- margin: z.number().optional(),
218
- marginHorizontal: z.number().optional(),
219
- marginVertical: z.number().optional(),
220
- borderWidth: z.number().optional(),
221
- borderRadius: z.number().optional(),
222
- borderColor: z.string().optional(),
223
- opacity: z.number().min(0).max(1).optional(),
224
- });
225
-
226
- const ImageElementPropsSchema = BaseBoxPropsSchema.extend({
227
- url: z.string(),
228
- aspectRatio: z.number().optional(),
229
- resizeMode: z.enum(["cover", "contain", "stretch", "center"]).optional(),
230
- });
231
-
232
- const LottieElementPropsSchema = BaseBoxPropsSchema.extend({
233
- source: z.string(),
234
- autoPlay: z.boolean().optional(),
235
- loop: z.boolean().optional(),
236
- speed: z.number().optional(),
237
- });
238
-
239
- const RiveElementPropsSchema = BaseBoxPropsSchema.extend({
240
- url: z.string(),
241
- autoplay: z.boolean().optional(),
242
- fit: z.enum(["Contain", "Cover", "Fill", "FitWidth", "FitHeight", "None", "ScaleDown", "Layout"]).optional(),
243
- alignment: z.enum(["TopLeft", "TopCenter", "TopRight", "CenterLeft", "Center", "CenterRight", "BottomLeft", "BottomCenter", "BottomRight"]).optional(),
244
- artboardName: z.string().optional(),
245
- stateMachineName: z.string().optional(),
246
- });
247
-
248
- const IconElementPropsSchema = BaseBoxPropsSchema.extend({
249
- name: z.string(),
250
- size: z.number().optional(),
251
- color: z.string().optional(),
252
- strokeWidth: z.number().optional(),
253
- });
254
-
255
- const VideoElementPropsSchema = BaseBoxPropsSchema.extend({
256
- url: z.string(),
257
- autoPlay: z.boolean().optional(),
258
- loop: z.boolean().optional(),
259
- muted: z.boolean().optional(),
260
- controls: z.boolean().optional(),
261
- });
262
-
263
- const InputElementPropsSchema = BaseBoxPropsSchema.extend({
264
- variableName: z.string().optional(),
265
- placeholder: z.string().optional(),
266
- defaultValue: z.string().optional(),
267
- keyboardType: z.enum(["default", "email-address", "numeric", "phone-pad", "decimal-pad", "url", "number-pad", "ascii-capable", "numbers-and-punctuation", "name-phone-pad", "twitter", "web-search", "visible-password"]).optional(),
268
- returnKeyType: z.enum(["done", "next", "go", "search", "send", "default", "emergency-call", "google", "join", "route", "yahoo", "none", "previous"]).optional(),
269
- autoCapitalize: z.enum(["none", "sentences", "words", "characters"]).optional(),
270
- secureTextEntry: z.boolean().optional(),
271
- maxLength: z.number().int().nonnegative().optional(),
272
- multiline: z.boolean().optional(),
273
- numberOfLines: z.number().int().nonnegative().optional(),
274
- editable: z.boolean().optional(),
275
- color: z.string().optional(),
276
- backgroundColor: z.string().optional(),
277
- fontSize: z.number().optional(),
278
- fontWeight: z.string().optional(),
279
- textAlign: z.enum(["left", "center", "right"]).optional(),
280
- placeholderColor: z.string().optional(),
281
- });
282
-
283
92
  export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
284
93
  z.union([
285
94
  z.object({
@@ -331,6 +140,18 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
331
140
  type: z.literal("Input"),
332
141
  props: InputElementPropsSchema,
333
142
  }),
143
+ z.object({
144
+ id: z.string(),
145
+ name: z.string().optional(),
146
+ type: z.literal("Button"),
147
+ props: ButtonElementPropsSchema,
148
+ }),
149
+ z.object({
150
+ id: z.string(),
151
+ name: z.string().optional(),
152
+ type: z.literal("RadioGroup"),
153
+ props: RadioGroupElementPropsSchema,
154
+ }),
334
155
  ])
335
156
  );
336
157
 
@@ -15,10 +15,11 @@ export const OnboardingProgressProvider = ({
15
15
  displayProgressHeader: false,
16
16
  });
17
17
  const [totalSteps, setTotalSteps] = useState(0);
18
- const [composableVariables, setComposableVariables] = useState<Record<string, string>>({});
18
+ const [composableVariables, setComposableVariables] = useState<Record<string, ComposableVariableEntry>>({});
19
19
 
20
- const setComposableVariable = useCallback((key: string, value: string) => {
21
- setComposableVariables((prev) => ({ ...prev, [key]: value }));
20
+ const setComposableVariable = useCallback((key: string, entry: ComposableVariableEntry | string) => {
21
+ const normalizedEntry: ComposableVariableEntry = typeof entry === "string" ? { value: entry } : entry;
22
+ setComposableVariables((prev) => ({ ...prev, [key]: normalizedEntry }));
22
23
  }, []);
23
24
 
24
25
  return (
@@ -34,6 +35,8 @@ export const OnboardingProgressProvider = ({
34
35
  );
35
36
  };
36
37
 
38
+ export type ComposableVariableEntry = { value: string; label?: string };
39
+
37
40
  export const OnboardingProgressContext = createContext({
38
41
  activeStep: { number: 0, displayProgressHeader: false },
39
42
  setActiveStep: (_step: {
@@ -42,6 +45,6 @@ export const OnboardingProgressContext = createContext({
42
45
  }) => { },
43
46
  totalSteps: 0,
44
47
  setTotalSteps: (_steps: number) => { },
45
- composableVariables: {} as Record<string, string>,
46
- setComposableVariable: (_key: string, _value: string) => { },
48
+ composableVariables: {} as Record<string, ComposableVariableEntry>,
49
+ setComposableVariable: (_key: string, _entry: ComposableVariableEntry | string) => { },
47
50
  });