@rocapine/react-native-onboarding-ui 1.22.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/UI/Pages/ComposableScreen/Renderer.d.ts.map +1 -1
  2. package/dist/UI/Pages/ComposableScreen/Renderer.js +8 -1
  3. package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
  4. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +21 -0
  5. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -1
  6. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +10 -1
  7. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -1
  8. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +289 -4
  9. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -1
  10. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js +98 -57
  11. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js.map +1 -1
  12. package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts +8 -0
  13. package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts.map +1 -1
  14. package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.js +11 -0
  15. package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.js.map +1 -1
  16. package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts +8 -0
  17. package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts.map +1 -1
  18. package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts +8 -0
  19. package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts.map +1 -1
  20. package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +8 -0
  21. package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -1
  22. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +8 -0
  23. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -1
  24. package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +8 -0
  25. package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -1
  26. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +8 -0
  27. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -1
  28. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +8 -0
  29. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -1
  30. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +8 -0
  31. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -1
  32. package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts +8 -0
  33. package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts.map +1 -1
  34. package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +8 -0
  35. package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -1
  36. package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +8 -0
  37. package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -1
  38. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +8 -0
  39. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -1
  40. package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts +8 -0
  41. package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts.map +1 -1
  42. package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.d.ts +11 -0
  43. package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.d.ts.map +1 -0
  44. package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.js +74 -0
  45. package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.js.map +1 -0
  46. package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
  47. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +6 -0
  48. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
  49. package/dist/UI/Pages/ComposableScreen/types.d.ts +16 -0
  50. package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
  51. package/dist/UI/Pages/ComposableScreen/types.js +16 -0
  52. package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/UI/Pages/ComposableScreen/Renderer.tsx +12 -1
  55. package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +20 -0
  56. package/src/UI/Pages/ComposableScreen/elements/ButtonElement.tsx +169 -69
  57. package/src/UI/Pages/ComposableScreen/elements/CarouselElement.tsx +9 -0
  58. package/src/UI/Pages/ComposableScreen/elements/collectDefaults.ts +76 -0
  59. package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +8 -0
  60. package/src/UI/Pages/ComposableScreen/types.ts +36 -0
