@umituz/react-native-subscription 2.27.146 → 2.27.148
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/domains/subscription/presentation/components/details/PremiumDetailsCard.constants.ts +1 -0
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.tsx +15 -75
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCardActions.tsx +46 -0
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCardHeader.tsx +32 -0
- package/src/domains/subscription/presentation/components/details/premiumDetailsHelpers.ts +5 -0
- package/src/domains/subscription/presentation/components/feedback/FeedbackOption.styles.ts +34 -0
- package/src/domains/subscription/presentation/components/feedback/FeedbackOption.tsx +12 -91
- package/src/domains/subscription/presentation/components/feedback/FeedbackOption.types.ts +9 -0
- package/src/domains/subscription/presentation/components/feedback/FeedbackRadioButton.tsx +28 -0
- package/src/domains/subscription/presentation/components/feedback/FeedbackTextInput.tsx +40 -0
- package/src/domains/subscription/presentation/components/feedback/feedbackOptionConstants.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.27.
|
|
3
|
+
"version": "2.27.148",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DAYS_REMAINING_WARNING_THRESHOLD = 7;
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Premium Details Card
|
|
3
|
-
* Generic component for displaying subscription details
|
|
4
|
-
* Accepts credits via props for app-specific customization
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import React from "react";
|
|
8
|
-
import { View
|
|
2
|
+
import { View } from "react-native";
|
|
9
3
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
10
|
-
import { PremiumStatusBadge } from "./PremiumStatusBadge";
|
|
11
4
|
import { DetailRow } from "./DetailRow";
|
|
12
5
|
import { CreditRow } from "./CreditRow";
|
|
13
6
|
import { styles } from "./PremiumDetailsCard.styles";
|
|
14
7
|
import type { PremiumDetailsCardProps } from "./PremiumDetailsCardTypes";
|
|
8
|
+
import { PremiumDetailsCardHeader } from "./PremiumDetailsCardHeader";
|
|
9
|
+
import { PremiumDetailsCardActions } from "./PremiumDetailsCardActions";
|
|
10
|
+
import { shouldHighlightExpiration } from "./premiumDetailsHelpers";
|
|
15
11
|
|
|
16
12
|
export type { CreditInfo, PremiumDetailsTranslations, PremiumDetailsCardProps } from "./PremiumDetailsCardTypes";
|
|
17
13
|
|
|
@@ -32,60 +28,26 @@ export const PremiumDetailsCard: React.FC<PremiumDetailsCardProps> = ({
|
|
|
32
28
|
|
|
33
29
|
return (
|
|
34
30
|
<View style={[styles.card, { backgroundColor: tokens.colors.surface }]}>
|
|
35
|
-
{(isPremium || showCredits) &&
|
|
36
|
-
<View style={styles.header}>
|
|
37
|
-
<View style={styles.headerTitleContainer}>
|
|
38
|
-
<AtomicText type="titleLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
39
|
-
{translations.title}
|
|
40
|
-
</AtomicText>
|
|
41
|
-
</View>
|
|
42
|
-
<PremiumStatusBadge
|
|
43
|
-
status={statusType}
|
|
44
|
-
activeLabel={translations.statusActive}
|
|
45
|
-
expiredLabel={translations.statusExpired}
|
|
46
|
-
noneLabel={translations.statusInactive}
|
|
47
|
-
canceledLabel={translations.statusCanceled}
|
|
48
|
-
/>
|
|
49
|
-
</View>
|
|
50
|
-
)}
|
|
31
|
+
{(isPremium || showCredits) && <PremiumDetailsCardHeader statusType={statusType} translations={translations} />}
|
|
51
32
|
|
|
52
33
|
|
|
53
34
|
{isPremium && (
|
|
54
35
|
<View style={styles.detailsSection}>
|
|
55
36
|
{isLifetime && translations.lifetimeLabel ? (
|
|
56
|
-
<DetailRow
|
|
57
|
-
label={translations.statusLabel}
|
|
58
|
-
value={translations.lifetimeLabel}
|
|
59
|
-
/>
|
|
37
|
+
<DetailRow label={translations.statusLabel} value={translations.lifetimeLabel} />
|
|
60
38
|
) : (
|
|
61
39
|
<>
|
|
62
40
|
{expirationDate && (
|
|
63
|
-
<DetailRow
|
|
64
|
-
label={translations.expiresLabel}
|
|
65
|
-
value={expirationDate}
|
|
66
|
-
highlight={
|
|
67
|
-
daysRemaining !== null &&
|
|
68
|
-
daysRemaining !== undefined &&
|
|
69
|
-
daysRemaining > 0 &&
|
|
70
|
-
daysRemaining <= 7
|
|
71
|
-
}
|
|
72
|
-
/>
|
|
73
|
-
)}
|
|
74
|
-
{purchaseDate && (
|
|
75
|
-
<DetailRow
|
|
76
|
-
label={translations.purchasedLabel}
|
|
77
|
-
value={purchaseDate}
|
|
78
|
-
/>
|
|
41
|
+
<DetailRow label={translations.expiresLabel} value={expirationDate} highlight={shouldHighlightExpiration(daysRemaining)} />
|
|
79
42
|
)}
|
|
43
|
+
{purchaseDate && <DetailRow label={translations.purchasedLabel} value={purchaseDate} />}
|
|
80
44
|
</>
|
|
81
45
|
)}
|
|
82
46
|
</View>
|
|
83
47
|
)}
|
|
84
48
|
|
|
85
49
|
{showCredits && (
|
|
86
|
-
<View
|
|
87
|
-
style={[styles.creditsSection, { borderTopColor: tokens.colors.border }]}
|
|
88
|
-
>
|
|
50
|
+
<View style={[styles.creditsSection, { borderTopColor: tokens.colors.border }]}>
|
|
89
51
|
{translations.creditsTitle && (
|
|
90
52
|
<AtomicText type="labelMedium" style={[styles.sectionTitle, { color: tokens.colors.textPrimary }]}>
|
|
91
53
|
{translations.creditsTitle}
|
|
@@ -103,34 +65,12 @@ export const PremiumDetailsCard: React.FC<PremiumDetailsCardProps> = ({
|
|
|
103
65
|
</View>
|
|
104
66
|
)}
|
|
105
67
|
|
|
106
|
-
<
|
|
107
|
-
{isPremium
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
]}
|
|
113
|
-
onPress={onManageSubscription}
|
|
114
|
-
>
|
|
115
|
-
<AtomicText type="labelLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
116
|
-
{translations.manageButton}
|
|
117
|
-
</AtomicText>
|
|
118
|
-
</TouchableOpacity>
|
|
119
|
-
)}
|
|
120
|
-
{!isPremium && onUpgrade && translations.upgradeButton && (
|
|
121
|
-
<TouchableOpacity
|
|
122
|
-
style={[styles.premiumButton, { backgroundColor: tokens.colors.primary }]}
|
|
123
|
-
onPress={onUpgrade}
|
|
124
|
-
>
|
|
125
|
-
<AtomicText
|
|
126
|
-
type="titleMedium"
|
|
127
|
-
style={{ color: tokens.colors.onPrimary, fontWeight: "700" }}
|
|
128
|
-
>
|
|
129
|
-
{translations.upgradeButton}
|
|
130
|
-
</AtomicText>
|
|
131
|
-
</TouchableOpacity>
|
|
132
|
-
)}
|
|
133
|
-
</View>
|
|
68
|
+
<PremiumDetailsCardActions
|
|
69
|
+
isPremium={isPremium}
|
|
70
|
+
onManageSubscription={onManageSubscription}
|
|
71
|
+
onUpgrade={onUpgrade}
|
|
72
|
+
translations={translations}
|
|
73
|
+
/>
|
|
134
74
|
</View>
|
|
135
75
|
);
|
|
136
76
|
};
|
package/src/domains/subscription/presentation/components/details/PremiumDetailsCardActions.tsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, TouchableOpacity } from "react-native";
|
|
3
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
4
|
+
import { styles } from "./PremiumDetailsCard.styles";
|
|
5
|
+
import type { PremiumDetailsTranslations } from "./PremiumDetailsCardTypes";
|
|
6
|
+
|
|
7
|
+
interface PremiumDetailsCardActionsProps {
|
|
8
|
+
isPremium: boolean;
|
|
9
|
+
onManageSubscription?: () => void;
|
|
10
|
+
onUpgrade?: () => void;
|
|
11
|
+
translations: PremiumDetailsTranslations;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const PremiumDetailsCardActions: React.FC<PremiumDetailsCardActionsProps> = ({
|
|
15
|
+
isPremium,
|
|
16
|
+
onManageSubscription,
|
|
17
|
+
onUpgrade,
|
|
18
|
+
translations,
|
|
19
|
+
}) => {
|
|
20
|
+
const tokens = useAppDesignTokens();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View style={styles.actionsSection}>
|
|
24
|
+
{isPremium && onManageSubscription && translations.manageButton && (
|
|
25
|
+
<TouchableOpacity
|
|
26
|
+
style={[styles.secondaryButton, { backgroundColor: tokens.colors.surfaceSecondary }]}
|
|
27
|
+
onPress={onManageSubscription}
|
|
28
|
+
>
|
|
29
|
+
<AtomicText type="labelLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
30
|
+
{translations.manageButton}
|
|
31
|
+
</AtomicText>
|
|
32
|
+
</TouchableOpacity>
|
|
33
|
+
)}
|
|
34
|
+
{!isPremium && onUpgrade && translations.upgradeButton && (
|
|
35
|
+
<TouchableOpacity
|
|
36
|
+
style={[styles.premiumButton, { backgroundColor: tokens.colors.primary }]}
|
|
37
|
+
onPress={onUpgrade}
|
|
38
|
+
>
|
|
39
|
+
<AtomicText type="titleMedium" style={{ color: tokens.colors.onPrimary, fontWeight: "700" }}>
|
|
40
|
+
{translations.upgradeButton}
|
|
41
|
+
</AtomicText>
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
)}
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
4
|
+
import { PremiumStatusBadge } from "./PremiumStatusBadge";
|
|
5
|
+
import { styles } from "./PremiumDetailsCard.styles";
|
|
6
|
+
import type { PremiumDetailsTranslations } from "./PremiumDetailsCardTypes";
|
|
7
|
+
|
|
8
|
+
interface PremiumDetailsCardHeaderProps {
|
|
9
|
+
statusType: "active" | "expired" | "none" | "canceled";
|
|
10
|
+
translations: PremiumDetailsTranslations;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const PremiumDetailsCardHeader: React.FC<PremiumDetailsCardHeaderProps> = ({ statusType, translations }) => {
|
|
14
|
+
const tokens = useAppDesignTokens();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<View style={styles.header}>
|
|
18
|
+
<View style={styles.headerTitleContainer}>
|
|
19
|
+
<AtomicText type="titleLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
20
|
+
{translations.title}
|
|
21
|
+
</AtomicText>
|
|
22
|
+
</View>
|
|
23
|
+
<PremiumStatusBadge
|
|
24
|
+
status={statusType}
|
|
25
|
+
activeLabel={translations.statusActive}
|
|
26
|
+
expiredLabel={translations.statusExpired}
|
|
27
|
+
noneLabel={translations.statusInactive}
|
|
28
|
+
canceledLabel={translations.statusCanceled}
|
|
29
|
+
/>
|
|
30
|
+
</View>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { DAYS_REMAINING_WARNING_THRESHOLD } from "./PremiumDetailsCard.constants";
|
|
2
|
+
|
|
3
|
+
export const shouldHighlightExpiration = (daysRemaining: number | null | undefined): boolean => {
|
|
4
|
+
return daysRemaining !== null && daysRemaining !== undefined && daysRemaining > 0 && daysRemaining <= DAYS_REMAINING_WARNING_THRESHOLD;
|
|
5
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
|
|
3
|
+
export const feedbackOptionStyles = StyleSheet.create({
|
|
4
|
+
optionRow: {
|
|
5
|
+
flexDirection: "row",
|
|
6
|
+
alignItems: "center",
|
|
7
|
+
justifyContent: "space-between",
|
|
8
|
+
},
|
|
9
|
+
optionText: {
|
|
10
|
+
flex: 1,
|
|
11
|
+
marginRight: 12,
|
|
12
|
+
},
|
|
13
|
+
radioButton: {
|
|
14
|
+
width: 22,
|
|
15
|
+
height: 22,
|
|
16
|
+
borderRadius: 11,
|
|
17
|
+
borderWidth: 2,
|
|
18
|
+
justifyContent: "center",
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
},
|
|
21
|
+
radioButtonInner: {
|
|
22
|
+
width: 12,
|
|
23
|
+
height: 12,
|
|
24
|
+
borderRadius: 6,
|
|
25
|
+
},
|
|
26
|
+
inputContainer: {
|
|
27
|
+
padding: 12,
|
|
28
|
+
},
|
|
29
|
+
textInput: {
|
|
30
|
+
fontSize: 15,
|
|
31
|
+
borderWidth: 1,
|
|
32
|
+
borderColor: "#ccc",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feedback Option Component
|
|
3
|
-
* Single feedback option with radio button and optional text input
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React from "react";
|
|
7
|
-
import { View, TouchableOpacity
|
|
2
|
+
import { View, TouchableOpacity } from "react-native";
|
|
8
3
|
import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
import type { FeedbackOptionProps } from "./FeedbackOption.types";
|
|
5
|
+
import { feedbackOptionStyles } from "./FeedbackOption.styles";
|
|
6
|
+
import { FEEDBACK_OPTION_OPACITY } from "./feedbackOptionConstants";
|
|
7
|
+
import { FeedbackRadioButton } from "./FeedbackRadioButton";
|
|
8
|
+
import { FeedbackTextInput } from "./FeedbackTextInput";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
isSelected: boolean;
|
|
12
|
-
text: string;
|
|
13
|
-
showInput: boolean;
|
|
14
|
-
placeholder: string;
|
|
15
|
-
inputValue: string;
|
|
16
|
-
onSelect: () => void;
|
|
17
|
-
onChangeText: (text: string) => void;
|
|
18
|
-
}
|
|
10
|
+
export type { FeedbackOptionProps } from "./FeedbackOption.types";
|
|
19
11
|
|
|
20
12
|
export const FeedbackOption: React.FC<FeedbackOptionProps> = React.memo(({
|
|
21
13
|
isSelected,
|
|
@@ -39,7 +31,7 @@ export const FeedbackOption: React.FC<FeedbackOptionProps> = React.memo(({
|
|
|
39
31
|
<View style={containerStyle}>
|
|
40
32
|
<TouchableOpacity
|
|
41
33
|
style={[
|
|
42
|
-
|
|
34
|
+
feedbackOptionStyles.optionRow,
|
|
43
35
|
{
|
|
44
36
|
borderBottomWidth: showInput ? 1 : 0,
|
|
45
37
|
borderBottomColor: tokens.colors.border,
|
|
@@ -48,92 +40,21 @@ export const FeedbackOption: React.FC<FeedbackOptionProps> = React.memo(({
|
|
|
48
40
|
},
|
|
49
41
|
]}
|
|
50
42
|
onPress={onSelect}
|
|
51
|
-
activeOpacity={
|
|
43
|
+
activeOpacity={FEEDBACK_OPTION_OPACITY}
|
|
52
44
|
>
|
|
53
45
|
<AtomicText
|
|
54
46
|
type="bodyMedium"
|
|
55
|
-
style={[
|
|
56
|
-
styles.optionText,
|
|
57
|
-
isSelected && { color: tokens.colors.primary, fontWeight: "600" },
|
|
58
|
-
]}
|
|
47
|
+
style={[feedbackOptionStyles.optionText, isSelected && { color: tokens.colors.primary, fontWeight: "600" }]}
|
|
59
48
|
>
|
|
60
49
|
{text}
|
|
61
50
|
</AtomicText>
|
|
62
51
|
|
|
63
|
-
<
|
|
64
|
-
style={[
|
|
65
|
-
styles.radioButton,
|
|
66
|
-
{
|
|
67
|
-
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
68
|
-
backgroundColor: isSelected ? tokens.colors.primary : "transparent",
|
|
69
|
-
},
|
|
70
|
-
]}
|
|
71
|
-
>
|
|
72
|
-
{isSelected && (
|
|
73
|
-
<View style={[styles.radioButtonInner, { backgroundColor: tokens.colors.primary }]} />
|
|
74
|
-
)}
|
|
75
|
-
</View>
|
|
52
|
+
<FeedbackRadioButton isSelected={isSelected} />
|
|
76
53
|
</TouchableOpacity>
|
|
77
54
|
|
|
78
|
-
{showInput &&
|
|
79
|
-
<View style={styles.inputContainer}>
|
|
80
|
-
<TextInput
|
|
81
|
-
style={[
|
|
82
|
-
styles.textInput,
|
|
83
|
-
{
|
|
84
|
-
backgroundColor: tokens.colors.surface,
|
|
85
|
-
borderRadius: tokens.borderRadius.sm,
|
|
86
|
-
padding: tokens.spacing.sm,
|
|
87
|
-
color: tokens.colors.textPrimary,
|
|
88
|
-
minHeight: 80,
|
|
89
|
-
textAlignVertical: "top",
|
|
90
|
-
},
|
|
91
|
-
]}
|
|
92
|
-
placeholder={placeholder}
|
|
93
|
-
placeholderTextColor={tokens.colors.textTertiary}
|
|
94
|
-
multiline
|
|
95
|
-
maxLength={200}
|
|
96
|
-
value={inputValue}
|
|
97
|
-
onChangeText={onChangeText}
|
|
98
|
-
autoFocus
|
|
99
|
-
/>
|
|
100
|
-
</View>
|
|
101
|
-
)}
|
|
55
|
+
{showInput && <FeedbackTextInput placeholder={placeholder} value={inputValue} onChangeText={onChangeText} />}
|
|
102
56
|
</View>
|
|
103
57
|
);
|
|
104
58
|
});
|
|
105
59
|
|
|
106
60
|
FeedbackOption.displayName = "FeedbackOption";
|
|
107
|
-
|
|
108
|
-
const styles = StyleSheet.create({
|
|
109
|
-
optionRow: {
|
|
110
|
-
flexDirection: "row",
|
|
111
|
-
alignItems: "center",
|
|
112
|
-
justifyContent: "space-between",
|
|
113
|
-
},
|
|
114
|
-
optionText: {
|
|
115
|
-
flex: 1,
|
|
116
|
-
marginRight: 12,
|
|
117
|
-
},
|
|
118
|
-
radioButton: {
|
|
119
|
-
width: 22,
|
|
120
|
-
height: 22,
|
|
121
|
-
borderRadius: 11,
|
|
122
|
-
borderWidth: 2,
|
|
123
|
-
justifyContent: "center",
|
|
124
|
-
alignItems: "center",
|
|
125
|
-
},
|
|
126
|
-
radioButtonInner: {
|
|
127
|
-
width: 12,
|
|
128
|
-
height: 12,
|
|
129
|
-
borderRadius: 6,
|
|
130
|
-
},
|
|
131
|
-
inputContainer: {
|
|
132
|
-
padding: 12,
|
|
133
|
-
},
|
|
134
|
-
textInput: {
|
|
135
|
-
fontSize: 15,
|
|
136
|
-
borderWidth: 1,
|
|
137
|
-
borderColor: "#ccc",
|
|
138
|
-
},
|
|
139
|
-
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
import { feedbackOptionStyles } from "./FeedbackOption.styles";
|
|
5
|
+
|
|
6
|
+
interface FeedbackRadioButtonProps {
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FeedbackRadioButton: React.FC<FeedbackRadioButtonProps> = ({ isSelected }) => {
|
|
11
|
+
const tokens = useAppDesignTokens();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<View
|
|
15
|
+
style={[
|
|
16
|
+
feedbackOptionStyles.radioButton,
|
|
17
|
+
{
|
|
18
|
+
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
19
|
+
backgroundColor: isSelected ? tokens.colors.primary : "transparent",
|
|
20
|
+
},
|
|
21
|
+
]}
|
|
22
|
+
>
|
|
23
|
+
{isSelected && (
|
|
24
|
+
<View style={[feedbackOptionStyles.radioButtonInner, { backgroundColor: tokens.colors.primary }]} />
|
|
25
|
+
)}
|
|
26
|
+
</View>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, TextInput } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
import { feedbackOptionStyles } from "./FeedbackOption.styles";
|
|
5
|
+
import { FEEDBACK_INPUT_MAX_LENGTH, FEEDBACK_INPUT_MIN_HEIGHT } from "./feedbackOptionConstants";
|
|
6
|
+
|
|
7
|
+
interface FeedbackTextInputProps {
|
|
8
|
+
placeholder: string;
|
|
9
|
+
value: string;
|
|
10
|
+
onChangeText: (text: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const FeedbackTextInput: React.FC<FeedbackTextInputProps> = ({ placeholder, value, onChangeText }) => {
|
|
14
|
+
const tokens = useAppDesignTokens();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<View style={feedbackOptionStyles.inputContainer}>
|
|
18
|
+
<TextInput
|
|
19
|
+
style={[
|
|
20
|
+
feedbackOptionStyles.textInput,
|
|
21
|
+
{
|
|
22
|
+
backgroundColor: tokens.colors.surface,
|
|
23
|
+
borderRadius: tokens.borderRadius.sm,
|
|
24
|
+
padding: tokens.spacing.sm,
|
|
25
|
+
color: tokens.colors.textPrimary,
|
|
26
|
+
minHeight: FEEDBACK_INPUT_MIN_HEIGHT,
|
|
27
|
+
textAlignVertical: "top",
|
|
28
|
+
},
|
|
29
|
+
]}
|
|
30
|
+
placeholder={placeholder}
|
|
31
|
+
placeholderTextColor={tokens.colors.textTertiary}
|
|
32
|
+
multiline
|
|
33
|
+
maxLength={FEEDBACK_INPUT_MAX_LENGTH}
|
|
34
|
+
value={value}
|
|
35
|
+
onChangeText={onChangeText}
|
|
36
|
+
autoFocus
|
|
37
|
+
/>
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
};
|