@umituz/react-native-subscription 2.11.27 → 2.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.11.27",
3
+ "version": "2.12.0",
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",
@@ -47,7 +47,7 @@
47
47
  "devDependencies": {
48
48
  "@tanstack/react-query": "^5.0.0",
49
49
  "@types/react": "~19.1.10",
50
- "@umituz/react-native-design-system": "^2.1.3",
50
+ "@umituz/react-native-design-system": "^2.2.0",
51
51
  "@umituz/react-native-firebase": "*",
52
52
  "@umituz/react-native-legal": "*",
53
53
  "@umituz/react-native-localization": "*",
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
- import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
3
+ import { AtomicText, useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
4
4
  import { LinearGradient } from "expo-linear-gradient";
5
5
 
6
6
  interface BestValueBadgeProps {
@@ -11,6 +11,10 @@ interface BestValueBadgeProps {
11
11
  export const BestValueBadge: React.FC<BestValueBadgeProps> = React.memo(
12
12
  ({ text, visible = true }) => {
13
13
  const tokens = useAppDesignTokens();
14
+ const { spacingMultiplier, getFontSize } = useResponsive();
15
+
16
+ const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
17
+ const fontSize = getFontSize(10);
14
18
 
15
19
  if (!visible) return null;
16
20
 
@@ -24,7 +28,7 @@ export const BestValueBadge: React.FC<BestValueBadgeProps> = React.memo(
24
28
  >
25
29
  <AtomicText
26
30
  type="labelSmall"
27
- style={{ color: tokens.colors.onPrimary, fontWeight: "800", textTransform: "uppercase", fontSize: 10 }}
31
+ style={{ color: tokens.colors.onPrimary, fontWeight: "800", textTransform: "uppercase", fontSize }}
28
32
  >
29
33
  {text}
30
34
  </AtomicText>
@@ -36,19 +40,20 @@ export const BestValueBadge: React.FC<BestValueBadgeProps> = React.memo(
36
40
 
37
41
  BestValueBadge.displayName = "BestValueBadge";
38
42
 
39
- const styles = StyleSheet.create({
40
- badgeContainer: {
41
- position: "absolute",
42
- top: -12,
43
- right: 16,
44
- zIndex: 1,
45
- alignSelf: "flex-end",
46
- },
47
- badge: {
48
- paddingHorizontal: 16,
49
- paddingVertical: 6,
50
- borderRadius: 16,
51
- alignItems: "center",
52
- justifyContent: "center",
53
- },
54
- });
43
+ const createStyles = (spacingMult: number) =>
44
+ StyleSheet.create({
45
+ badgeContainer: {
46
+ position: "absolute",
47
+ top: -12 * spacingMult,
48
+ right: 16 * spacingMult,
49
+ zIndex: 1,
50
+ alignSelf: "flex-end",
51
+ },
52
+ badge: {
53
+ paddingHorizontal: 16 * spacingMult,
54
+ paddingVertical: 6 * spacingMult,
55
+ borderRadius: 16 * spacingMult,
56
+ alignItems: "center",
57
+ justifyContent: "center",
58
+ },
59
+ });
@@ -5,8 +5,7 @@
5
5
 
6
6
  import React from "react";
7
7
  import { View, StyleSheet, ScrollView } from "react-native";
8
- import { AtomicText, AtomicButton } from "@umituz/react-native-design-system";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
8
+ import { AtomicText, AtomicButton, Grid, useResponsiveDesignTokens } from "@umituz/react-native-design-system";
10
9
  import { useLocalization } from "@umituz/react-native-localization";
11
10
  import { CreditsPackageCard } from "./CreditsPackageCard";
12
11
  import { PaywallLegalFooter } from "./PaywallLegalFooter";
@@ -38,7 +37,7 @@ export const CreditsTabContent: React.FC<CreditsTabContentProps> = React.memo(
38
37
  creditsInfoText,
39
38
  processingText,
40
39
  }) => {
41
- const tokens = useAppDesignTokens();
40
+ const tokens = useResponsiveDesignTokens();
42
41
  const { t } = useLocalization();
43
42
 
44
43
  const needsCredits = requiredCredits && requiredCredits > currentCredits;
@@ -72,10 +71,10 @@ export const CreditsTabContent: React.FC<CreditsTabContentProps> = React.memo(
72
71
 
73
72
  <ScrollView
74
73
  style={styles.scrollView}
75
- contentContainerStyle={styles.scrollContent}
74
+ contentContainerStyle={[styles.scrollContent, { paddingHorizontal: tokens.spacing.lg, paddingBottom: tokens.spacing.md }]}
76
75
  showsVerticalScrollIndicator={false}
77
76
  >
78
- <View style={styles.packagesContainer}>
77
+ <Grid mobileColumns={1} tabletColumns={2} gap={tokens.spacing.sm}>
79
78
  {packages.map((pkg) => (
80
79
  <CreditsPackageCard
81
80
  key={pkg.id}
@@ -84,7 +83,7 @@ export const CreditsTabContent: React.FC<CreditsTabContentProps> = React.memo(
84
83
  onSelect={() => onSelectPackage(pkg.id)}
85
84
  />
86
85
  ))}
87
- </View>
86
+ </Grid>
88
87
  </ScrollView>
89
88
 
90
89
  <View style={styles.footer}>
@@ -116,13 +115,7 @@ const styles = StyleSheet.create({
116
115
  scrollView: {
117
116
  flex: 1,
118
117
  },
119
- scrollContent: {
120
- paddingHorizontal: 24,
121
- paddingBottom: 16,
122
- },
123
- packagesContainer: {
124
- gap: 12,
125
- },
118
+ scrollContent: {},
126
119
  footer: {
127
120
  padding: 24,
128
121
  paddingTop: 16,
@@ -5,8 +5,7 @@
5
5
 
6
6
  import React, { useMemo } from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
8
+ import { AtomicText, AtomicIcon, useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
10
9
 
11
10
  interface PaywallFeatureItemProps {
12
11
  icon: string;
@@ -16,6 +15,12 @@ interface PaywallFeatureItemProps {
16
15
  export const PaywallFeatureItem: React.FC<PaywallFeatureItemProps> = React.memo(
17
16
  ({ icon, text }) => {
18
17
  const tokens = useAppDesignTokens();
18
+ const { spacingMultiplier, getFontSize } = useResponsive();
19
+
20
+ const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
21
+ const fontSize = getFontSize(15);
22
+ const lineHeight = getFontSize(22);
23
+ const iconSize = getFontSize(20);
19
24
 
20
25
  // Pass icon name directly to AtomicIcon (which uses Ionicons)
21
26
  // Do NOT capitalize, as Ionicons names are lowercase/kebab-case.
@@ -28,13 +33,13 @@ export const PaywallFeatureItem: React.FC<PaywallFeatureItemProps> = React.memo(
28
33
  <View style={styles.featureItem}>
29
34
  <AtomicIcon
30
35
  name={iconName}
31
- customSize={20}
36
+ customSize={iconSize}
32
37
  customColor={tokens.colors.primary}
33
38
  style={styles.featureIcon}
34
39
  />
35
40
  <AtomicText
36
41
  type="bodyMedium"
37
- style={[styles.featureText, { color: tokens.colors.textPrimary }]}
42
+ style={[styles.featureText, { color: tokens.colors.textPrimary, fontSize, lineHeight }]}
38
43
  >
39
44
  {text}
40
45
  </AtomicText>
@@ -45,17 +50,16 @@ export const PaywallFeatureItem: React.FC<PaywallFeatureItemProps> = React.memo(
45
50
 
46
51
  PaywallFeatureItem.displayName = "PaywallFeatureItem";
47
52
 
48
- const styles = StyleSheet.create({
49
- featureItem: {
50
- flexDirection: "row",
51
- alignItems: "center",
52
- },
53
- featureIcon: {
54
- marginRight: 12,
55
- },
56
- featureText: {
57
- fontSize: 15,
58
- flex: 1,
59
- lineHeight: 22,
60
- },
61
- });
53
+ const createStyles = (spacingMult: number) =>
54
+ StyleSheet.create({
55
+ featureItem: {
56
+ flexDirection: "row",
57
+ alignItems: "center",
58
+ },
59
+ featureIcon: {
60
+ marginRight: 12 * spacingMult,
61
+ },
62
+ featureText: {
63
+ flex: 1,
64
+ },
65
+ });
@@ -5,6 +5,7 @@
5
5
 
6
6
  import React from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
+ import { useResponsive } from "@umituz/react-native-design-system";
8
9
  import { PaywallFeatureItem } from "./PaywallFeatureItem";
9
10
 
10
11
  interface PaywallFeaturesListProps {
@@ -18,12 +19,15 @@ interface PaywallFeaturesListProps {
18
19
 
19
20
  export const PaywallFeaturesList: React.FC<PaywallFeaturesListProps> = React.memo(
20
21
  ({ features, containerStyle, gap = 12 }) => {
22
+ const { spacingMultiplier } = useResponsive();
23
+ const responsiveGap = gap * spacingMultiplier;
24
+
21
25
  return (
22
26
  <View style={[styles.container, containerStyle]}>
23
27
  {features.map((feature, index) => (
24
28
  <View
25
29
  key={`${feature.icon}-${feature.text}-${index}`}
26
- style={{ marginBottom: index < features.length - 1 ? gap : 0 }}
30
+ style={{ marginBottom: index < features.length - 1 ? responsiveGap : 0 }}
27
31
  >
28
32
  <PaywallFeatureItem icon={feature.icon} text={feature.text} />
29
33
  </View>
@@ -1,8 +1,7 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, StyleSheet, TouchableOpacity } from "react-native";
3
3
  import type { PurchasesPackage } from "react-native-purchases";
4
- import { AtomicText } from "@umituz/react-native-design-system";
5
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
4
+ import { AtomicText, useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
6
5
  import { PaywallLegalFooter } from "./PaywallLegalFooter";
7
6
 
8
7
  interface SubscriptionFooterProps {
@@ -42,6 +41,10 @@ export const SubscriptionFooter: React.FC<SubscriptionFooterProps> = React.memo(
42
41
  onRestore,
43
42
  }) => {
44
43
  const tokens = useAppDesignTokens();
44
+ const { spacingMultiplier, getFontSize } = useResponsive();
45
+
46
+ const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
47
+ const buttonFontSize = getFontSize(16);
45
48
 
46
49
  const isDisabled = !selectedPkg || isProcessing || isLoading;
47
50
 
@@ -65,7 +68,7 @@ export const SubscriptionFooter: React.FC<SubscriptionFooterProps> = React.memo(
65
68
  style={{
66
69
  color: tokens.colors.onPrimary,
67
70
  fontWeight: "800",
68
- fontSize: 16,
71
+ fontSize: buttonFontSize,
69
72
  }}
70
73
  >
71
74
  {isProcessing ? processingText : purchaseButtonText}
@@ -92,21 +95,22 @@ export const SubscriptionFooter: React.FC<SubscriptionFooterProps> = React.memo(
92
95
 
93
96
  SubscriptionFooter.displayName = "SubscriptionFooter";
94
97
 
95
- const styles = StyleSheet.create({
96
- container: {},
97
- actions: {
98
- paddingHorizontal: 24,
99
- paddingVertical: 16,
100
- gap: 12,
101
- },
102
- gradientButton: {
103
- paddingVertical: 16,
104
- borderRadius: 16,
105
- alignItems: "center",
106
- justifyContent: "center",
107
- },
108
- restoreButton: {
109
- alignItems: "center",
110
- paddingVertical: 8,
111
- },
112
- });
98
+ const createStyles = (spacingMult: number) =>
99
+ StyleSheet.create({
100
+ container: {},
101
+ actions: {
102
+ paddingHorizontal: 24 * spacingMult,
103
+ paddingVertical: 16 * spacingMult,
104
+ gap: 12 * spacingMult,
105
+ },
106
+ gradientButton: {
107
+ paddingVertical: 16 * spacingMult,
108
+ borderRadius: 16 * spacingMult,
109
+ alignItems: "center",
110
+ justifyContent: "center",
111
+ },
112
+ restoreButton: {
113
+ alignItems: "center",
114
+ paddingVertical: 8 * spacingMult,
115
+ },
116
+ });
@@ -3,9 +3,9 @@
3
3
  * Fullscreen subscription flow using BaseModal from design system
4
4
  */
5
5
 
6
- import React from "react";
6
+ import React, { useMemo } from "react";
7
7
  import { View, StyleSheet, ScrollView } from "react-native";
8
- import { BaseModal } from "@umituz/react-native-design-system";
8
+ import { BaseModal, useResponsive } from "@umituz/react-native-design-system";
9
9
  import type { PurchasesPackage } from "react-native-purchases";
10
10
 
11
11
  import { SubscriptionModalHeader } from "./SubscriptionModalHeader";
@@ -85,6 +85,9 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
85
85
  onClose,
86
86
  });
87
87
 
88
+ const { spacingMultiplier } = useResponsive();
89
+ const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
90
+
88
91
  return (
89
92
  <BaseModal visible={visible} onClose={onClose}>
90
93
  <View style={styles.container}>
@@ -145,20 +148,21 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
145
148
 
146
149
  SubscriptionModal.displayName = "SubscriptionModal";
147
150
 
148
- const styles = StyleSheet.create({
149
- container: {
150
- flex: 1,
151
- width: "100%",
152
- },
153
- scrollView: {
154
- flex: 1,
155
- },
156
- scrollContent: {
157
- flexGrow: 1,
158
- paddingBottom: 32,
159
- },
160
- featuresList: {
161
- paddingHorizontal: 24,
162
- marginBottom: 24,
163
- },
164
- });
151
+ const createStyles = (spacingMult: number) =>
152
+ StyleSheet.create({
153
+ container: {
154
+ flex: 1,
155
+ width: "100%",
156
+ },
157
+ scrollView: {
158
+ flex: 1,
159
+ },
160
+ scrollContent: {
161
+ flexGrow: 1,
162
+ paddingBottom: 32 * spacingMult,
163
+ },
164
+ featuresList: {
165
+ paddingHorizontal: 24 * spacingMult,
166
+ marginBottom: 24 * spacingMult,
167
+ },
168
+ });
@@ -3,24 +3,91 @@
3
3
  * Single Responsibility: Display a subscription plan option
4
4
  */
5
5
 
6
- import React from "react";
7
- import { View, TouchableOpacity } from "react-native";
6
+ import React, { useMemo } from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
8
  import { AtomicText } from "@umituz/react-native-design-system";
9
- import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system";
9
+ import { useAppDesignTokens, withAlpha, useResponsive } from "@umituz/react-native-design-system";
10
10
  import { formatPrice } from "../../../utils/priceUtils";
11
11
  import { useLocalization } from "@umituz/react-native-localization";
12
12
  import { BestValueBadge } from "./BestValueBadge";
13
13
  import { getPeriodLabel, isYearlyPackage } from "../../../utils/packagePeriodUtils";
14
14
  import { LinearGradient } from "expo-linear-gradient";
15
15
  import type { SubscriptionPlanCardProps } from "./SubscriptionPlanCardTypes";
16
- import { styles } from "./SubscriptionPlanCardStyles";
17
16
 
18
17
  export type { SubscriptionPlanCardProps } from "./SubscriptionPlanCardTypes";
19
18
 
19
+ /**
20
+ * Create responsive styles for subscription plan card
21
+ */
22
+ const createStyles = (spacingMult: number, touchTarget: number) => {
23
+ const basePadding = 18;
24
+ const baseRadius = 16;
25
+ const baseCreditRadius = 12;
26
+
27
+ const radioSize = Math.max(touchTarget * 0.5, 24);
28
+ const radioInnerSize = radioSize * 0.5;
29
+
30
+ return StyleSheet.create({
31
+ container: {
32
+ borderRadius: baseRadius * spacingMult,
33
+ position: "relative",
34
+ overflow: "hidden",
35
+ },
36
+ gradientWrapper: {
37
+ flex: 1,
38
+ padding: basePadding * spacingMult,
39
+ },
40
+ content: {
41
+ flexDirection: "row",
42
+ justifyContent: "space-between",
43
+ alignItems: "center",
44
+ },
45
+ leftSection: {
46
+ flexDirection: "row",
47
+ alignItems: "center",
48
+ flex: 1,
49
+ },
50
+ radio: {
51
+ width: radioSize,
52
+ height: radioSize,
53
+ borderRadius: radioSize / 2,
54
+ borderWidth: 2,
55
+ alignItems: "center",
56
+ justifyContent: "center",
57
+ marginRight: 16 * spacingMult,
58
+ },
59
+ radioInner: {
60
+ width: radioInnerSize,
61
+ height: radioInnerSize,
62
+ borderRadius: radioInnerSize / 2,
63
+ },
64
+ textContainer: {
65
+ flex: 1,
66
+ },
67
+ title: {
68
+ fontWeight: "600",
69
+ marginBottom: 2 * spacingMult,
70
+ },
71
+ creditBadge: {
72
+ paddingHorizontal: 10 * spacingMult,
73
+ paddingVertical: 4 * spacingMult,
74
+ borderRadius: baseCreditRadius * spacingMult,
75
+ marginBottom: 4 * spacingMult,
76
+ },
77
+ rightSection: {
78
+ alignItems: "flex-end",
79
+ },
80
+ price: {
81
+ fontWeight: "700",
82
+ },
83
+ });
84
+ };
85
+
20
86
  export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
21
87
  React.memo(({ package: pkg, isSelected, onSelect, isBestValue = false, creditAmount }) => {
22
88
  const tokens = useAppDesignTokens();
23
89
  const { t } = useLocalization();
90
+ const { spacingMultiplier, getFontSize, minTouchTarget } = useResponsive();
24
91
 
25
92
  const period = pkg.product.subscriptionPeriod;
26
93
  const isYearly = isYearlyPackage(pkg);
@@ -41,6 +108,11 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
41
108
  }
42
109
  : {};
43
110
 
111
+ // Responsive styles
112
+ const styles = useMemo(() => createStyles(spacingMultiplier, minTouchTarget), [spacingMultiplier, minTouchTarget]);
113
+ const secondaryFontSize = getFontSize(11);
114
+ const creditFontSize = getFontSize(11);
115
+
44
116
  return (
45
117
  <TouchableOpacity
46
118
  onPress={onSelect}
@@ -87,12 +159,12 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
87
159
  >
88
160
  {title}
89
161
  </AtomicText>
90
- {isYearly && (
162
+ {isYearly && monthlyEquivalent && (
91
163
  <AtomicText
92
164
  type="bodySmall"
93
- style={{ color: tokens.colors.textSecondary, fontSize: 11 }}
165
+ style={{ color: tokens.colors.textSecondary, fontSize: secondaryFontSize }}
94
166
  >
95
- {price}
167
+ {monthlyEquivalent}/mo
96
168
  </AtomicText>
97
169
  )}
98
170
  </View>
@@ -117,7 +189,7 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
117
189
  style={{
118
190
  color: tokens.colors.primary,
119
191
  fontWeight: "800",
120
- fontSize: 11,
192
+ fontSize: creditFontSize,
121
193
  }}
122
194
  >
123
195
  {creditAmount} {t("paywall.credits") || "Credits"}
@@ -128,9 +200,7 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
128
200
  type="titleMedium"
129
201
  style={[styles.price, { color: tokens.colors.textPrimary }]}
130
202
  >
131
- {isYearly && monthlyEquivalent
132
- ? `${monthlyEquivalent}/mo`
133
- : price}
203
+ {price}
134
204
  </AtomicText>
135
205
  </View>
136
206
  </View>
@@ -3,9 +3,9 @@
3
3
  * Expandable subscription plan card with credit display
4
4
  */
5
5
 
6
- import React, { useCallback } from "react";
6
+ import React, { useCallback, useMemo } from "react";
7
7
  import { View, StyleSheet, type StyleProp, type ViewStyle } from "react-native";
8
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
8
+ import { useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
9
9
  import { formatPrice } from "../../../../utils/priceUtils";
10
10
  import { getPeriodLabel, isYearlyPackage } from "../../../../utils/packagePeriodUtils";
11
11
  import { useLocalization } from "@umituz/react-native-localization";
@@ -28,6 +28,9 @@ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
28
28
  }) => {
29
29
  const tokens = useAppDesignTokens();
30
30
  const { t } = useLocalization();
31
+ const { spacingMultiplier } = useResponsive();
32
+
33
+ const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
31
34
 
32
35
  const period = pkg.product.subscriptionPeriod;
33
36
  const isYearly = isYearlyPackage(pkg);
@@ -88,9 +91,10 @@ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
88
91
 
89
92
  AccordionPlanCard.displayName = "AccordionPlanCard";
90
93
 
91
- const styles = StyleSheet.create({
92
- container: {
93
- borderRadius: 16,
94
- marginBottom: 12,
95
- },
96
- });
94
+ const createStyles = (spacingMult: number) =>
95
+ StyleSheet.create({
96
+ container: {
97
+ borderRadius: 16 * spacingMult,
98
+ marginBottom: 12 * spacingMult,
99
+ },
100
+ });
@@ -3,11 +3,12 @@
3
3
  * Expanded state of accordion subscription card
4
4
  */
5
5
 
6
- import React from "react";
6
+ import React, { useMemo } from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
8
  import {
9
9
  AtomicText,
10
10
  useAppDesignTokens,
11
+ useResponsive,
11
12
  } from "@umituz/react-native-design-system";
12
13
  import type { PlanCardDetailsProps } from "./AccordionPlanCardTypes";
13
14
 
@@ -21,6 +22,9 @@ export const PlanCardDetails: React.FC<PlanCardDetailsProps> = ({
21
22
  perMonthLabel = "Per Month",
22
23
  }) => {
23
24
  const tokens = useAppDesignTokens();
25
+ const { spacingMultiplier } = useResponsive();
26
+
27
+ const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
24
28
 
25
29
  return (
26
30
  <View
@@ -57,7 +61,10 @@ export const PlanCardDetails: React.FC<PlanCardDetailsProps> = ({
57
61
  </AtomicText>
58
62
  <AtomicText
59
63
  type="bodyMedium"
60
- style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
64
+ style={{
65
+ color: tokens.colors.primary,
66
+ fontWeight: "700",
67
+ }}
61
68
  >
62
69
  {fullPrice}
63
70
  </AtomicText>
@@ -74,10 +81,7 @@ export const PlanCardDetails: React.FC<PlanCardDetailsProps> = ({
74
81
  </AtomicText>
75
82
  <AtomicText
76
83
  type="bodyMedium"
77
- style={{
78
- color: tokens.colors.primary,
79
- fontWeight: "700",
80
- }}
84
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
81
85
  >
82
86
  {monthlyEquivalent}
83
87
  </AtomicText>
@@ -87,16 +91,17 @@ export const PlanCardDetails: React.FC<PlanCardDetailsProps> = ({
87
91
  );
88
92
  };
89
93
 
90
- const styles = StyleSheet.create({
91
- container: {
92
- paddingHorizontal: 16,
93
- paddingVertical: 12,
94
- borderTopWidth: 1,
95
- gap: 10,
96
- },
97
- row: {
98
- flexDirection: "row",
99
- justifyContent: "space-between",
100
- alignItems: "center",
101
- },
102
- });
94
+ const createStyles = (spacingMult: number) =>
95
+ StyleSheet.create({
96
+ container: {
97
+ paddingHorizontal: 16 * spacingMult,
98
+ paddingVertical: 12 * spacingMult,
99
+ borderTopWidth: 1,
100
+ gap: 10 * spacingMult,
101
+ },
102
+ row: {
103
+ flexDirection: "row",
104
+ justifyContent: "space-between",
105
+ alignItems: "center",
106
+ },
107
+ });
@@ -3,12 +3,13 @@
3
3
  * Collapsed state of accordion subscription card
4
4
  */
5
5
 
6
- import React from "react";
6
+ import React, { useMemo } from "react";
7
7
  import { View, TouchableOpacity, StyleSheet } from "react-native";
8
8
  import {
9
9
  AtomicText,
10
10
  useAppDesignTokens,
11
11
  withAlpha,
12
+ useResponsive,
12
13
  } from "@umituz/react-native-design-system";
13
14
  import { BestValueBadge } from "../BestValueBadge";
14
15
  import { useLocalization } from "@umituz/react-native-localization";
@@ -24,6 +25,13 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
24
25
  }) => {
25
26
  const tokens = useAppDesignTokens();
26
27
  const { t } = useLocalization();
28
+ const { spacingMultiplier, getFontSize, minTouchTarget } = useResponsive();
29
+
30
+ const styles = useMemo(
31
+ () => createStyles(spacingMultiplier, minTouchTarget),
32
+ [spacingMultiplier, minTouchTarget]
33
+ );
34
+ const creditFontSize = getFontSize(11);
27
35
 
28
36
  return (
29
37
  <TouchableOpacity
@@ -77,7 +85,7 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
77
85
  style={{
78
86
  color: tokens.colors.primary,
79
87
  fontWeight: "700",
80
- fontSize: 11,
88
+ fontSize: creditFontSize,
81
89
  }}
82
90
  >
83
91
  {creditAmount} {t("paywall.credits") || "Credits"}
@@ -103,50 +111,55 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
103
111
  );
104
112
  };
105
113
 
106
- const styles = StyleSheet.create({
107
- container: {
108
- width: "100%",
109
- },
110
- content: {
111
- flexDirection: "row",
112
- alignItems: "center",
113
- justifyContent: "space-between",
114
- paddingVertical: 16,
115
- paddingHorizontal: 16,
116
- },
117
- leftSection: {
118
- flexDirection: "row",
119
- alignItems: "center",
120
- flex: 1,
121
- },
122
- radio: {
123
- width: 22,
124
- height: 22,
125
- borderRadius: 11,
126
- borderWidth: 2,
127
- alignItems: "center",
128
- justifyContent: "center",
129
- marginRight: 12,
130
- },
131
- radioInner: {
132
- width: 12,
133
- height: 12,
134
- borderRadius: 6,
135
- },
136
- textContainer: {
137
- flex: 1,
138
- gap: 6,
139
- },
140
- creditBadge: {
141
- paddingHorizontal: 10,
142
- paddingVertical: 4,
143
- borderRadius: 12,
144
- borderWidth: 1,
145
- alignSelf: "flex-start",
146
- },
147
- rightSection: {
148
- flexDirection: "row",
149
- alignItems: "center",
150
- gap: 12,
151
- },
152
- });
114
+ const createStyles = (spacingMult: number, touchTarget: number) => {
115
+ const radioSize = Math.max(touchTarget * 0.4, 22);
116
+ const radioInnerSize = radioSize * 0.55;
117
+
118
+ return StyleSheet.create({
119
+ container: {
120
+ width: "100%",
121
+ },
122
+ content: {
123
+ flexDirection: "row",
124
+ alignItems: "center",
125
+ justifyContent: "space-between",
126
+ paddingVertical: 16 * spacingMult,
127
+ paddingHorizontal: 16 * spacingMult,
128
+ },
129
+ leftSection: {
130
+ flexDirection: "row",
131
+ alignItems: "center",
132
+ flex: 1,
133
+ },
134
+ radio: {
135
+ width: radioSize,
136
+ height: radioSize,
137
+ borderRadius: radioSize / 2,
138
+ borderWidth: 2,
139
+ alignItems: "center",
140
+ justifyContent: "center",
141
+ marginRight: 12 * spacingMult,
142
+ },
143
+ radioInner: {
144
+ width: radioInnerSize,
145
+ height: radioInnerSize,
146
+ borderRadius: radioInnerSize / 2,
147
+ },
148
+ textContainer: {
149
+ flex: 1,
150
+ gap: 6 * spacingMult,
151
+ },
152
+ creditBadge: {
153
+ paddingHorizontal: 10 * spacingMult,
154
+ paddingVertical: 4 * spacingMult,
155
+ borderRadius: 12 * spacingMult,
156
+ borderWidth: 1,
157
+ alignSelf: "flex-start",
158
+ },
159
+ rightSection: {
160
+ flexDirection: "row",
161
+ alignItems: "center",
162
+ gap: 12 * spacingMult,
163
+ },
164
+ });
165
+ };