@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.
- package/dist/UI/Pages/ComposableScreen/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.js +8 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +21 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +10 -1
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +289 -4
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js +98 -57
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.js +11 -0
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts +8 -0
- package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.d.ts +11 -0
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.js +74 -0
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +6 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.d.ts +16 -0
- package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.js +16 -0
- package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
- package/package.json +2 -2
- package/src/UI/Pages/ComposableScreen/Renderer.tsx +12 -1
- package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +20 -0
- package/src/UI/Pages/ComposableScreen/elements/ButtonElement.tsx +169 -69
- package/src/UI/Pages/ComposableScreen/elements/CarouselElement.tsx +9 -0
- package/src/UI/Pages/ComposableScreen/elements/collectDefaults.ts +76 -0
- package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +8 -0
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
?
|
|
238
|
+
? (eff.backgroundColor ?? legacyDisabledBg ?? theme.colors.disable)
|
|
184
239
|
: "transparent"
|
|
185
240
|
: isFilled
|
|
186
|
-
? (
|
|
241
|
+
? (eff.backgroundColor ?? theme.colors.primary)
|
|
187
242
|
: "transparent";
|
|
188
243
|
const textColor = isDisabled
|
|
189
|
-
?
|
|
244
|
+
? (eff.color ?? legacyDisabledText ?? theme.colors.text.disable)
|
|
190
245
|
: isFilled
|
|
191
|
-
? (
|
|
192
|
-
: (
|
|
246
|
+
? (eff.color ?? theme.colors.text.opposite)
|
|
247
|
+
: (eff.color ?? theme.colors.primary);
|
|
193
248
|
const outlinedBorderColor = isDisabled
|
|
194
|
-
?
|
|
195
|
-
: (
|
|
249
|
+
? (eff.borderColor ?? legacyDisabledBg ?? theme.colors.disable)
|
|
250
|
+
: (eff.borderColor ?? theme.colors.primary);
|
|
196
251
|
|
|
197
|
-
const hasGradient = isFilled && !isDisabled && !!
|
|
198
|
-
const borderRadius =
|
|
252
|
+
const hasGradient = isFilled && !isDisabled && !!eff.backgroundGradient;
|
|
253
|
+
const borderRadius = eff.borderRadius ?? 90;
|
|
199
254
|
const inheritedFontFamily = resolveInheritedFontFamily(
|
|
200
|
-
|
|
255
|
+
eff.fontFamily,
|
|
201
256
|
theme.typography.defaultFontFamily
|
|
202
257
|
);
|
|
203
258
|
const resolvedFont = useResolvedFontStyle(
|
|
204
259
|
inheritedFontFamily,
|
|
205
|
-
|
|
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:
|
|
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:
|
|
218
|
-
textAlign:
|
|
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
|
-
<
|
|
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
|
-
<
|
|
244
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
<
|
|
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 ? (
|
|
271
|
-
borderColor: isOutlined ? outlinedBorderColor :
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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),
|