@@ -1,6 +1,6 @@
1
- import React, { useMemo } from "react";
1
+ import React, { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { z } from "zod";
3
- import { Text, TouchableOpacity } from "react-native";
3
+ import { Animated, Pressable, Text } from "react-native";
4
4
  import {
5
5
  useResolvedFontStyle,
6
6
  evaluateCondition,
@@ -67,12 +67,21 @@ export const ButtonActionSchema = z.union([
67
67
  SetVariableButtonActionSchema,
68
68
  ]);
69
69
 
70
+ type ButtonOverridableProps = BaseBoxProps & {
71
+ variant?: "filled" | "outlined" | "ghost";
72
+ backgroundColor?: string;
73
+ color?: string;
74
+ fontSize?: number;
75
+ fontWeight?: string;
76
+ fontFamily?: string | "inherit";
77
+ fontStyle?: "normal" | "italic";
78
+ textAlign?: "left" | "center" | "right";
79
+ };
80
+
81
+ export type ButtonStyleOverride = Partial<ButtonOverridableProps>;
82
+
70
83
  export type ButtonElementProps = BaseBoxProps & {
71
84
  label: string;
72
- /**
73
- * Ordered list of actions to run on press. Sequential, await async handlers,
74
- * abort on error, `"continue"` is terminal.
75
- */
76
85
  actions?: ButtonAction[];
77
86
  /** @deprecated Use `actions` instead. */
78
87
  action?: "continue";
@@ -85,10 +94,26 @@ export type ButtonElementProps = BaseBoxProps & {
85
94
  fontStyle?: "normal" | "italic";
86
95
  textAlign?: "left" | "center" | "right";
87
96
  disabledWhen?: LeafCondition | ConditionGroup;
97
+ /** @deprecated Use `disabledStyle.backgroundColor`. */
88
98
  disabledBackgroundColor?: string;
99
+ /** @deprecated Use `disabledStyle.color`. */
89
100
  disabledColor?: string;
101
+ pressedStyle?: ButtonStyleOverride;
102
+ disabledStyle?: ButtonStyleOverride;
103
+ transitionDurationMs?: number;
90
104
  };
91
105
 
106
+ export const ButtonStyleOverrideSchema = BaseBoxPropsSchema.extend({
107
+ variant: z.enum(["filled", "outlined", "ghost"]).optional(),
108
+ backgroundColor: z.string().optional(),
109
+ color: z.string().optional(),
110
+ fontSize: z.number().optional(),
111
+ fontWeight: z.string().optional(),
112
+ fontFamily: z.string().optional(),
113
+ fontStyle: z.enum(["normal", "italic"]).optional(),
114
+ textAlign: z.enum(["left", "center", "right"]).optional(),
115
+ }).partial();
116
+
92
117
  export const ButtonElementPropsSchema = BaseBoxPropsSchema.extend({
93
118
  label: z.string().min(1, "label must not be empty"),
94
119
  actions: z.array(ButtonActionSchema).optional(),
@@ -104,6 +129,9 @@ export const ButtonElementPropsSchema = BaseBoxPropsSchema.extend({
104
129
  disabledWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
105
130
  disabledBackgroundColor: z.string().optional(),
106
131
  disabledColor: z.string().optional(),
132
+ pressedStyle: ButtonStyleOverrideSchema.optional(),
133
+ disabledStyle: ButtonStyleOverrideSchema.optional(),
134
+ transitionDurationMs: z.number().min(0).optional(),
107
135
  });
108
136
 
109
137
  type ButtonUIElement = Extract<UIElement, { type: "Button" }>;
@@ -113,6 +141,14 @@ type Props = {
113
141
  ctx: RenderContext;
114
142
  };
115
143
 
144
+ const buildShadowStyle = (p: Pick<BaseBoxProps, "shadowColor" | "shadowOffset" | "shadowOpacity" | "shadowRadius" | "elevation">) => ({
145
+ shadowColor: p.shadowColor,
146
+ shadowOffset: p.shadowOffset,
147
+ shadowOpacity: p.shadowOpacity,
148
+ shadowRadius: p.shadowRadius,
149
+ elevation: p.elevation,
150
+ });
151
+
116
152
  export const ButtonElementComponent = ({ element, ctx }: Props): React.ReactElement => {
117
153
  const { theme, onContinue, customActions, variables, setVariable } = ctx;
118
154
  const flatVariables = useMemo(
@@ -125,6 +161,8 @@ export const ButtonElementComponent = ({ element, ctx }: Props): React.ReactElem
125
161
  const isDisabled = element.props.disabledWhen
126
162
  ? evaluateCondition(element.props.disabledWhen, flatVariables)
127
163
  : false;
164
+ const [isPressed, setIsPressed] = useState(false);
165
+
128
166
  const handlePress = async () => {
129
167
  if (isDisabled) return;
130
168
  const { actions, action } = element.props;
@@ -171,119 +209,181 @@ export const ButtonElementComponent = ({ element, ctx }: Props): React.ReactElem
171
209
  }
172
210
  }
173
211
  };
174
- const variant = element.props.variant ?? "filled";
212
+
213
+ // State overrides are merged over base props. disabledStyle wins over the
214
+ // deprecated `disabledBackgroundColor`/`disabledColor` fields; falls back to
215
+ // those when only the legacy fields are set.
216
+ const stateOverride: ButtonStyleOverride = isDisabled
217
+ ? (element.props.disabledStyle ?? {})
218
+ : isPressed
219
+ ? (element.props.pressedStyle ?? {})
220
+ : {};
221
+ const eff = { ...element.props, ...stateOverride };
222
+
223
+ const variant = eff.variant ?? "filled";
175
224
  const isFilled = variant === "filled";
176
225
  const isOutlined = variant === "outlined";
177
- const disabledBg =
178
- element.props.disabledBackgroundColor ?? theme.colors.disable;
179
- const disabledText =
180
- element.props.disabledColor ?? theme.colors.text.disable;
226
+
227
+ const legacyDisabledBg =
228
+ isDisabled && !element.props.disabledStyle
229
+ ? (element.props.disabledBackgroundColor ?? theme.colors.disable)
230
+ : undefined;
231
+ const legacyDisabledText =
232
+ isDisabled && !element.props.disabledStyle
233
+ ? (element.props.disabledColor ?? theme.colors.text.disable)
234
+ : undefined;
235
+
181
236
  const bgColor = isDisabled
182
237
  ? isFilled
183
- ? disabledBg
238
+ ? (eff.backgroundColor ?? legacyDisabledBg ?? theme.colors.disable)
184
239
  : "transparent"
185
240
  : isFilled
186
- ? (element.props.backgroundColor ?? theme.colors.primary)
241
+ ? (eff.backgroundColor ?? theme.colors.primary)
187
242
  : "transparent";
188
243
  const textColor = isDisabled
189
- ? disabledText
244
+ ? (eff.color ?? legacyDisabledText ?? theme.colors.text.disable)
190
245
  : isFilled
191
- ? (element.props.color ?? theme.colors.text.opposite)
192
- : (element.props.color ?? theme.colors.primary);
246
+ ? (eff.color ?? theme.colors.text.opposite)
247
+ : (eff.color ?? theme.colors.primary);
193
248
  const outlinedBorderColor = isDisabled
194
- ? disabledBg
195
- : (element.props.borderColor ?? theme.colors.primary);
249
+ ? (eff.borderColor ?? legacyDisabledBg ?? theme.colors.disable)
250
+ : (eff.borderColor ?? theme.colors.primary);
196
251
 
197
- const hasGradient = isFilled && !isDisabled && !!element.props.backgroundGradient;
198
- const borderRadius = element.props.borderRadius ?? 90;
252
+ const hasGradient = isFilled && !isDisabled && !!eff.backgroundGradient;
253
+ const borderRadius = eff.borderRadius ?? 90;
199
254
  const inheritedFontFamily = resolveInheritedFontFamily(
200
- element.props.fontFamily,
255
+ eff.fontFamily,
201
256
  theme.typography.defaultFontFamily
202
257
  );
203
258
  const resolvedFont = useResolvedFontStyle(
204
259
  inheritedFontFamily,
205
- element.props.fontWeight
260
+ eff.fontWeight
206
261
  );
207
262
 
263
+ // Animate opacity between rest/pressed/disabled. Uses native driver — color
264
+ // and shadow* changes apply instantly on state transition (acceptable for
265
+ // tap-feedback timescales). transitionDurationMs gates animation length.
266
+ const opacityAnim = useRef(new Animated.Value(eff.opacity ?? 1)).current;
267
+ const duration = element.props.transitionDurationMs ?? 150;
268
+ const restOpacity = element.props.opacity ?? 1;
269
+ const pressedOpacity = element.props.pressedStyle?.opacity ?? 0.8;
270
+ const disabledOpacity =
271
+ element.props.disabledStyle?.opacity ?? element.props.opacity ?? 1;
272
+ const targetOpacity = isDisabled
273
+ ? disabledOpacity
274
+ : isPressed
275
+ ? pressedOpacity
276
+ : restOpacity;
277
+
278
+ useEffect(() => {
279
+ Animated.timing(opacityAnim, {
280
+ toValue: targetOpacity,
281
+ duration,
282
+ useNativeDriver: true,
283
+ }).start();
284
+ }, [targetOpacity, duration, opacityAnim]);
285
+
286
+ const shadowStyle = buildShadowStyle(eff);
287
+
208
288
  const labelNode = (
209
289
  <Text
210
290
  style={{
211
291
  color: textColor,
212
- fontSize: element.props.fontSize ?? theme.typography.textStyles.button.fontSize,
292
+ fontSize: eff.fontSize ?? theme.typography.textStyles.button.fontSize,
213
293
  fontWeight: resolvedFont.resolvedToVariant
214
294
  ? undefined
215
295
  : ((resolvedFont.fontWeight as any) ?? theme.typography.textStyles.button.fontWeight),
216
296
  fontFamily: resolvedFont.fontFamily,
217
- fontStyle: element.props.fontStyle,
218
- textAlign: element.props.textAlign ?? "center",
297
+ fontStyle: eff.fontStyle,
298
+ textAlign: eff.textAlign ?? "center",
219
299
  }}
220
300
  >
221
301
  {element.props.label}
222
302
  </Text>
223
303
  );
224
304
 
305
+ const onPressIn = () => setIsPressed(true);
306
+ const onPressOut = () => setIsPressed(false);
307
+
225
308
  if (hasGradient) {
226
309
  return (
227
- <GradientBox
228
- gradient={element.props.backgroundGradient}
310
+ <Animated.View
229
311
  style={{
312
+ ...shadowStyle,
313
+ opacity: opacityAnim,
314
+ width: dim(eff.width),
315
+ height: dim(eff.height),
316
+ margin: eff.margin,
317
+ marginHorizontal: eff.marginHorizontal,
318
+ marginVertical: eff.marginVertical,
319
+ alignSelf: eff.alignSelf ?? (eff.width ? undefined : "stretch"),
230
320
  borderRadius,
231
- borderWidth: isOutlined ? (element.props.borderWidth ?? 1) : (element.props.borderWidth ?? 0),
232
- borderColor: isOutlined ? outlinedBorderColor : element.props.borderColor,
233
- width: dim(element.props.width),
234
- height: dim(element.props.height),
235
- margin: element.props.margin,
236
- marginHorizontal: element.props.marginHorizontal,
237
- marginVertical: element.props.marginVertical,
238
- opacity: element.props.opacity,
239
- alignSelf: element.props.alignSelf ?? (element.props.width ? undefined : "stretch"),
240
- overflow: "hidden",
241
321
  }}
242
322
  >
243
- <TouchableOpacity
244
- activeOpacity={0.8}
245
- onPress={handlePress}
246
- disabled={isDisabled}
323
+ <GradientBox
324
+ gradient={eff.backgroundGradient}
247
325
  style={{
326
+ borderRadius,
327
+ borderWidth: isOutlined ? (eff.borderWidth ?? 1) : (eff.borderWidth ?? 0),
328
+ borderColor: isOutlined ? outlinedBorderColor : eff.borderColor,
329
+ overflow: "hidden",
248
330
  flex: 1,
249
- padding: element.props.padding,
250
- paddingVertical: element.props.paddingVertical ?? 14,
251
- paddingHorizontal: element.props.paddingHorizontal ?? 24,
252
- alignItems: "center",
253
- justifyContent: "center",
254
331
  }}
255
332
  >
256
- {labelNode}
257
- </TouchableOpacity>
258
- </GradientBox>
333
+ <Pressable
334
+ onPress={handlePress}
335
+ onPressIn={onPressIn}
336
+ onPressOut={onPressOut}
337
+ disabled={isDisabled}
338
+ style={{
339
+ flex: 1,
340
+ padding: eff.padding,
341
+ paddingVertical: eff.paddingVertical ?? 14,
342
+ paddingHorizontal: eff.paddingHorizontal ?? 24,
343
+ alignItems: "center",
344
+ justifyContent: "center",
345
+ }}
346
+ >
347
+ {labelNode}
348
+ </Pressable>
349
+ </GradientBox>
350
+ </Animated.View>
259
351
  );
260
352
  }
261
353
 
262
354
  return (
263
- <TouchableOpacity
264
- activeOpacity={0.8}
265
- onPress={handlePress}
266
- disabled={isDisabled}
355
+ <Animated.View
267
356
  style={{
357
+ ...shadowStyle,
358
+ opacity: opacityAnim,
268
359
  backgroundColor: bgColor,
269
360
  borderRadius,
270
- borderWidth: isOutlined ? (element.props.borderWidth ?? 1) : (element.props.borderWidth ?? 0),
271
- borderColor: isOutlined ? outlinedBorderColor : element.props.borderColor,
272
- padding: element.props.padding,
273
- paddingVertical: element.props.paddingVertical ?? 14,
274
- paddingHorizontal: element.props.paddingHorizontal ?? 24,
275
- width: dim(element.props.width),
276
- height: dim(element.props.height),
277
- margin: element.props.margin,
278
- marginHorizontal: element.props.marginHorizontal,
279
- marginVertical: element.props.marginVertical,
280
- opacity: element.props.opacity,
281
- alignSelf: element.props.alignSelf ?? (element.props.width ? undefined : "stretch"),
282
- alignItems: "center",
283
- justifyContent: "center",
361
+ borderWidth: isOutlined ? (eff.borderWidth ?? 1) : (eff.borderWidth ?? 0),
362
+ borderColor: isOutlined ? outlinedBorderColor : eff.borderColor,
363
+ width: dim(eff.width),
364
+ height: dim(eff.height),
365
+ margin: eff.margin,
366
+ marginHorizontal: eff.marginHorizontal,
367
+ marginVertical: eff.marginVertical,
368
+ alignSelf: eff.alignSelf ?? (eff.width ? undefined : "stretch"),
284
369
  }}
285
370
  >
286
- {labelNode}
287
- </TouchableOpacity>
371
+ <Pressable
372
+ onPress={handlePress}
373
+ onPressIn={onPressIn}
374
+ onPressOut={onPressOut}
375
+ disabled={isDisabled}
376
+ style={{
377
+ padding: eff.padding,
378
+ paddingVertical: eff.paddingVertical ?? 14,
379
+ paddingHorizontal: eff.paddingHorizontal ?? 24,
380
+ alignItems: "center",
381
+ justifyContent: "center",
382
+ borderRadius,
383
+ }}
384
+ >
385
+ {labelNode}
386
+ </Pressable>
387
+ </Animated.View>
288
388
  );
289
389
  };
@@ -82,6 +82,15 @@ export function CarouselElementComponent({ element, ctx }: Props): React.ReactEl
82
82
  ref.current?.scrollTo({ count: target - progress.value, animated: true });
83
83
  }, [variableName, variableValue, childrenCount]);
84
84
 
85
+ // Persist the initial index into ctx.variables when no value exists yet, so the
86
+ // default reaches downstream renderWhen / interpolation across renders.
87
+ useEffect(() => {
88
+ if (!variableName) return;
89
+ if (variableValue !== undefined) return;
90
+ if (props.defaultIndex == null) return;
91
+ ctx.setVariable(variableName, { value: String(clampIndex(props.defaultIndex)) });
92
+ }, [variableName, variableValue, props.defaultIndex, childrenCount]);
93
+
85
94
  const onLayout = (e: LayoutChangeEvent) => {
86
95
  const { width, height } = e.nativeEvent.layout;
87
96
  if (!size || size.width !== width || size.height !== height) {
@@ -0,0 +1,76 @@
1
+ import { UIElement } from "../types";
2
+ import { ComposableVariableEntry } from "../../../Provider/OnboardingProgressProvider";
3
+
4
+ /**
5
+ * Walks the element tree and returns the initial variable map declared via
6
+ * element-level defaults (`defaultIndex`, `defaultValue`, `defaultValues`).
7
+ * Used by `Renderer` to overlay defaults onto `ctx.variables` so
8
+ * `renderWhen` / expressions evaluate against them on first render — before
9
+ * per-element seeding effects run.
10
+ */
11
+ export function collectElementDefaults(
12
+ elements: UIElement[]
13
+ ): Record<string, ComposableVariableEntry> {
14
+ const out: Record<string, ComposableVariableEntry> = {};
15
+ const visit = (el: UIElement) => {
16
+ switch (el.type) {
17
+ case "Carousel": {
18
+ const name = el.props.variableName;
19
+ if (name && el.props.defaultIndex != null) {
20
+ // Mirror CarouselElementComponent's clampIndex so the overlaid default
21
+ // matches the index the carousel actually mounts at.
22
+ const raw = Number(el.props.defaultIndex);
23
+ const safe = Number.isFinite(raw) ? raw : 0;
24
+ const maxIdx = Math.max(0, el.children.length - 1);
25
+ const clamped = Math.max(0, Math.min(safe, maxIdx));
26
+ out[name] = { value: String(clamped) };
27
+ }
28
+ el.children.forEach(visit);
29
+ break;
30
+ }
31
+ case "RadioGroup": {
32
+ const name = el.props.variableName;
33
+ const dv = el.props.defaultValue;
34
+ if (name && dv !== undefined) {
35
+ const item = el.props.items.find((i) => i.value === dv);
36
+ out[name] = { value: dv, label: item?.label };
37
+ }
38
+ break;
39
+ }
40
+ case "CheckboxGroup": {
41
+ const name = el.props.variableName;
42
+ const dvs = el.props.defaultValues;
43
+ if (name && dvs !== undefined) {
44
+ const labels = dvs.map(
45
+ (dv) => el.props.items.find((i) => i.value === dv)?.label ?? dv
46
+ );
47
+ out[name] = { value: JSON.stringify(dvs), label: labels.join(", ") };
48
+ }
49
+ break;
50
+ }
51
+ case "Input": {
52
+ const name = el.props.variableName;
53
+ if (name && el.props.defaultValue !== undefined) {
54
+ out[name] = { value: el.props.defaultValue };
55
+ }
56
+ break;
57
+ }
58
+ case "DatePicker": {
59
+ const name = el.props.variableName;
60
+ if (name && el.props.defaultValue !== undefined) {
61
+ const d = new Date(el.props.defaultValue);
62
+ if (!isNaN(d.getTime())) out[name] = { value: d.toISOString() };
63
+ }
64
+ break;
65
+ }
66
+ case "YStack":
67
+ case "XStack":
68
+ case "ZStack":
69
+ case "SafeAreaView":
70
+ el.children.forEach(visit);
71
+ break;
72
+ }
73
+ };
74
+ elements.forEach(visit);
75
+ return out;
76
+ }
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { evaluateCondition } from "@rocapine/react-native-onboarding";
2
3
  import { UIElement } from "../types";
3
4
  import { RenderContext } from "./shared";
4
5
  import { StackElementComponent } from "./StackElement";
@@ -22,6 +23,13 @@ export const renderElement = (
22
23
  ctx: RenderContext,
23
24
  parentType?: "XStack" | "YStack" | "ZStack"
24
25
  ): React.ReactNode => {
26
+ if (element.renderWhen) {
27
+ const flatVars = Object.fromEntries(
28
+ Object.entries(ctx.variables).map(([k, v]) => [k, v?.value])
29
+ );
30
+ if (!evaluateCondition(element.renderWhen, flatVars)) return null;
31
+ }
32
+
25
33
  if (element.type === "YStack" || element.type === "XStack") {
26
34
  return <StackElementComponent key={element.id} element={element} ctx={ctx} parentType={parentType} />;
27
35
  }
@@ -1,4 +1,10 @@
1
1
  import { z } from "zod";
2
+ import {
3
+ type LeafCondition,
4
+ type ConditionGroup,
5
+ LeafConditionSchema,
6
+ ConditionGroupSchema,
7
+ } from "@rocapine/react-native-onboarding";
2
8
  import { CustomPayloadSchema } from "../types";
3
9
  import { type StackElementProps, StackElementPropsSchema } from "./elements/StackElement";
4
10
  import { type TextElementProps, TextElementPropsSchema } from "./elements/TextElement";
@@ -40,6 +46,7 @@ export type UIElement =
40
46
  | {
41
47
  id: string;
42
48
  name?: string;
49
+ renderWhen?: LeafCondition | ConditionGroup;
43
50
  type: "YStack" | "XStack";
44
51
  props: StackElementProps;
45
52
  children: UIElement[];
@@ -47,72 +54,84 @@ export type UIElement =
47
54
  | {
48
55
  id: string;
49
56
  name?: string;
57
+ renderWhen?: LeafCondition | ConditionGroup;
50
58
  type: "Text";
51
59
  props: TextElementProps;
52
60
  }
53
61
  | {
54
62
  id: string;
55
63
  name?: string;
64
+ renderWhen?: LeafCondition | ConditionGroup;
56
65
  type: "Image";
57
66
  props: ImageElementProps;
58
67
  }
59
68
  | {
60
69
  id: string;
61
70
  name?: string;
71
+ renderWhen?: LeafCondition | ConditionGroup;
62
72
  type: "Lottie";
63
73
  props: LottieElementProps;
64
74
  }
65
75
  | {
66
76
  id: string;
67
77
  name?: string;
78
+ renderWhen?: LeafCondition | ConditionGroup;
68
79
  type: "Rive";
69
80
  props: RiveElementProps;
70
81
  }
71
82
  | {
72
83
  id: string;
73
84
  name?: string;
85
+ renderWhen?: LeafCondition | ConditionGroup;
74
86
  type: "Icon";
75
87
  props: IconElementProps;
76
88
  }
77
89
  | {
78
90
  id: string;
79
91
  name?: string;
92
+ renderWhen?: LeafCondition | ConditionGroup;
80
93
  type: "Video";
81
94
  props: VideoElementProps;
82
95
  }
83
96
  | {
84
97
  id: string;
85
98
  name?: string;
99
+ renderWhen?: LeafCondition | ConditionGroup;
86
100
  type: "Input";
87
101
  props: InputElementProps;
88
102
  }
89
103
  | {
90
104
  id: string;
91
105
  name?: string;
106
+ renderWhen?: LeafCondition | ConditionGroup;
92
107
  type: "Button";
93
108
  props: ButtonElementProps;
94
109
  }
95
110
  | {
96
111
  id: string;
97
112
  name?: string;
113
+ renderWhen?: LeafCondition | ConditionGroup;
98
114
  type: "RadioGroup";
99
115
  props: RadioGroupElementProps;
100
116
  }
101
117
  | {
102
118
  id: string;
103
119
  name?: string;
120
+ renderWhen?: LeafCondition | ConditionGroup;
104
121
  type: "CheckboxGroup";
105
122
  props: CheckboxGroupElementProps;
106
123
  }
107
124
  | {
108
125
  id: string;
109
126
  name?: string;
127
+ renderWhen?: LeafCondition | ConditionGroup;
110
128
  type: "DatePicker";
111
129
  props: DatePickerElementProps;
112
130
  }
113
131
  | {
114
132
  id: string;
115
133
  name?: string;
134
+ renderWhen?: LeafCondition | ConditionGroup;
116
135
  type: "Carousel";
117
136
  props: CarouselElementProps;
118
137
  children: UIElement[];
@@ -120,6 +139,7 @@ export type UIElement =
120
139
  | {
121
140
  id: string;
122
141
  name?: string;
142
+ renderWhen?: LeafCondition | ConditionGroup;
123
143
  type: "ZStack";
124
144
  props: ZStackElementProps;
125
145
  children: UIElement[];
@@ -127,6 +147,7 @@ export type UIElement =
127
147
  | {
128
148
  id: string;
129
149
  name?: string;
150
+ renderWhen?: LeafCondition | ConditionGroup;
130
151
  type: "SafeAreaView";
131
152
  props: SafeAreaViewElementProps;
132
153
  children: UIElement[];
@@ -137,6 +158,7 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
137
158
  z.object({
138
159
  id: z.string(),
139
160
  name: z.string().optional(),
161
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
140
162
  type: z.union([z.literal("YStack"), z.literal("XStack")]),
141
163
  props: StackElementPropsSchema,
142
164
  children: z.array(UIElementSchema),
@@ -144,72 +166,84 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
144
166
  z.object({
145
167
  id: z.string(),
146
168
  name: z.string().optional(),
169
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
147
170
  type: z.literal("Text"),
148
171
  props: TextElementPropsSchema,
149
172
  }),
150
173
  z.object({
151
174
  id: z.string(),
152
175
  name: z.string().optional(),
176
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
153
177
  type: z.literal("Image"),
154
178
  props: ImageElementPropsSchema,
155
179
  }),
156
180
  z.object({
157
181
  id: z.string(),
158
182
  name: z.string().optional(),
183
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
159
184
  type: z.literal("Lottie"),
160
185
  props: LottieElementPropsSchema,
161
186
  }),
162
187
  z.object({
163
188
  id: z.string(),
164
189
  name: z.string().optional(),
190
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
165
191
  type: z.literal("Rive"),
166
192
  props: RiveElementPropsSchema,
167
193
  }),
168
194
  z.object({
169
195
  id: z.string(),
170
196
  name: z.string().optional(),
197
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
171
198
  type: z.literal("Icon"),
172
199
  props: IconElementPropsSchema,
173
200
  }),
174
201
  z.object({
175
202
  id: z.string(),
176
203
  name: z.string().optional(),
204
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
177
205
  type: z.literal("Video"),
178
206
  props: VideoElementPropsSchema,
179
207
  }),
180
208
  z.object({
181
209
  id: z.string(),
182
210
  name: z.string().optional(),
211
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
183
212
  type: z.literal("Input"),
184
213
  props: InputElementPropsSchema,
185
214
  }),
186
215
  z.object({
187
216
  id: z.string(),
188
217
  name: z.string().optional(),
218
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
189
219
  type: z.literal("Button"),
190
220
  props: ButtonElementPropsSchema,
191
221
  }),
192
222
  z.object({
193
223
  id: z.string(),
194
224
  name: z.string().optional(),
225
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
195
226
  type: z.literal("RadioGroup"),
196
227
  props: RadioGroupElementPropsSchema,
197
228
  }),
198
229
  z.object({
199
230
  id: z.string(),
200
231
  name: z.string().optional(),
232
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
201
233
  type: z.literal("CheckboxGroup"),
202
234
  props: CheckboxGroupElementPropsSchema,
203
235
  }),
204
236
  z.object({
205
237
  id: z.string(),
206
238
  name: z.string().optional(),
239
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
207
240
  type: z.literal("DatePicker"),
208
241
  props: DatePickerElementPropsSchema,
209
242
  }),
210
243
  z.object({
211
244
  id: z.string(),
212
245
  name: z.string().optional(),
246
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
213
247
  type: z.literal("Carousel"),
214
248
  props: CarouselElementPropsSchema,
215
249
  children: z.array(UIElementSchema),
@@ -217,6 +251,7 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
217
251
  z.object({
218
252
  id: z.string(),
219
253
  name: z.string().optional(),
254
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
220
255
  type: z.literal("ZStack"),
221
256
  props: ZStackElementPropsSchema,
222
257
  children: z.array(UIElementSchema),
@@ -224,6 +259,7 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
224
259
  z.object({
225
260
  id: z.string(),
226
261
  name: z.string().optional(),
262
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
227
263
  type: z.literal("SafeAreaView"),
228
264
  props: SafeAreaViewElementPropsSchema,
229
265
  children: z.array(UIElementSchema),