@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,52 @@
1
+ import React from "react";
2
+ import { z } from "zod";
3
+ import { Image } from "react-native";
4
+ import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
5
+ import { UIElement } from "../types";
6
+ import { RenderContext } from "./shared";
7
+
8
+ export type ImageElementProps = BaseBoxProps & {
9
+ url: string;
10
+ aspectRatio?: number;
11
+ resizeMode?: "cover" | "contain" | "stretch" | "center";
12
+ };
13
+
14
+ export const ImageElementPropsSchema = BaseBoxPropsSchema.extend({
15
+ url: z.string().min(1, "url must not be empty"),
16
+ aspectRatio: z.number().optional(),
17
+ resizeMode: z.enum(["cover", "contain", "stretch", "center"]).optional(),
18
+ });
19
+
20
+ type ImageUIElement = Extract<UIElement, { type: "Image" }>;
21
+
22
+ type Props = {
23
+ element: ImageUIElement;
24
+ ctx: RenderContext;
25
+ };
26
+
27
+ export const ImageElementComponent = ({ element }: Props): React.ReactElement => {
28
+ const hasExplicitHeight = element.props.height !== undefined;
29
+ const aspectRatio = hasExplicitHeight ? undefined : (element.props.aspectRatio ?? 16 / 9);
30
+
31
+ return (
32
+ <Image
33
+ source={{ uri: element.props.url }}
34
+ resizeMode={element.props.resizeMode ?? "cover"}
35
+ style={{
36
+ width: element.props.width ?? "100%",
37
+ height: element.props.height,
38
+ aspectRatio,
39
+ borderRadius: element.props.borderRadius,
40
+ borderWidth: element.props.borderWidth,
41
+ borderColor: element.props.borderColor,
42
+ opacity: element.props.opacity,
43
+ margin: element.props.margin,
44
+ marginHorizontal: element.props.marginHorizontal,
45
+ marginVertical: element.props.marginVertical,
46
+ padding: element.props.padding,
47
+ paddingHorizontal: element.props.paddingHorizontal,
48
+ paddingVertical: element.props.paddingVertical,
49
+ }}
50
+ />
51
+ );
52
+ };
@@ -0,0 +1,109 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { z } from "zod";
3
+ import { View, TextInput } from "react-native";
4
+ import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
5
+ import { UIElement } from "../types";
6
+ import { RenderContext } from "./shared";
7
+
8
+ export type InputElementProps = BaseBoxProps & {
9
+ variableName?: string;
10
+ placeholder?: string;
11
+ defaultValue?: string;
12
+ 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";
13
+ returnKeyType?: "done" | "next" | "go" | "search" | "send" | "default" | "emergency-call" | "google" | "join" | "route" | "yahoo" | "none" | "previous";
14
+ autoCapitalize?: "none" | "sentences" | "words" | "characters";
15
+ secureTextEntry?: boolean;
16
+ maxLength?: number;
17
+ multiline?: boolean;
18
+ numberOfLines?: number;
19
+ editable?: boolean;
20
+ color?: string;
21
+ backgroundColor?: string;
22
+ fontSize?: number;
23
+ fontWeight?: string;
24
+ textAlign?: "left" | "center" | "right";
25
+ placeholderColor?: string;
26
+ };
27
+
28
+ export const InputElementPropsSchema = BaseBoxPropsSchema.extend({
29
+ variableName: z.string().min(1).optional(),
30
+ placeholder: z.string().optional(),
31
+ defaultValue: z.string().optional(),
32
+ 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(),
33
+ returnKeyType: z.enum(["done", "next", "go", "search", "send", "default", "emergency-call", "google", "join", "route", "yahoo", "none", "previous"]).optional(),
34
+ autoCapitalize: z.enum(["none", "sentences", "words", "characters"]).optional(),
35
+ secureTextEntry: z.boolean().optional(),
36
+ maxLength: z.number().int().nonnegative().optional(),
37
+ multiline: z.boolean().optional(),
38
+ numberOfLines: z.number().int().nonnegative().optional(),
39
+ editable: z.boolean().optional(),
40
+ color: z.string().optional(),
41
+ backgroundColor: z.string().optional(),
42
+ fontSize: z.number().optional(),
43
+ fontWeight: z.string().optional(),
44
+ textAlign: z.enum(["left", "center", "right"]).optional(),
45
+ placeholderColor: z.string().optional(),
46
+ });
47
+
48
+ type InputUIElement = Extract<UIElement, { type: "Input" }>;
49
+
50
+ type Props = {
51
+ element: InputUIElement;
52
+ ctx: RenderContext;
53
+ };
54
+
55
+ export const InputElementComponent = ({ element, ctx }: Props): React.ReactElement => {
56
+ const { theme, variables, setVariable } = ctx;
57
+ const persistedValue = element.props.variableName ? variables[element.props.variableName]?.value : undefined;
58
+ const [value, setValue] = useState(persistedValue ?? element.props.defaultValue ?? "");
59
+
60
+ useEffect(() => {
61
+ if (element.props.variableName && element.props.defaultValue !== undefined && persistedValue === undefined) {
62
+ setVariable(element.props.variableName, { value: element.props.defaultValue });
63
+ }
64
+ }, [element.props.variableName, element.props.defaultValue, persistedValue]);
65
+
66
+ const handleChange = (text: string) => {
67
+ setValue(text);
68
+ if (element.props.variableName) {
69
+ setVariable(element.props.variableName, { value: text });
70
+ }
71
+ };
72
+
73
+ return (
74
+ <TextInput
75
+ value={value}
76
+ onChangeText={handleChange}
77
+ placeholder={element.props.placeholder}
78
+ placeholderTextColor={element.props.placeholderColor ?? theme.colors.text.tertiary}
79
+ keyboardType={element.props.keyboardType ?? "default"}
80
+ returnKeyType={element.props.returnKeyType ?? "done"}
81
+ autoCapitalize={element.props.autoCapitalize ?? "sentences"}
82
+ secureTextEntry={element.props.secureTextEntry ?? false}
83
+ maxLength={element.props.maxLength}
84
+ multiline={element.props.multiline ?? false}
85
+ numberOfLines={element.props.numberOfLines}
86
+ editable={element.props.editable ?? true}
87
+ style={{
88
+ alignSelf: element.props.alignSelf,
89
+ width: element.props.width,
90
+ height: element.props.height,
91
+ opacity: element.props.opacity,
92
+ margin: element.props.margin,
93
+ marginHorizontal: element.props.marginHorizontal,
94
+ marginVertical: element.props.marginVertical,
95
+ backgroundColor: element.props.backgroundColor ?? theme.colors.neutral.lowest,
96
+ borderWidth: element.props.borderWidth ?? 1,
97
+ borderRadius: element.props.borderRadius ?? 8,
98
+ borderColor: element.props.borderColor ?? theme.colors.neutral.low,
99
+ color: element.props.color ?? theme.colors.text.primary,
100
+ fontSize: element.props.fontSize ?? theme.typography.textStyles.body.fontSize,
101
+ fontWeight: element.props.fontWeight as any,
102
+ textAlign: element.props.textAlign,
103
+ padding: element.props.padding ?? 12,
104
+ paddingHorizontal: element.props.paddingHorizontal,
105
+ paddingVertical: element.props.paddingVertical,
106
+ }}
107
+ />
108
+ );
109
+ };
@@ -0,0 +1,97 @@
1
+ import React 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 LottieElementProps = BaseBoxProps & {
10
+ source: string;
11
+ autoPlay?: boolean;
12
+ loop?: boolean;
13
+ speed?: number;
14
+ };
15
+
16
+ export const LottieElementPropsSchema = BaseBoxPropsSchema.extend({
17
+ source: z.string().min(1, "source must not be empty"),
18
+ autoPlay: z.boolean().optional(),
19
+ loop: z.boolean().optional(),
20
+ speed: z.number().optional(),
21
+ });
22
+
23
+ type LottieUIElement = Extract<UIElement, { type: "Lottie" }>;
24
+
25
+ let LottieView: React.ComponentType<{
26
+ source: string | object;
27
+ autoPlay?: boolean;
28
+ loop?: boolean;
29
+ speed?: number;
30
+ style?: object;
31
+ }> | null = null;
32
+ try {
33
+ LottieView = require("lottie-react-native").default;
34
+ } catch {
35
+ // lottie-react-native not installed
36
+ }
37
+
38
+ type Props = {
39
+ element: LottieUIElement;
40
+ ctx: RenderContext;
41
+ };
42
+
43
+ export const LottieElementComponent = ({ element, ctx }: Props): React.ReactElement => {
44
+ const { theme } = ctx;
45
+ const wrapperStyle = {
46
+ width: element.props.width ?? ("100%" as `${number}%`),
47
+ height: element.props.height ?? 200,
48
+ opacity: element.props.opacity,
49
+ margin: element.props.margin,
50
+ marginHorizontal: element.props.marginHorizontal,
51
+ marginVertical: element.props.marginVertical,
52
+ padding: element.props.padding,
53
+ paddingHorizontal: element.props.paddingHorizontal,
54
+ paddingVertical: element.props.paddingVertical,
55
+ borderWidth: element.props.borderWidth,
56
+ borderRadius: element.props.borderRadius,
57
+ borderColor: element.props.borderColor,
58
+ overflow: "hidden" as const,
59
+ };
60
+
61
+ if (!LottieView) {
62
+ return (
63
+ <View style={[wrapperStyle, styles.mediaFallback, { backgroundColor: theme.colors.neutral.lowest }]}>
64
+ <Text style={[styles.mediaFallbackText, getTextStyle(theme, "caption"), { color: theme.colors.text.tertiary }]}>
65
+ Install lottie-react-native to render Lottie animations.
66
+ </Text>
67
+ </View>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <View style={wrapperStyle}>
73
+ <LottieView
74
+ source={{ uri: element.props.source }}
75
+ autoPlay={element.props.autoPlay ?? true}
76
+ loop={element.props.loop ?? true}
77
+ speed={element.props.speed}
78
+ style={styles.fill}
79
+ />
80
+ </View>
81
+ );
82
+ };
83
+
84
+ const styles = StyleSheet.create({
85
+ fill: {
86
+ width: "100%",
87
+ height: "100%",
88
+ },
89
+ mediaFallback: {
90
+ alignItems: "center",
91
+ justifyContent: "center",
92
+ },
93
+ mediaFallbackText: {
94
+ textAlign: "center",
95
+ paddingHorizontal: 16,
96
+ },
97
+ });
@@ -0,0 +1,182 @@
1
+ import React, { useEffect } from "react";
2
+ import { z } from "zod";
3
+ import { View, Text, TouchableOpacity } from "react-native";
4
+ import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
5
+ import { UIElement } from "../types";
6
+ import { RenderContext } from "./shared";
7
+
8
+ export type RadioGroupElementProps = BaseBoxProps & {
9
+ variableName?: string;
10
+ defaultValue?: string;
11
+ gap?: number;
12
+ direction?: "vertical" | "horizontal";
13
+ items: Array<{ label: string; value: string }>;
14
+ itemBackgroundColor?: string;
15
+ itemSelectedBackgroundColor?: string;
16
+ itemBorderColor?: string;
17
+ itemSelectedBorderColor?: string;
18
+ itemBorderRadius?: number;
19
+ itemBorderWidth?: number;
20
+ itemColor?: string;
21
+ itemSelectedColor?: string;
22
+ itemFontSize?: number;
23
+ itemFontWeight?: string;
24
+ itemFontFamily?: string;
25
+ itemPadding?: number;
26
+ itemPaddingHorizontal?: number;
27
+ itemPaddingVertical?: number;
28
+ };
29
+
30
+ export const RadioGroupElementPropsSchema = BaseBoxPropsSchema.extend({
31
+ variableName: z.string().optional(),
32
+ defaultValue: z.string().optional(),
33
+ gap: z.number().optional(),
34
+ direction: z.enum(["vertical", "horizontal"]).optional(),
35
+ items: z.array(z.object({ label: z.string().trim().min(1, "item label must not be empty"), value: z.string().trim().min(1, "item value must not be empty") })).min(1, "items must not be empty"),
36
+ itemBackgroundColor: z.string().optional(),
37
+ itemSelectedBackgroundColor: z.string().optional(),
38
+ itemBorderColor: z.string().optional(),
39
+ itemSelectedBorderColor: z.string().optional(),
40
+ itemBorderRadius: z.number().optional(),
41
+ itemBorderWidth: z.number().optional(),
42
+ itemColor: z.string().optional(),
43
+ itemSelectedColor: z.string().optional(),
44
+ itemFontSize: z.number().optional(),
45
+ itemFontWeight: z.string().optional(),
46
+ itemFontFamily: z.string().optional(),
47
+ itemPadding: z.number().optional(),
48
+ itemPaddingHorizontal: z.number().optional(),
49
+ itemPaddingVertical: z.number().optional(),
50
+ }).superRefine((data, ctx) => {
51
+ const values = data.items.map((i) => i.value);
52
+ const unique = new Set(values);
53
+ if (unique.size !== values.length) {
54
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "item values must be unique", path: ["items"] });
55
+ }
56
+ if (data.defaultValue !== undefined && !unique.has(data.defaultValue)) {
57
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "defaultValue must match one of the item values", path: ["defaultValue"] });
58
+ }
59
+ });
60
+
61
+ type RadioGroupUIElement = Extract<UIElement, { type: "RadioGroup" }>;
62
+
63
+ type Props = {
64
+ element: RadioGroupUIElement;
65
+ ctx: RenderContext;
66
+ };
67
+
68
+ export const RadioGroupComponent = ({ element, ctx }: Props): React.ReactElement => {
69
+ const { theme, variables, setVariable } = ctx;
70
+ const selectedValue = element.props.variableName ? variables[element.props.variableName]?.value : undefined;
71
+
72
+ useEffect(() => {
73
+ if (element.props.variableName && element.props.defaultValue && selectedValue === undefined) {
74
+ const defaultItem = element.props.items.find((i) => i.value === element.props.defaultValue);
75
+ setVariable(element.props.variableName, { value: element.props.defaultValue, label: defaultItem?.label });
76
+ }
77
+ }, [element.props.variableName, element.props.defaultValue, element.props.items, selectedValue]);
78
+
79
+ const handleSelect = (value: string, label: string) => {
80
+ if (element.props.variableName) {
81
+ setVariable(element.props.variableName, { value, label });
82
+ }
83
+ };
84
+
85
+ const isHorizontal = element.props.direction === "horizontal";
86
+
87
+ return (
88
+ <View
89
+ accessibilityRole="radiogroup"
90
+ style={{
91
+ flexDirection: isHorizontal ? "row" : "column",
92
+ flexWrap: isHorizontal ? "wrap" : undefined,
93
+ alignSelf: element.props.alignSelf,
94
+ gap: element.props.gap ?? 8,
95
+ width: element.props.width,
96
+ height: element.props.height,
97
+ margin: element.props.margin,
98
+ marginHorizontal: element.props.marginHorizontal,
99
+ marginVertical: element.props.marginVertical,
100
+ padding: element.props.padding,
101
+ paddingHorizontal: element.props.paddingHorizontal,
102
+ paddingVertical: element.props.paddingVertical,
103
+ borderWidth: element.props.borderWidth,
104
+ borderRadius: element.props.borderRadius,
105
+ borderColor: element.props.borderColor,
106
+ opacity: element.props.opacity,
107
+ }}
108
+ >
109
+ {element.props.items.map((item) => {
110
+ const isSelected = selectedValue === item.value;
111
+ // Note: "+ "15"" appends hex alpha (≈8% opacity) assuming a 6-digit hex primary color.
112
+ // This works for standard hex colors but may produce unexpected results for other color formats.
113
+ const bgColor = isSelected
114
+ ? (element.props.itemSelectedBackgroundColor ?? theme.colors.primary + "15")
115
+ : (element.props.itemBackgroundColor ?? "transparent");
116
+ const textColor = isSelected
117
+ ? (element.props.itemSelectedColor ?? theme.colors.primary)
118
+ : (element.props.itemColor ?? theme.colors.text.primary);
119
+ const borderColor = isSelected
120
+ ? (element.props.itemSelectedBorderColor ?? theme.colors.primary)
121
+ : (element.props.itemBorderColor ?? theme.colors.neutral.low);
122
+
123
+ return (
124
+ <TouchableOpacity
125
+ key={item.value}
126
+ activeOpacity={0.7}
127
+ onPress={() => handleSelect(item.value, item.label)}
128
+ accessibilityRole="radio"
129
+ accessibilityState={{ selected: isSelected, checked: isSelected }}
130
+ accessibilityLabel={item.label}
131
+ style={{
132
+ flexDirection: "row",
133
+ alignItems: "center",
134
+ gap: 12,
135
+ backgroundColor: bgColor,
136
+ borderRadius: element.props.itemBorderRadius ?? 8,
137
+ borderWidth: element.props.itemBorderWidth ?? 1,
138
+ borderColor: borderColor,
139
+ padding: element.props.itemPadding ?? (element.props.itemPaddingHorizontal === undefined && element.props.itemPaddingVertical === undefined ? 12 : undefined),
140
+ paddingHorizontal: element.props.itemPaddingHorizontal,
141
+ paddingVertical: element.props.itemPaddingVertical,
142
+ }}
143
+ >
144
+ <View
145
+ style={{
146
+ width: 20,
147
+ height: 20,
148
+ borderRadius: 10,
149
+ borderWidth: 2,
150
+ borderColor: isSelected ? theme.colors.primary : theme.colors.neutral.medium,
151
+ alignItems: "center",
152
+ justifyContent: "center",
153
+ }}
154
+ >
155
+ {isSelected && (
156
+ <View
157
+ style={{
158
+ width: 10,
159
+ height: 10,
160
+ borderRadius: 5,
161
+ backgroundColor: theme.colors.primary,
162
+ }}
163
+ />
164
+ )}
165
+ </View>
166
+ <Text
167
+ style={{
168
+ flexShrink: 1,
169
+ color: textColor,
170
+ fontSize: element.props.itemFontSize ?? theme.typography.textStyles.body.fontSize,
171
+ fontWeight: (element.props.itemFontWeight as any) ?? theme.typography.textStyles.body.fontWeight,
172
+ fontFamily: element.props.itemFontFamily,
173
+ }}
174
+ >
175
+ {item.label}
176
+ </Text>
177
+ </TouchableOpacity>
178
+ );
179
+ })}
180
+ </View>
181
+ );
182
+ };
@@ -0,0 +1,105 @@
1
+ import React 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 RiveElementProps = BaseBoxProps & {
10
+ url: string;
11
+ autoplay?: boolean;
12
+ fit?: "Contain" | "Cover" | "Fill" | "FitWidth" | "FitHeight" | "None" | "ScaleDown" | "Layout";
13
+ alignment?: "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
14
+ artboardName?: string;
15
+ stateMachineName?: string;
16
+ };
17
+
18
+ export const RiveElementPropsSchema = BaseBoxPropsSchema.extend({
19
+ url: z.string().min(1, "url must not be empty"),
20
+ autoplay: z.boolean().optional(),
21
+ fit: z.enum(["Contain", "Cover", "Fill", "FitWidth", "FitHeight", "None", "ScaleDown", "Layout"]).optional(),
22
+ alignment: z.enum(["TopLeft", "TopCenter", "TopRight", "CenterLeft", "Center", "CenterRight", "BottomLeft", "BottomCenter", "BottomRight"]).optional(),
23
+ artboardName: z.string().optional(),
24
+ stateMachineName: z.string().optional(),
25
+ });
26
+
27
+ type RiveUIElement = Extract<UIElement, { type: "Rive" }>;
28
+
29
+ let RiveElementComponent: React.ComponentType<{ element: RiveUIElement; riveStyle: object }> | null = null;
30
+ try {
31
+ const riveModule = require("rive-react-native");
32
+ const Rive = riveModule.default;
33
+ const { Fit, Alignment } = riveModule;
34
+ RiveElementComponent = ({ element, riveStyle }: { element: RiveUIElement; riveStyle: object }) => {
35
+ return (
36
+ <Rive
37
+ url={element.props.url}
38
+ autoplay={element.props.autoplay ?? true}
39
+ fit={element.props.fit ? Fit[element.props.fit] : Fit.Contain}
40
+ alignment={element.props.alignment ? Alignment[element.props.alignment] : Alignment.Center}
41
+ artboardName={element.props.artboardName}
42
+ stateMachineName={element.props.stateMachineName}
43
+ style={riveStyle}
44
+ onError={console.error}
45
+ />
46
+ );
47
+ };
48
+ } catch {
49
+ // rive-react-native not installed - will show fallback if Rive is used
50
+ }
51
+
52
+ type Props = {
53
+ element: RiveUIElement;
54
+ ctx: RenderContext;
55
+ };
56
+
57
+ export const RiveElementRenderer = ({ element, ctx }: Props): React.ReactElement => {
58
+ const { theme } = ctx;
59
+ const wrapperStyle = {
60
+ width: element.props.width ?? ("100%" as `${number}%`),
61
+ height: element.props.height ?? 200,
62
+ opacity: element.props.opacity,
63
+ margin: element.props.margin,
64
+ marginHorizontal: element.props.marginHorizontal,
65
+ marginVertical: element.props.marginVertical,
66
+ padding: element.props.padding,
67
+ paddingHorizontal: element.props.paddingHorizontal,
68
+ paddingVertical: element.props.paddingVertical,
69
+ borderWidth: element.props.borderWidth,
70
+ borderRadius: element.props.borderRadius,
71
+ borderColor: element.props.borderColor,
72
+ overflow: "hidden" as const,
73
+ };
74
+
75
+ if (!RiveElementComponent) {
76
+ return (
77
+ <View style={[wrapperStyle, styles.mediaFallback, { backgroundColor: theme.colors.neutral.lowest }]}>
78
+ <Text style={[styles.mediaFallbackText, getTextStyle(theme, "caption"), { color: theme.colors.text.tertiary }]}>
79
+ Install rive-react-native to render Rive animations.
80
+ </Text>
81
+ </View>
82
+ );
83
+ }
84
+
85
+ return (
86
+ <View style={wrapperStyle}>
87
+ <RiveElementComponent element={element} riveStyle={styles.fill} />
88
+ </View>
89
+ );
90
+ };
91
+
92
+ const styles = StyleSheet.create({
93
+ fill: {
94
+ width: "100%",
95
+ height: "100%",
96
+ },
97
+ mediaFallback: {
98
+ alignItems: "center",
99
+ justifyContent: "center",
100
+ },
101
+ mediaFallbackText: {
102
+ textAlign: "center",
103
+ paddingHorizontal: 16,
104
+ },
105
+ });
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { z } from "zod";
3
+ import { View } from "react-native";
4
+ import { UIElement } from "../types";
5
+ import { RenderContext } from "./shared";
6
+
7
+ export type StackElementProps = {
8
+ gap?: number;
9
+ padding?: number;
10
+ paddingHorizontal?: number;
11
+ paddingVertical?: number;
12
+ margin?: number;
13
+ marginHorizontal?: number;
14
+ marginVertical?: number;
15
+ flex?: number;
16
+ width?: number;
17
+ height?: number;
18
+ minWidth?: number;
19
+ maxWidth?: number;
20
+ minHeight?: number;
21
+ maxHeight?: number;
22
+ alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
23
+ alignSelf?: "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
24
+ justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-around";
25
+ backgroundColor?: string;
26
+ flexWrap?: "wrap" | "nowrap";
27
+ flexShrink?: number;
28
+ borderWidth?: number;
29
+ borderRadius?: number;
30
+ borderColor?: string;
31
+ overflow?: "hidden" | "visible" | "scroll";
32
+ opacity?: number;
33
+ };
34
+
35
+ export const StackElementPropsSchema = z.object({
36
+ gap: z.number().optional(),
37
+ padding: z.number().optional(),
38
+ paddingHorizontal: z.number().optional(),
39
+ paddingVertical: z.number().optional(),
40
+ margin: z.number().optional(),
41
+ marginHorizontal: z.number().optional(),
42
+ marginVertical: z.number().optional(),
43
+ flex: z.number().optional(),
44
+ width: z.number().optional(),
45
+ height: z.number().optional(),
46
+ minWidth: z.number().optional(),
47
+ maxWidth: z.number().optional(),
48
+ minHeight: z.number().optional(),
49
+ maxHeight: z.number().optional(),
50
+ alignItems: z.enum(["flex-start", "center", "flex-end", "stretch"]).optional(),
51
+ alignSelf: z.enum(["auto", "flex-start", "flex-end", "center", "stretch", "baseline"]).optional(),
52
+ justifyContent: z.enum(["flex-start", "center", "flex-end", "space-between", "space-around"]).optional(),
53
+ backgroundColor: z.string().optional(),
54
+ flexWrap: z.enum(["wrap", "nowrap"]).optional(),
55
+ flexShrink: z.number().optional(),
56
+ borderWidth: z.number().optional(),
57
+ borderRadius: z.number().optional(),
58
+ borderColor: z.string().optional(),
59
+ overflow: z.enum(["hidden", "visible", "scroll"]).optional(),
60
+ opacity: z.number().min(0).max(1).optional(),
61
+ });
62
+
63
+ type StackUIElement = Extract<UIElement, { type: "YStack" | "XStack" }>;
64
+
65
+ type Props = {
66
+ element: StackUIElement;
67
+ ctx: RenderContext;
68
+ parentType?: "XStack" | "YStack";
69
+ };
70
+
71
+ export const StackElementComponent = ({ element, ctx, parentType }: Props): React.ReactElement => {
72
+ return (
73
+ <View
74
+ style={{
75
+ flexDirection: element.type === "XStack" ? "row" : "column",
76
+ alignSelf: element.props.alignSelf,
77
+ alignItems: element.props.alignItems,
78
+ gap: element.props.gap,
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
+ flex: element.props.flex,
86
+ width: element.props.width,
87
+ height: element.props.height,
88
+ minWidth: element.props.minWidth,
89
+ maxWidth: element.props.maxWidth,
90
+ minHeight: element.props.minHeight,
91
+ maxHeight: element.props.maxHeight,
92
+ flexShrink: element.props.flexShrink ?? (parentType === "XStack" ? 1 : undefined),
93
+ flexWrap: element.props.flexWrap,
94
+ justifyContent: element.props.justifyContent,
95
+ backgroundColor: element.props.backgroundColor,
96
+ borderWidth: element.props.borderWidth,
97
+ borderRadius: element.props.borderRadius,
98
+ borderColor: element.props.borderColor,
99
+ overflow: element.props.overflow,
100
+ opacity: element.props.opacity,
101
+ }}
102
+ >
103
+ {ctx.renderChildren(element.children, element.type)}
104
+ </View>
105
+ );
106
+ };