@rocapine/react-native-onboarding-ui 1.6.0 → 1.8.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/dist/UI/OnboardingPage.js +1 -1
- package/dist/UI/OnboardingPage.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.d.ts +0 -2
- package/dist/UI/Pages/ComposableScreen/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.js +13 -273
- package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +30 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +19 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +67 -0
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js +65 -0
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +41 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.js +37 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +42 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.js +34 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +102 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.js +68 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +39 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.js +60 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +78 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.js +119 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +62 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.js +68 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +85 -0
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.js +64 -0
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +66 -0
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.js +59 -0
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +41 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.js +84 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts +5 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +49 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts +13 -0
- package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/shared.js +6 -0
- package/dist/UI/Pages/ComposableScreen/elements/shared.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/types.d.ts +40 -113
- package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.js +33 -121
- package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
- package/dist/UI/Provider/OnboardingProgressProvider.d.ts +6 -2
- package/dist/UI/Provider/OnboardingProgressProvider.d.ts.map +1 -1
- package/dist/UI/Provider/OnboardingProgressProvider.js +4 -3
- package/dist/UI/Provider/OnboardingProgressProvider.js.map +1 -1
- package/package.json +2 -2
- package/src/UI/OnboardingPage.tsx +1 -1
- package/src/UI/Pages/ComposableScreen/Renderer.tsx +22 -430
- package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +31 -0
- package/src/UI/Pages/ComposableScreen/elements/ButtonElement.tsx +96 -0
- package/src/UI/Pages/ComposableScreen/elements/IconElement.tsx +67 -0
- package/src/UI/Pages/ComposableScreen/elements/ImageElement.tsx +52 -0
- package/src/UI/Pages/ComposableScreen/elements/InputElement.tsx +115 -0
- package/src/UI/Pages/ComposableScreen/elements/LottieElement.tsx +97 -0
- package/src/UI/Pages/ComposableScreen/elements/RadioGroupElement.tsx +181 -0
- package/src/UI/Pages/ComposableScreen/elements/RiveElement.tsx +105 -0
- package/src/UI/Pages/ComposableScreen/elements/StackElement.tsx +103 -0
- package/src/UI/Pages/ComposableScreen/elements/TextElement.tsx +95 -0
- package/src/UI/Pages/ComposableScreen/elements/VideoElement.tsx +113 -0
- package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +61 -0
- package/src/UI/Pages/ComposableScreen/elements/shared.ts +15 -0
- package/src/UI/Pages/ComposableScreen/types.ts +56 -233
- package/src/UI/Provider/OnboardingProgressProvider.tsx +8 -5
|
@@ -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,103 @@
|
|
|
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
|
+
justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-around";
|
|
24
|
+
backgroundColor?: string;
|
|
25
|
+
flexWrap?: "wrap" | "nowrap";
|
|
26
|
+
flexShrink?: number;
|
|
27
|
+
borderWidth?: number;
|
|
28
|
+
borderRadius?: number;
|
|
29
|
+
borderColor?: string;
|
|
30
|
+
overflow?: "hidden" | "visible" | "scroll";
|
|
31
|
+
opacity?: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const StackElementPropsSchema = z.object({
|
|
35
|
+
gap: z.number().optional(),
|
|
36
|
+
padding: z.number().optional(),
|
|
37
|
+
paddingHorizontal: z.number().optional(),
|
|
38
|
+
paddingVertical: z.number().optional(),
|
|
39
|
+
margin: z.number().optional(),
|
|
40
|
+
marginHorizontal: z.number().optional(),
|
|
41
|
+
marginVertical: z.number().optional(),
|
|
42
|
+
flex: z.number().optional(),
|
|
43
|
+
width: z.number().optional(),
|
|
44
|
+
height: z.number().optional(),
|
|
45
|
+
minWidth: z.number().optional(),
|
|
46
|
+
maxWidth: z.number().optional(),
|
|
47
|
+
minHeight: z.number().optional(),
|
|
48
|
+
maxHeight: z.number().optional(),
|
|
49
|
+
alignItems: z.enum(["flex-start", "center", "flex-end", "stretch"]).optional(),
|
|
50
|
+
justifyContent: z.enum(["flex-start", "center", "flex-end", "space-between", "space-around"]).optional(),
|
|
51
|
+
backgroundColor: z.string().optional(),
|
|
52
|
+
flexWrap: z.enum(["wrap", "nowrap"]).optional(),
|
|
53
|
+
flexShrink: z.number().optional(),
|
|
54
|
+
borderWidth: z.number().optional(),
|
|
55
|
+
borderRadius: z.number().optional(),
|
|
56
|
+
borderColor: z.string().optional(),
|
|
57
|
+
overflow: z.enum(["hidden", "visible", "scroll"]).optional(),
|
|
58
|
+
opacity: z.number().min(0).max(1).optional(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
type StackUIElement = Extract<UIElement, { type: "YStack" | "XStack" }>;
|
|
62
|
+
|
|
63
|
+
type Props = {
|
|
64
|
+
element: StackUIElement;
|
|
65
|
+
ctx: RenderContext;
|
|
66
|
+
parentType?: "XStack" | "YStack";
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const StackElementComponent = ({ element, ctx, parentType }: Props): React.ReactElement => {
|
|
70
|
+
return (
|
|
71
|
+
<View
|
|
72
|
+
style={{
|
|
73
|
+
flexDirection: element.type === "XStack" ? "row" : "column",
|
|
74
|
+
gap: element.props.gap,
|
|
75
|
+
padding: element.props.padding,
|
|
76
|
+
paddingHorizontal: element.props.paddingHorizontal,
|
|
77
|
+
paddingVertical: element.props.paddingVertical,
|
|
78
|
+
margin: element.props.margin,
|
|
79
|
+
marginHorizontal: element.props.marginHorizontal,
|
|
80
|
+
marginVertical: element.props.marginVertical,
|
|
81
|
+
flex: element.props.flex,
|
|
82
|
+
width: element.props.width,
|
|
83
|
+
height: element.props.height,
|
|
84
|
+
minWidth: element.props.minWidth,
|
|
85
|
+
maxWidth: element.props.maxWidth,
|
|
86
|
+
minHeight: element.props.minHeight,
|
|
87
|
+
maxHeight: element.props.maxHeight,
|
|
88
|
+
flexShrink: element.props.flexShrink ?? (parentType === "XStack" ? 1 : undefined),
|
|
89
|
+
flexWrap: element.props.flexWrap,
|
|
90
|
+
alignItems: element.props.alignItems,
|
|
91
|
+
justifyContent: element.props.justifyContent,
|
|
92
|
+
backgroundColor: element.props.backgroundColor,
|
|
93
|
+
borderWidth: element.props.borderWidth,
|
|
94
|
+
borderRadius: element.props.borderRadius,
|
|
95
|
+
borderColor: element.props.borderColor,
|
|
96
|
+
overflow: element.props.overflow,
|
|
97
|
+
opacity: element.props.opacity,
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
{ctx.renderChildren(element.children, element.type)}
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
@@ -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 ?? "");
|