@towsila/expo-ui-library 3.5.0 → 3.5.2
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
CHANGED
|
@@ -1,120 +1,161 @@
|
|
|
1
|
-
// import { useRouter } from "expo-router";
|
|
2
1
|
import React, { useState } from "react";
|
|
3
|
-
import
|
|
4
|
-
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";
|
|
2
|
+
import { ScrollView, Text, TouchableOpacity, View } from "react-native";
|
|
8
3
|
import { Field } from "../../types/formFields";
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
4
|
+
import { useAppTheme } from "../../context/ThemeContext";
|
|
5
|
+
import { useLanguage } from "../../context/LanguageContext";
|
|
6
|
+
import ScreenHeader from "../ui/PageTopBar";
|
|
7
|
+
import { DynamicForm } from "../ui/dynamicForm";
|
|
8
|
+
import { Fonts, textSize } from "../../tokens/typography";
|
|
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 { useFormSubmit } from "../../hooks/useSignup";
|
|
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
|
+
agreeText?: string; // "By continuing, you agree to our"
|
|
32
|
+
termsLinkText?: string; // "Terms & Privacy"
|
|
33
|
+
t?: (key: string) => string;
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
formState,
|
|
37
|
-
setFormState,
|
|
38
|
-
onSubmit,
|
|
39
|
-
onLogin,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
backLabel,
|
|
35
|
+
|
|
36
|
+
export default function SignUpScreen({
|
|
37
|
+
title,
|
|
38
|
+
formState,
|
|
39
|
+
setFormState,
|
|
40
|
+
onSubmit,
|
|
41
|
+
onLogin,
|
|
42
|
+
haveAccountLabel,
|
|
43
|
+
loginLabel,
|
|
44
|
+
buttonLabel,
|
|
45
|
+
fields,
|
|
46
|
+
nextLabel,
|
|
47
|
+
backLabel,
|
|
47
48
|
generalTerms,
|
|
48
49
|
dialogTitle,
|
|
49
50
|
dialogDescription,
|
|
50
51
|
dialogButton,
|
|
52
|
+
agreeText = "By continuing, you agree",
|
|
53
|
+
termsLinkText = "Terms & Privacy",
|
|
51
54
|
t,
|
|
52
55
|
}: SignupProps) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const [showTerms, setShowTerms] = useState(true);
|
|
57
|
+
const { colors } = useAppTheme();
|
|
58
|
+
const { isRTL } = useLanguage();
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
const { handleSubmit, errors, clearError, loading, setErrors } = useFormSubmit({
|
|
58
61
|
fields,
|
|
59
62
|
formState,
|
|
60
63
|
onSubmit,
|
|
61
|
-
});
|
|
64
|
+
});
|
|
65
|
+
|
|
62
66
|
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
|
-
|
|
67
|
+
<View style={{ flex: 1, paddingHorizontal: 25, paddingTop: 50, paddingBottom: 20 }}>
|
|
68
|
+
{/* Fixed: header */}
|
|
69
|
+
<ScreenHeader title={title} description={""} />
|
|
70
|
+
|
|
71
|
+
{/* Scrollable: everything after the header */}
|
|
72
|
+
<ScrollView
|
|
73
|
+
style={{ flex: 1 }}
|
|
74
|
+
showsVerticalScrollIndicator={false}
|
|
75
|
+
keyboardShouldPersistTaps="handled"
|
|
76
|
+
contentContainerStyle={{ paddingBottom: 20 }}
|
|
77
|
+
>
|
|
78
|
+
<DynamicForm
|
|
79
|
+
fields={fields}
|
|
80
|
+
formState={formState}
|
|
81
|
+
setFormState={setFormState}
|
|
82
|
+
errors={errors}
|
|
83
|
+
clearError={clearError}
|
|
84
|
+
nextLabel={nextLabel}
|
|
85
|
+
backLabel={backLabel}
|
|
86
|
+
setErrors={setErrors}
|
|
87
|
+
translator={t}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
{/* Terms notice */}
|
|
91
|
+
<Text
|
|
92
|
+
style={{
|
|
93
|
+
flexDirection: isRTL ? "row" : "row-reverse",
|
|
94
|
+
textAlign: isRTL? "left" : "right",
|
|
95
|
+
fontSize: textSize.xs,
|
|
96
|
+
color: colors.text.secondary,
|
|
97
|
+
fontFamily: Fonts.montserrat.regular,
|
|
98
|
+
marginBottom: 40,
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{agreeText}{" "}
|
|
102
|
+
<Text
|
|
103
|
+
onPress={() => setShowTerms(true)}
|
|
104
|
+
style={{ color: colors.primary, fontFamily: Fonts.montserrat.semiBold, textDecorationLine:"underline" }}
|
|
105
|
+
>
|
|
106
|
+
{termsLinkText}
|
|
107
|
+
</Text>
|
|
108
|
+
</Text>
|
|
109
|
+
|
|
110
|
+
<SharpButton
|
|
111
|
+
text={buttonLabel}
|
|
112
|
+
bgColor={colors.primary}
|
|
113
|
+
onPress={handleSubmit}
|
|
114
|
+
loading={loading}
|
|
115
|
+
height={50}
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<View
|
|
119
|
+
style={{
|
|
120
|
+
flexDirection: isRTL ? "row" : "row-reverse",
|
|
121
|
+
justifyContent: "center",
|
|
122
|
+
alignItems: "center",
|
|
123
|
+
gap: 4,
|
|
124
|
+
marginTop: 15,
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<Text
|
|
128
|
+
style={{
|
|
129
|
+
fontSize: textSize.xs,
|
|
130
|
+
color: colors.text.secondary,
|
|
131
|
+
fontFamily: Fonts.montserrat.regular,
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{haveAccountLabel}
|
|
135
|
+
</Text>
|
|
136
|
+
<TouchableOpacity onPress={onLogin}>
|
|
137
|
+
<Text
|
|
138
|
+
style={{
|
|
139
|
+
fontSize: textSize.xs,
|
|
140
|
+
fontFamily: Fonts.montserrat.regular,
|
|
141
|
+
color: colors.primary,
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{loginLabel}
|
|
145
|
+
</Text>
|
|
146
|
+
</TouchableOpacity>
|
|
147
|
+
</View>
|
|
148
|
+
</ScrollView>
|
|
149
|
+
|
|
150
|
+
{/* Popup lives outside the scroll */}
|
|
151
|
+
<TermsPopup
|
|
152
|
+
title={dialogTitle}
|
|
153
|
+
description={dialogDescription}
|
|
154
|
+
buttonText={dialogButton}
|
|
155
|
+
showTerms={showTerms}
|
|
156
|
+
generalTerms={generalTerms}
|
|
157
|
+
onAgree={() => setShowTerms(false)}
|
|
158
|
+
/>
|
|
159
|
+
</View>
|
|
119
160
|
);
|
|
120
161
|
}
|
|
@@ -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,13 +1,12 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import Input from "./input";
|
|
5
|
+
|
|
6
|
+
type KeyedField = Field & { key?: string };
|
|
7
|
+
|
|
9
8
|
interface Props {
|
|
10
|
-
fields:
|
|
9
|
+
fields: KeyedField[];
|
|
11
10
|
formState: Record<string, string>;
|
|
12
11
|
setFormState: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
13
12
|
errors: Record<string, string>;
|
|
@@ -16,27 +15,7 @@ interface Props {
|
|
|
16
15
|
onStepChange?: (step: number, total: number) => void;
|
|
17
16
|
nextLabel?: string;
|
|
18
17
|
backLabel?: string;
|
|
19
|
-
|
|
20
|
-
// 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;
|
|
18
|
+
translator?: (key: string) => string;
|
|
40
19
|
}
|
|
41
20
|
|
|
42
21
|
export const DynamicForm: React.FC<Props> = ({
|
|
@@ -44,113 +23,60 @@ export const DynamicForm: React.FC<Props> = ({
|
|
|
44
23
|
formState,
|
|
45
24
|
setFormState,
|
|
46
25
|
errors,
|
|
47
|
-
setErrors,
|
|
48
26
|
clearError,
|
|
49
|
-
|
|
50
|
-
nextLabel,
|
|
51
|
-
backLabel,
|
|
27
|
+
translator,
|
|
52
28
|
}) => {
|
|
53
29
|
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
30
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
31
|
+
// Group consecutive fields that share the same `key`
|
|
32
|
+
const groups = fields.reduce<KeyedField[][]>((acc, field) => {
|
|
33
|
+
const last = acc[acc.length - 1];
|
|
34
|
+
if (field.key && last && last[0].key === field.key) {
|
|
35
|
+
last.push(field);
|
|
36
|
+
} else {
|
|
37
|
+
acc.push([field]);
|
|
38
|
+
}
|
|
39
|
+
return acc;
|
|
40
|
+
}, []);
|
|
71
41
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
field.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
secureTextEntry={field.secureTextEntry && !showPasswordField[field.name]}
|
|
96
|
-
returnKeyType="next"
|
|
97
|
-
error={errors[field.name]}
|
|
98
|
-
onClearError={() => clearError(field.name)}
|
|
99
|
-
label={field.label}
|
|
100
|
-
/>
|
|
101
|
-
</View>
|
|
42
|
+
const renderField = (field: KeyedField) => (
|
|
43
|
+
<Input
|
|
44
|
+
value={formState[field.name] || ""}
|
|
45
|
+
onChangeText={(text) => setFormState((prev) => ({ ...prev, [field.name]: text }))}
|
|
46
|
+
iconLeft={field.icon}
|
|
47
|
+
iconRight={
|
|
48
|
+
field.secureTextEntry
|
|
49
|
+
? showPasswordField[field.name] ? "eye-off-outline" : "eye-outline"
|
|
50
|
+
: undefined
|
|
51
|
+
}
|
|
52
|
+
placeholder={field.placeholder}
|
|
53
|
+
onPressIconRight={
|
|
54
|
+
field.secureTextEntry
|
|
55
|
+
? () => setShowPasswordField((prev) => ({ ...prev, [field.name]: !prev[field.name] }))
|
|
56
|
+
: undefined
|
|
57
|
+
}
|
|
58
|
+
secureTextEntry={field.secureTextEntry && !showPasswordField[field.name]}
|
|
59
|
+
returnKeyType="next"
|
|
60
|
+
error={errors[field.name] ? translator?.(errors[field.name]) : ""}
|
|
61
|
+
onClearError={() => clearError(field.name)}
|
|
62
|
+
label={field.label}
|
|
63
|
+
/>
|
|
102
64
|
);
|
|
103
65
|
|
|
104
66
|
return (
|
|
105
|
-
<View style={{ gap: 4
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
67
|
+
<View style={{ gap: 4 }}>
|
|
68
|
+
{groups.map((group) => (
|
|
69
|
+
<View
|
|
70
|
+
key={group.map((f) => f.name).join("-")}
|
|
71
|
+
style={{ flexDirection: "row", gap: 10, marginBottom: 10 }}
|
|
72
|
+
>
|
|
73
|
+
{group.map((field) => (
|
|
74
|
+
<View key={field.name} style={{ flex: 1, minWidth: 0 }}>
|
|
75
|
+
{renderField(field)}
|
|
76
|
+
</View>
|
|
77
|
+
))}
|
|
152
78
|
</View>
|
|
153
|
-
)}
|
|
79
|
+
))}
|
|
154
80
|
</View>
|
|
155
81
|
);
|
|
156
82
|
};
|
|
@@ -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>
|