@towsila/expo-ui-library 3.4.0 → 3.5.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.
- package/package.json +1 -1
- package/src/components/screens/signUpScreen.tsx +116 -102
- package/src/components/ui/customPopup.tsx +5 -3
- package/src/components/ui/dynamicForm.tsx +10 -108
- package/src/components/ui/scrollIndicator.tsx +130 -0
- package/src/components/ui/termsPopup.tsx +6 -2
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -1,120 +1,134 @@
|
|
|
1
|
-
// import { useRouter } from "expo-router";
|
|
2
1
|
import React, { useState } from "react";
|
|
3
|
-
import TemplatePage from "../reusable/templatePage";
|
|
4
2
|
import { Text, TouchableOpacity, View } from "react-native";
|
|
5
|
-
import TermsPopup from "../ui/termsPopup";
|
|
6
|
-
import { Fonts, textSize } from "../../tokens/typography";
|
|
7
|
-
import { useAppTheme } from "../../context/ThemeContext";
|
|
8
3
|
import { Field } from "../../types/formFields";
|
|
4
|
+
import { useAppTheme } from "../../context/ThemeContext";
|
|
5
|
+
import { useLanguage } from "../../context/LanguageContext";
|
|
9
6
|
import { useFormSubmit } from "../../hooks/useSignup";
|
|
10
|
-
import
|
|
7
|
+
import { ScrollWithIndicator } from "../ui/scrollIndicator";
|
|
8
|
+
import { DynamicForm } from "../ui/dynamicForm";
|
|
11
9
|
import SharpButton from "../ui/sharpButton";
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
import TermsPopup from "../ui/termsPopup";
|
|
11
|
+
import { Fonts, textSize } from "../../tokens/typography";
|
|
12
|
+
|
|
13
|
+
interface SignupProps {
|
|
14
|
+
title: string;
|
|
15
|
+
subtitle: string;
|
|
16
|
+
formState: Record<string, string>;
|
|
17
|
+
setFormState: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
18
|
+
onSubmit: (formState: Record<string, string>) => Promise<void>;
|
|
19
|
+
onLogin: () => void;
|
|
20
|
+
errorMessage?: string;
|
|
21
|
+
haveAccountLabel: string;
|
|
22
|
+
loginLabel: string;
|
|
23
|
+
buttonLabel: string;
|
|
24
|
+
fields: Field[];
|
|
25
|
+
nextLabel: string;
|
|
26
|
+
backLabel: string;
|
|
27
|
+
generalTerms: string[];
|
|
28
|
+
dialogTitle: string;
|
|
29
|
+
dialogDescription: string;
|
|
30
|
+
dialogButton: string;
|
|
31
|
+
t?: (key: string) => string;
|
|
32
|
+
appLogo?: React.ReactNode;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
nextLabel,
|
|
46
|
-
backLabel,
|
|
34
|
+
|
|
35
|
+
export default function SignUpScreen({
|
|
36
|
+
formState,
|
|
37
|
+
setFormState,
|
|
38
|
+
onSubmit,
|
|
39
|
+
onLogin,
|
|
40
|
+
haveAccountLabel,
|
|
41
|
+
loginLabel,
|
|
42
|
+
buttonLabel,
|
|
43
|
+
fields,
|
|
44
|
+
nextLabel,
|
|
45
|
+
backLabel,
|
|
47
46
|
generalTerms,
|
|
48
47
|
dialogTitle,
|
|
49
48
|
dialogDescription,
|
|
50
49
|
dialogButton,
|
|
51
50
|
t,
|
|
51
|
+
appLogo,
|
|
52
52
|
}: SignupProps) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const {handleSubmit, errors, clearError, loading, setErrors} = useFormSubmit({
|
|
53
|
+
const [showTerms, setShowTerms] = useState(true);
|
|
54
|
+
const { colors } = useAppTheme();
|
|
55
|
+
const {isRTL} = useLanguage();
|
|
56
|
+
const { handleSubmit, errors, clearError, loading, setErrors } = useFormSubmit({
|
|
58
57
|
fields,
|
|
59
58
|
formState,
|
|
60
59
|
onSubmit,
|
|
61
|
-
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
62
|
return (
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
63
|
+
<View style={{ flex: 1, paddingHorizontal: 25, paddingVertical: 40 }}>
|
|
64
|
+
{/* Fixed: logo */}
|
|
65
|
+
<View style={{ alignItems: "center" }}>{appLogo}</View>
|
|
66
|
+
|
|
67
|
+
{/* Scrollable: form only, with vertical progress indicator */}
|
|
68
|
+
<ScrollWithIndicator
|
|
69
|
+
keyboardShouldPersistTaps="handled"
|
|
70
|
+
>
|
|
71
|
+
<DynamicForm
|
|
72
|
+
fields={fields}
|
|
73
|
+
formState={formState}
|
|
74
|
+
setFormState={setFormState}
|
|
75
|
+
errors={errors}
|
|
76
|
+
clearError={clearError}
|
|
77
|
+
nextLabel={nextLabel}
|
|
78
|
+
backLabel={backLabel}
|
|
79
|
+
setErrors={setErrors}
|
|
80
|
+
translator={t}
|
|
81
|
+
/>
|
|
82
|
+
</ScrollWithIndicator>
|
|
83
|
+
|
|
84
|
+
{/* Fixed: button + login link */}
|
|
85
|
+
<View style={{ gap: 2, paddingTop: 20 }}>
|
|
86
|
+
<SharpButton
|
|
87
|
+
text={buttonLabel}
|
|
88
|
+
bgColor={colors.primary}
|
|
89
|
+
onPress={handleSubmit}
|
|
90
|
+
loading={loading}
|
|
91
|
+
height={50}
|
|
92
|
+
/>
|
|
93
|
+
<TermsPopup
|
|
94
|
+
title={dialogTitle}
|
|
95
|
+
description={dialogDescription}
|
|
96
|
+
buttonText={dialogButton}
|
|
97
|
+
showTerms={showTerms}
|
|
98
|
+
generalTerms={generalTerms}
|
|
99
|
+
onAgree={() => setShowTerms(false)}
|
|
100
|
+
/>
|
|
101
|
+
<View
|
|
102
|
+
style={{
|
|
103
|
+
flexDirection:isRTL ? "row":"row-reverse",
|
|
104
|
+
justifyContent:isRTL ? "flex-end" :"flex-start",
|
|
105
|
+
alignItems: "center",
|
|
106
|
+
gap: 4,
|
|
107
|
+
marginTop: 5,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<Text
|
|
111
|
+
style={{
|
|
112
|
+
fontSize: textSize.xs,
|
|
113
|
+
color: colors.text.secondary,
|
|
114
|
+
fontFamily: Fonts.montserrat.regular,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{haveAccountLabel}
|
|
118
|
+
</Text>
|
|
119
|
+
<TouchableOpacity onPress={onLogin}>
|
|
120
|
+
<Text
|
|
121
|
+
style={{
|
|
122
|
+
fontSize: textSize.xs,
|
|
123
|
+
fontFamily: Fonts.montserrat.semiBold,
|
|
124
|
+
color: colors.primary,
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{loginLabel}
|
|
128
|
+
</Text>
|
|
129
|
+
</TouchableOpacity>
|
|
130
|
+
</View>
|
|
131
|
+
</View>
|
|
132
|
+
</View>
|
|
119
133
|
);
|
|
120
134
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { View, Text, StyleSheet, Modal, Animated, Dimensions, ViewStyle } from "react-native";
|
|
2
|
-
import { useAppTheme } from "../../context/ThemeContext";
|
|
3
|
-
import { textSize, Fonts } from "../../tokens/typography";
|
|
4
2
|
import { useEffect, useRef } from "react";
|
|
3
|
+
import { Fonts, textSize } from "../../tokens/typography";
|
|
4
|
+
import { useAppTheme } from "../../context/ThemeContext";
|
|
5
|
+
import { useLanguage } from "../../context/LanguageContext";
|
|
5
6
|
import SharpButton from "./sharpButton";
|
|
6
7
|
|
|
7
8
|
const { height } = Dimensions.get("window");
|
|
@@ -91,7 +92,7 @@ const handleClose = () => {
|
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
const {isRTL} = useLanguage();
|
|
95
96
|
|
|
96
97
|
return (
|
|
97
98
|
<Modal
|
|
@@ -119,6 +120,7 @@ const handleClose = () => {
|
|
|
119
120
|
fontSize: textSize.xxl,
|
|
120
121
|
color: colors.text.primary,
|
|
121
122
|
marginBottom: 10,
|
|
123
|
+
textAlign:isRTL ? "left" :"right"
|
|
122
124
|
}}>
|
|
123
125
|
{title}
|
|
124
126
|
</Text>
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import Input from "./input";
|
|
2
|
+
import {View } from "react-native";
|
|
4
3
|
import { Field } from "../../types/formFields";
|
|
5
|
-
import
|
|
6
|
-
import { validateForm } from "../../utils/inputValidator/registerValidator";
|
|
7
|
-
import { Fonts, textSize } from "../../tokens/typography";
|
|
8
|
-
import {Arrow, ArrowBack} from "@towsila/icons";
|
|
4
|
+
import Input from "./input";
|
|
9
5
|
interface Props {
|
|
10
6
|
fields: Field[];
|
|
11
7
|
formState: Record<string, string>;
|
|
@@ -16,67 +12,24 @@ interface Props {
|
|
|
16
12
|
onStepChange?: (step: number, total: number) => void;
|
|
17
13
|
nextLabel?: string;
|
|
18
14
|
backLabel?: string;
|
|
15
|
+
translator?: (key: string) => string;
|
|
19
16
|
}
|
|
20
17
|
// can i split it into 2 not only move the password if the number of fields is > 4
|
|
21
|
-
function splitIntoSteps(fields: Field[]): Field[][] {
|
|
22
|
-
const info = fields.filter((f) => f.type !== "password");
|
|
23
|
-
const security = fields.filter((f) => f.type === "password");
|
|
24
|
-
const steps: Field[][] = [];
|
|
25
|
-
|
|
26
|
-
if (info.length > 4) {
|
|
27
|
-
const mid = Math.ceil(info.length / 2);
|
|
28
|
-
steps.push(info.slice(0, mid));
|
|
29
|
-
steps.push(info.slice(mid));
|
|
30
|
-
} else if (info.length > 0) {
|
|
31
|
-
steps.push(info);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Add security fields as separate step
|
|
35
|
-
if (security.length > 0) {
|
|
36
|
-
steps.push(security);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return steps;
|
|
40
|
-
}
|
|
41
18
|
|
|
42
19
|
export const DynamicForm: React.FC<Props> = ({
|
|
43
20
|
fields,
|
|
44
21
|
formState,
|
|
45
22
|
setFormState,
|
|
46
23
|
errors,
|
|
47
|
-
setErrors,
|
|
48
24
|
clearError,
|
|
49
|
-
|
|
50
|
-
nextLabel,
|
|
51
|
-
backLabel,
|
|
25
|
+
translator,
|
|
52
26
|
}) => {
|
|
53
27
|
const [showPasswordField, setShowPasswordField] = useState<Record<string, boolean>>({});
|
|
54
|
-
const [currentStep, setCurrentStep] = useState(0);
|
|
55
|
-
const { colors } = useAppTheme();
|
|
56
|
-
|
|
57
|
-
const steps = splitIntoSteps(fields);
|
|
58
|
-
const totalSteps = steps.length;
|
|
59
|
-
const stepFields = steps[currentStep] ?? [];
|
|
60
|
-
|
|
61
|
-
const goNext = () => {
|
|
62
|
-
// validate current step fields before proceeding
|
|
63
|
-
const newErrors = validateForm(stepFields, formState);
|
|
64
|
-
setErrors((prev) => ({ ...prev, ...newErrors }));
|
|
65
|
-
if (Object.keys(newErrors).length > 0) return;
|
|
66
28
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const goPrev = () => {
|
|
73
|
-
const prev = Math.max(currentStep - 1, 0);
|
|
74
|
-
setCurrentStep(prev);
|
|
75
|
-
onStepChange?.(prev, totalSteps);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const renderInput = (field: Field) => (
|
|
79
|
-
<View key={field.name} style={{ marginBottom: 10 }}>
|
|
29
|
+
return (
|
|
30
|
+
<View style={{ gap: 4, paddingBottom: 20 }}>
|
|
31
|
+
{fields.map((field) =>
|
|
32
|
+
<View key={field.name} style={{ marginBottom: 10 }}>
|
|
80
33
|
<Input
|
|
81
34
|
value={formState[field.name] || ""}
|
|
82
35
|
onChangeText={(text) => setFormState((prev) => ({ ...prev, [field.name]: text }))}
|
|
@@ -94,63 +47,12 @@ export const DynamicForm: React.FC<Props> = ({
|
|
|
94
47
|
}
|
|
95
48
|
secureTextEntry={field.secureTextEntry && !showPasswordField[field.name]}
|
|
96
49
|
returnKeyType="next"
|
|
97
|
-
error={errors[field.name]}
|
|
50
|
+
error={errors[field.name] ? translator?.(errors[field.name]) : ""}
|
|
98
51
|
onClearError={() => clearError(field.name)}
|
|
99
52
|
label={field.label}
|
|
100
53
|
/>
|
|
101
54
|
</View>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<View style={{ gap: 4, paddingBottom: 20 }}>
|
|
106
|
-
|
|
107
|
-
{/* Stepper — only show if more than 1 step */}
|
|
108
|
-
{totalSteps > 1 && (
|
|
109
|
-
<View style={{ flexDirection: "row", alignItems: "center", marginBottom: 12 }}>
|
|
110
|
-
{/* Back arrow */}
|
|
111
|
-
<View style={{ flex: 1 }}>
|
|
112
|
-
{currentStep > 0 && backLabel && (
|
|
113
|
-
<TouchableOpacity onPress={goPrev} style={{ flexDirection: "row", alignItems: "center", justifyContent: "flex-start", gap: 6 }}>
|
|
114
|
-
<ArrowBack width={16} height={16} color={colors.text.secondary} style={[I18nManager.isRTL && { transform: [{ scaleX: -1 }] },]}/>
|
|
115
|
-
<Text style={{ color: colors.text.secondary, fontFamily: Fonts.montserrat.semiBold, fontSize: textSize.sm }}>
|
|
116
|
-
{backLabel}
|
|
117
|
-
</Text>
|
|
118
|
-
</TouchableOpacity>
|
|
119
|
-
)}
|
|
120
|
-
</View>
|
|
121
|
-
|
|
122
|
-
{/* Pills */}
|
|
123
|
-
<View style={{ flexDirection: "row", gap: 8 }}>
|
|
124
|
-
{steps.map((_, i) => (
|
|
125
|
-
<View key={i} style={{
|
|
126
|
-
width: i === currentStep ? 28 : 10,
|
|
127
|
-
height: 6,
|
|
128
|
-
borderRadius: 3,
|
|
129
|
-
backgroundColor: i <= currentStep ? colors.primary : colors.text.placeholder + "40",
|
|
130
|
-
}} />
|
|
131
|
-
))}
|
|
132
|
-
</View>
|
|
133
|
-
<View style={{ flex: 1 }} />
|
|
134
|
-
</View>
|
|
135
|
-
)}
|
|
136
|
-
|
|
137
|
-
{/* Fields */}
|
|
138
|
-
{stepFields.map((f) => renderInput(f))}
|
|
139
|
-
|
|
140
|
-
{/* Navigation */}
|
|
141
|
-
{totalSteps > 1 && (
|
|
142
|
-
<View style={{ gap: 10, marginTop: 4 }}>
|
|
143
|
-
{currentStep < totalSteps - 1 && (
|
|
144
|
-
<TouchableOpacity onPress={goNext} style={{ flexDirection: "row", alignItems: "center", justifyContent: "flex-end", gap: 6 }}>
|
|
145
|
-
<Text style={{ color: colors.text.secondary, fontFamily: Fonts.montserrat.semiBold, fontSize: textSize.sm }}>
|
|
146
|
-
{nextLabel}
|
|
147
|
-
</Text>
|
|
148
|
-
<Arrow width={16} height={16} color={colors.text.secondary} style={[I18nManager.isRTL && { transform: [{ scaleX: -1 }] },]}/>
|
|
149
|
-
</TouchableOpacity>
|
|
150
|
-
)}
|
|
151
|
-
|
|
152
|
-
</View>
|
|
153
|
-
)}
|
|
55
|
+
)}
|
|
154
56
|
</View>
|
|
155
57
|
);
|
|
156
58
|
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
LayoutChangeEvent,
|
|
5
|
+
ScrollViewProps,
|
|
6
|
+
View,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { useAppTheme } from "../../context/ThemeContext";
|
|
9
|
+
import { useLanguage } from "../../context/LanguageContext";
|
|
10
|
+
|
|
11
|
+
interface Props extends ScrollViewProps {
|
|
12
|
+
trackWidth?: number;
|
|
13
|
+
trackLength?: number;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const IDLE_OPACITY = 0.45;
|
|
18
|
+
|
|
19
|
+
export const ScrollWithIndicator: React.FC<Props> = ({
|
|
20
|
+
children,
|
|
21
|
+
trackWidth = 5,
|
|
22
|
+
trackLength = 200,
|
|
23
|
+
style,
|
|
24
|
+
contentContainerStyle,
|
|
25
|
+
...rest
|
|
26
|
+
}) => {
|
|
27
|
+
const { colors } = useAppTheme();
|
|
28
|
+
const { isRTL } = useLanguage();
|
|
29
|
+
|
|
30
|
+
const scrollY = useRef(new Animated.Value(0)).current;
|
|
31
|
+
const fade = useRef(new Animated.Value(IDLE_OPACITY)).current;
|
|
32
|
+
const awake = useRef(false);
|
|
33
|
+
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
34
|
+
|
|
35
|
+
const [viewH, setViewH] = useState(0);
|
|
36
|
+
const [contentH, setContentH] = useState(0);
|
|
37
|
+
|
|
38
|
+
const scrollable = viewH > 0 && contentH > viewH + 1;
|
|
39
|
+
|
|
40
|
+
// short fixed-length track instead of full height
|
|
41
|
+
const trackH = Math.min(trackLength, viewH);
|
|
42
|
+
const thumbH = scrollable
|
|
43
|
+
? Math.min(trackH * 0.7, Math.max(24, (viewH / contentH) * trackH))
|
|
44
|
+
: 0;
|
|
45
|
+
const maxScroll = Math.max(contentH - viewH, 1);
|
|
46
|
+
const maxThumb = Math.max(trackH - thumbH, 1);
|
|
47
|
+
|
|
48
|
+
const translateY = scrollY.interpolate({
|
|
49
|
+
inputRange: [0, maxScroll],
|
|
50
|
+
outputRange: [0, maxThumb],
|
|
51
|
+
extrapolate: "clamp",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// brighter while scrolling, softer when idle
|
|
55
|
+
const wake = () => {
|
|
56
|
+
if (hideTimer.current) clearTimeout(hideTimer.current);
|
|
57
|
+
if (!awake.current) {
|
|
58
|
+
awake.current = true;
|
|
59
|
+
Animated.timing(fade, { toValue: 1, duration: 120, useNativeDriver: true }).start();
|
|
60
|
+
}
|
|
61
|
+
hideTimer.current = setTimeout(() => {
|
|
62
|
+
awake.current = false;
|
|
63
|
+
Animated.timing(fade, { toValue: IDLE_OPACITY, duration: 400, useNativeDriver: true }).start();
|
|
64
|
+
}, 900);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
return () => {
|
|
69
|
+
if (hideTimer.current) clearTimeout(hideTimer.current);
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<View style={[{ flex: 1, flexDirection: isRTL ? "row" : "row-reverse" }, style]}>
|
|
75
|
+
<Animated.ScrollView
|
|
76
|
+
{...rest}
|
|
77
|
+
style={{ flex: 1 }}
|
|
78
|
+
contentContainerStyle={contentContainerStyle}
|
|
79
|
+
showsVerticalScrollIndicator={false}
|
|
80
|
+
scrollEventThrottle={10}
|
|
81
|
+
onLayout={(e: LayoutChangeEvent) => setViewH(e.nativeEvent.layout.height)}
|
|
82
|
+
onContentSizeChange={(_, h) => setContentH(h)}
|
|
83
|
+
onScroll={Animated.event(
|
|
84
|
+
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
|
|
85
|
+
{ useNativeDriver: true, listener: wake }
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{children}
|
|
89
|
+
</Animated.ScrollView>
|
|
90
|
+
|
|
91
|
+
{scrollable && (
|
|
92
|
+
<Animated.View
|
|
93
|
+
pointerEvents="none"
|
|
94
|
+
style={[
|
|
95
|
+
{
|
|
96
|
+
width: trackWidth,
|
|
97
|
+
height: trackH,
|
|
98
|
+
alignSelf: "center",
|
|
99
|
+
opacity: fade,
|
|
100
|
+
},
|
|
101
|
+
isRTL ? { marginLeft: 15 } : { marginRight: 15 },
|
|
102
|
+
]}
|
|
103
|
+
>
|
|
104
|
+
{/* soft track */}
|
|
105
|
+
<View
|
|
106
|
+
style={{
|
|
107
|
+
position: "absolute",
|
|
108
|
+
top: 0,
|
|
109
|
+
bottom: 0,
|
|
110
|
+
width: trackWidth,
|
|
111
|
+
borderRadius: trackWidth / 2,
|
|
112
|
+
backgroundColor: colors.primary,
|
|
113
|
+
opacity: 0.15,
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
{/* thumb */}
|
|
117
|
+
<Animated.View
|
|
118
|
+
style={{
|
|
119
|
+
width: trackWidth,
|
|
120
|
+
height: thumbH,
|
|
121
|
+
borderRadius: trackWidth / 2,
|
|
122
|
+
backgroundColor: colors.primary,
|
|
123
|
+
transform: [{ translateY }],
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</Animated.View>
|
|
127
|
+
)}
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ScrollView, Text, View } from "react-native";
|
|
2
2
|
import CustomPopUp from "./customPopup";
|
|
3
|
-
import { Fonts, textSize } from "../../tokens/typography";
|
|
4
3
|
import { useAppTheme } from "../../context/ThemeContext";
|
|
4
|
+
import { useLanguage } from "../../context/LanguageContext";
|
|
5
|
+
import { Fonts, textSize } from "../../tokens/typography";
|
|
5
6
|
|
|
6
7
|
interface TermsPopupProps{
|
|
7
8
|
showTerms: boolean;
|
|
@@ -13,6 +14,7 @@ interface TermsPopupProps{
|
|
|
13
14
|
}
|
|
14
15
|
export default function TermsPopup({showTerms, generalTerms, onAgree, title, description, buttonText}: TermsPopupProps){
|
|
15
16
|
const {colors} = useAppTheme();
|
|
17
|
+
const {isRTL} = useLanguage();
|
|
16
18
|
return (
|
|
17
19
|
<CustomPopUp
|
|
18
20
|
visible={showTerms}
|
|
@@ -23,6 +25,7 @@ export default function TermsPopup({showTerms, generalTerms, onAgree, title, des
|
|
|
23
25
|
fontSize: textSize.sm,
|
|
24
26
|
color: colors.text.secondary,
|
|
25
27
|
marginBottom: 4,
|
|
28
|
+
textAlign:isRTL ? "left" :"right"
|
|
26
29
|
}}>
|
|
27
30
|
{description}
|
|
28
31
|
</Text>
|
|
@@ -34,7 +37,7 @@ export default function TermsPopup({showTerms, generalTerms, onAgree, title, des
|
|
|
34
37
|
showsVerticalScrollIndicator={false}
|
|
35
38
|
>
|
|
36
39
|
{generalTerms.map((term: string, i: number) => (
|
|
37
|
-
<View key={i} style={{ flexDirection: "row", gap: 8, marginBottom: 8 }}>
|
|
40
|
+
<View key={i} style={{ flexDirection: isRTL ? "row" : "row-reverse", gap: 8, marginBottom: 8 }}>
|
|
38
41
|
<Text style={{ color: colors.primary }}>•</Text>
|
|
39
42
|
<Text style={{
|
|
40
43
|
fontFamily: Fonts.montserrat.regular,
|
|
@@ -42,6 +45,7 @@ export default function TermsPopup({showTerms, generalTerms, onAgree, title, des
|
|
|
42
45
|
color: colors.text.secondary,
|
|
43
46
|
flex: 1,
|
|
44
47
|
lineHeight: 20,
|
|
48
|
+
textAlign:isRTL ? "left" :"right"
|
|
45
49
|
}}>
|
|
46
50
|
{term}
|
|
47
51
|
</Text>
|
package/src/index.ts
CHANGED
|
@@ -67,4 +67,5 @@ export {useArrivalDetection} from "./hooks/useArrivalDetection";
|
|
|
67
67
|
export {default as Toggle} from "./components/ui/toggle";
|
|
68
68
|
export {default as BackIcon} from "./components/ui/backIcon";
|
|
69
69
|
export {default as SuccessScreen} from "./components/screens/sucessScreen";
|
|
70
|
-
export {default as AnimatedSuccessIcon} from "./components/ui/AnimatedSuccessIcon";
|
|
70
|
+
export {default as AnimatedSuccessIcon} from "./components/ui/AnimatedSuccessIcon";
|
|
71
|
+
export {ScrollWithIndicator} from "./components/ui/scrollIndicator"
|