@umituz/react-native-subscription 2.12.2 → 2.12.4

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.
Files changed (47) hide show
  1. package/package.json +2 -2
  2. package/src/domains/index.ts +5 -0
  3. package/src/domains/paywall/components/CreditCard.tsx +117 -0
  4. package/src/domains/paywall/components/FeatureItem.tsx +50 -0
  5. package/src/domains/paywall/components/FeatureList.tsx +34 -0
  6. package/src/domains/paywall/components/PaywallFooter.tsx +98 -0
  7. package/src/{presentation/components/paywall/PaywallHeroHeader.tsx → domains/paywall/components/PaywallHeader.tsx} +15 -44
  8. package/src/domains/paywall/components/PaywallModal.tsx +187 -0
  9. package/src/domains/paywall/components/PaywallTabBar.tsx +102 -0
  10. package/src/domains/paywall/components/PlanCard.tsx +124 -0
  11. package/src/domains/paywall/components/index.ts +14 -0
  12. package/src/domains/paywall/entities/index.ts +5 -0
  13. package/src/domains/paywall/entities/types.ts +48 -0
  14. package/src/domains/paywall/hooks/index.ts +6 -0
  15. package/src/{presentation → domains/paywall}/hooks/usePaywall.ts +1 -1
  16. package/src/domains/paywall/index.ts +13 -0
  17. package/src/index.ts +15 -22
  18. package/src/presentation/components/details/PremiumDetailsCard.tsx +35 -11
  19. package/src/domain/entities/paywall/CreditsPackage.ts +0 -16
  20. package/src/domain/entities/paywall/PaywallMode.ts +0 -6
  21. package/src/domain/entities/paywall/PaywallTab.ts +0 -11
  22. package/src/domain/entities/paywall/SubscriptionPlan.ts +0 -27
  23. package/src/presentation/components/paywall/BestValueBadge.tsx +0 -59
  24. package/src/presentation/components/paywall/CreditsPackageCard.tsx +0 -161
  25. package/src/presentation/components/paywall/CreditsTabContent.tsx +0 -123
  26. package/src/presentation/components/paywall/PaywallFeatureItem.tsx +0 -79
  27. package/src/presentation/components/paywall/PaywallFeaturesList.tsx +0 -47
  28. package/src/presentation/components/paywall/PaywallHeader.tsx +0 -82
  29. package/src/presentation/components/paywall/PaywallLegalFooter.tsx +0 -145
  30. package/src/presentation/components/paywall/PaywallLegalFooterStyles.ts +0 -53
  31. package/src/presentation/components/paywall/PaywallLegalFooterTypes.ts +0 -19
  32. package/src/presentation/components/paywall/PaywallModal.tsx +0 -162
  33. package/src/presentation/components/paywall/PaywallTabBar.tsx +0 -120
  34. package/src/presentation/components/paywall/SubscriptionFooter.tsx +0 -116
  35. package/src/presentation/components/paywall/SubscriptionModal.tsx +0 -168
  36. package/src/presentation/components/paywall/SubscriptionModalHeader.tsx +0 -78
  37. package/src/presentation/components/paywall/SubscriptionPackageList.tsx +0 -171
  38. package/src/presentation/components/paywall/SubscriptionPlanCard.tsx +0 -213
  39. package/src/presentation/components/paywall/SubscriptionPlanCardStyles.ts +0 -61
  40. package/src/presentation/components/paywall/SubscriptionPlanCardTypes.ts +0 -15
  41. package/src/presentation/components/paywall/SubscriptionTabContent.tsx +0 -139
  42. package/src/presentation/components/paywall/accordion/AccordionPlanCard.tsx +0 -98
  43. package/src/presentation/components/paywall/accordion/AccordionPlanCardTypes.ts +0 -39
  44. package/src/presentation/components/paywall/accordion/PlanCardDetails.tsx +0 -107
  45. package/src/presentation/components/paywall/accordion/PlanCardHeader.tsx +0 -155
  46. package/src/presentation/components/paywall/accordion/index.ts +0 -12
  47. /package/src/{presentation → domains/paywall}/hooks/useSubscriptionModal.ts +0 -0
@@ -1,19 +0,0 @@
1
- /**
2
- * Paywall Legal Footer Types
3
- * Type definitions for paywall legal footer
4
- */
5
-
6
- export interface PaywallLegalFooterProps {
7
- termsText?: string;
8
- privacyUrl?: string;
9
- termsUrl?: string;
10
- privacyText?: string;
11
- termsOfServiceText?: string;
12
- showRestoreButton?: boolean;
13
- restoreButtonText?: string;
14
- onRestore?: () => void;
15
- isProcessing?: boolean;
16
- }
17
-
18
- export const DEFAULT_TERMS =
19
- "Payment will be charged to your account. Subscription automatically renews unless cancelled.";
@@ -1,162 +0,0 @@
1
- /**
2
- * Paywall Modal Component
3
- * Mode-based paywall: subscription, credits, or hybrid
4
- */
5
-
6
- import React from "react";
7
- import { View, StyleSheet } from "react-native";
8
- import { BaseModal } from "@umituz/react-native-design-system";
9
- import type { PurchasesPackage } from "react-native-purchases";
10
- import { usePaywall } from "../../hooks/usePaywall";
11
- import { PaywallHeroHeader } from "./PaywallHeroHeader";
12
- import { PaywallTabBar } from "./PaywallTabBar";
13
- import { CreditsTabContent } from "./CreditsTabContent";
14
- import { SubscriptionTabContent } from "./SubscriptionTabContent";
15
- import type { PaywallTabType } from "../../../domain/entities/paywall/PaywallTab";
16
- import type { PaywallMode } from "../../../domain/entities/paywall/PaywallMode";
17
- import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
18
-
19
- export interface PaywallModalProps {
20
- visible: boolean;
21
- onClose: () => void;
22
- mode: PaywallMode;
23
- initialTab?: PaywallTabType;
24
- creditsPackages?: CreditsPackage[];
25
- subscriptionPackages?: PurchasesPackage[];
26
- currentCredits?: number;
27
- requiredCredits?: number;
28
- onCreditsPurchase?: (packageId: string) => Promise<void>;
29
- onSubscriptionPurchase?: (pkg: PurchasesPackage) => Promise<void>;
30
- onRestore?: () => Promise<void>;
31
- subscriptionFeatures?: Array<{ icon: string; text: string }>;
32
- isLoading?: boolean;
33
- translations: PaywallTranslations;
34
- legalUrls?: PaywallLegalUrls;
35
- }
36
-
37
- export interface PaywallTranslations {
38
- title: string;
39
- subtitle: string;
40
- creditsTabLabel?: string;
41
- subscriptionTabLabel?: string;
42
- purchaseButtonText: string;
43
- subscribeButtonText?: string;
44
- restoreButtonText: string;
45
- loadingText: string;
46
- emptyText: string;
47
- processingText: string;
48
- privacyText?: string;
49
- termsOfServiceText?: string;
50
- }
51
-
52
- export interface PaywallLegalUrls {
53
- privacyUrl?: string;
54
- termsUrl?: string;
55
- }
56
-
57
- export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
58
- const {
59
- visible,
60
- onClose,
61
- mode,
62
- initialTab = mode === "credits" ? "credits" : "subscription",
63
- creditsPackages = [],
64
- subscriptionPackages = [],
65
- currentCredits = 0,
66
- requiredCredits,
67
- onCreditsPurchase,
68
- onSubscriptionPurchase,
69
- onRestore,
70
- subscriptionFeatures = [],
71
- isLoading = false,
72
- translations,
73
- legalUrls = {},
74
- } = props;
75
-
76
- const {
77
- activeTab,
78
- selectedCreditsPackageId,
79
- selectedSubscriptionPkg,
80
- handleTabChange,
81
- handleCreditsPackageSelect,
82
- handleSubscriptionPackageSelect,
83
- handleCreditsPurchase,
84
- handleSubscriptionPurchase,
85
- } = usePaywall({
86
- initialTab,
87
- onCreditsPurchase: onCreditsPurchase ?? (() => Promise.resolve()),
88
- onSubscriptionPurchase: onSubscriptionPurchase ?? (() => Promise.resolve()),
89
- });
90
-
91
- const showTabs = mode === "hybrid";
92
- const showCredits = mode === "credits" || (mode === "hybrid" && activeTab === "credits");
93
- const showSubscription = mode === "subscription" || (mode === "hybrid" && activeTab === "subscription");
94
-
95
- return (
96
- <BaseModal visible={visible} onClose={onClose}>
97
- <View style={styles.container}>
98
- <PaywallHeroHeader
99
- title={translations.title}
100
- subtitle={translations.subtitle}
101
- onClose={onClose}
102
- />
103
-
104
- {showTabs && (
105
- <PaywallTabBar
106
- activeTab={activeTab}
107
- onTabChange={handleTabChange}
108
- creditsLabel={translations.creditsTabLabel}
109
- subscriptionLabel={translations.subscriptionTabLabel}
110
- />
111
- )}
112
-
113
- <View style={styles.tabContent}>
114
- {showCredits && (
115
- <CreditsTabContent
116
- packages={creditsPackages}
117
- selectedPackageId={selectedCreditsPackageId}
118
- onSelectPackage={handleCreditsPackageSelect}
119
- onPurchase={handleCreditsPurchase}
120
- currentCredits={currentCredits}
121
- requiredCredits={requiredCredits}
122
- isLoading={isLoading}
123
- purchaseButtonText={translations.purchaseButtonText}
124
- />
125
- )}
126
- {showSubscription && (
127
- <SubscriptionTabContent
128
- packages={subscriptionPackages}
129
- selectedPackage={selectedSubscriptionPkg}
130
- onSelectPackage={handleSubscriptionPackageSelect}
131
- onPurchase={handleSubscriptionPurchase}
132
- features={subscriptionFeatures}
133
- isLoading={isLoading}
134
- purchaseButtonText={translations.subscribeButtonText ?? translations.purchaseButtonText}
135
- processingText={translations.processingText}
136
- restoreButtonText={translations.restoreButtonText}
137
- loadingText={translations.loadingText}
138
- emptyText={translations.emptyText}
139
- onRestore={onRestore}
140
- privacyUrl={legalUrls.privacyUrl}
141
- termsUrl={legalUrls.termsUrl}
142
- privacyText={translations.privacyText}
143
- termsOfServiceText={translations.termsOfServiceText}
144
- />
145
- )}
146
- </View>
147
- </View>
148
- </BaseModal>
149
- );
150
- });
151
-
152
- PaywallModal.displayName = "PaywallModal";
153
-
154
- const styles = StyleSheet.create({
155
- container: {
156
- flex: 1,
157
- width: "100%",
158
- },
159
- tabContent: {
160
- flex: 1,
161
- },
162
- });
@@ -1,120 +0,0 @@
1
- /**
2
- * Paywall Tab Bar Component
3
- * Segmented control for paywall tabs
4
- */
5
-
6
- import React from "react";
7
- import { View, TouchableOpacity, StyleSheet, Animated } from "react-native";
8
- import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
- import type { PaywallTabType } from "../../../domain/entities/paywall/PaywallTab";
10
-
11
- interface PaywallTabBarProps {
12
- activeTab: PaywallTabType;
13
- onTabChange: (tab: PaywallTabType) => void;
14
- creditsLabel?: string;
15
- subscriptionLabel?: string;
16
- }
17
-
18
- export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
19
- ({
20
- activeTab,
21
- onTabChange,
22
- creditsLabel = "Credits",
23
- subscriptionLabel = "Subscription",
24
- }) => {
25
- const tokens = useAppDesignTokens();
26
- const animatedValue = React.useRef(
27
- new Animated.Value(activeTab === "credits" ? 0 : 1)
28
- ).current;
29
-
30
- React.useEffect(() => {
31
- Animated.spring(animatedValue, {
32
- toValue: activeTab === "credits" ? 0 : 1,
33
- useNativeDriver: false,
34
- tension: 68,
35
- friction: 12,
36
- }).start();
37
- }, [activeTab, animatedValue]);
38
-
39
- const renderTab = (tab: PaywallTabType, label: string) => {
40
- const isActive = activeTab === tab;
41
-
42
- return (
43
- <TouchableOpacity
44
- key={tab}
45
- style={styles.tab}
46
- onPress={() => onTabChange(tab)}
47
- activeOpacity={0.7}
48
- >
49
- <AtomicText
50
- type="labelLarge"
51
- style={[
52
- styles.tabText,
53
- {
54
- color: isActive ? tokens.colors.primary : tokens.colors.textSecondary,
55
- },
56
- ]}
57
- >
58
- {label}
59
- </AtomicText>
60
- </TouchableOpacity>
61
- );
62
- };
63
-
64
- const indicatorTranslateX = animatedValue.interpolate({
65
- inputRange: [0, 1],
66
- outputRange: ["0%", "50%"],
67
- });
68
-
69
- return (
70
- <View
71
- style={[
72
- styles.container,
73
- { backgroundColor: tokens.colors.surfaceSecondary },
74
- ]}
75
- >
76
- <Animated.View
77
- style={[
78
- styles.indicator,
79
- {
80
- backgroundColor: tokens.colors.surface,
81
- left: indicatorTranslateX,
82
- },
83
- ]}
84
- />
85
- {renderTab("credits", creditsLabel)}
86
- {renderTab("subscription", subscriptionLabel)}
87
- </View>
88
- );
89
- }
90
- );
91
-
92
- PaywallTabBar.displayName = "PaywallTabBar";
93
-
94
- const styles = StyleSheet.create({
95
- container: {
96
- flexDirection: "row",
97
- borderRadius: 12,
98
- padding: 4,
99
- marginHorizontal: 24,
100
- marginBottom: 16,
101
- position: "relative",
102
- height: 44,
103
- },
104
- indicator: {
105
- position: "absolute",
106
- top: 4,
107
- bottom: 4,
108
- width: "48%",
109
- borderRadius: 8,
110
- },
111
- tab: {
112
- flex: 1,
113
- alignItems: "center",
114
- justifyContent: "center",
115
- zIndex: 1,
116
- },
117
- tabText: {
118
- fontWeight: "600",
119
- },
120
- });
@@ -1,116 +0,0 @@
1
- import React, { useMemo } from "react";
2
- import { View, StyleSheet, TouchableOpacity } from "react-native";
3
- import type { PurchasesPackage } from "react-native-purchases";
4
- import { AtomicText, useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
5
- import { PaywallLegalFooter } from "./PaywallLegalFooter";
6
-
7
- interface SubscriptionFooterProps {
8
- isProcessing: boolean;
9
- isLoading: boolean;
10
- processingText: string;
11
- purchaseButtonText: string;
12
- hasPackages: boolean;
13
- selectedPkg: PurchasesPackage | null;
14
- restoreButtonText: string;
15
- showRestoreButton: boolean;
16
- privacyUrl?: string;
17
- termsUrl?: string;
18
- privacyText?: string;
19
- termsOfServiceText?: string;
20
- onPurchase: () => void;
21
- onRestore: () => void;
22
- }
23
-
24
- import { LinearGradient } from "expo-linear-gradient";
25
-
26
- export const SubscriptionFooter: React.FC<SubscriptionFooterProps> = React.memo(
27
- ({
28
- isProcessing,
29
- isLoading,
30
- processingText,
31
- purchaseButtonText,
32
- hasPackages,
33
- selectedPkg,
34
- restoreButtonText,
35
- showRestoreButton,
36
- privacyUrl,
37
- termsUrl,
38
- privacyText,
39
- termsOfServiceText,
40
- onPurchase,
41
- onRestore,
42
- }) => {
43
- const tokens = useAppDesignTokens();
44
- const { spacingMultiplier, getFontSize } = useResponsive();
45
-
46
- const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
47
- const buttonFontSize = getFontSize(16);
48
-
49
- const isDisabled = !selectedPkg || isProcessing || isLoading;
50
-
51
- return (
52
- <View style={styles.container}>
53
- <View style={styles.actions}>
54
- {hasPackages && (
55
- <TouchableOpacity
56
- onPress={onPurchase}
57
- disabled={isDisabled}
58
- activeOpacity={0.8}
59
- >
60
- <LinearGradient
61
- colors={[tokens.colors.primary, tokens.colors.secondary]}
62
- start={{ x: 0, y: 0 }}
63
- end={{ x: 1, y: 0 }}
64
- style={[styles.gradientButton, isDisabled && { opacity: 0.5 }]}
65
- >
66
- <AtomicText
67
- type="titleSmall"
68
- style={{
69
- color: tokens.colors.onPrimary,
70
- fontWeight: "800",
71
- fontSize: buttonFontSize,
72
- }}
73
- >
74
- {isProcessing ? processingText : purchaseButtonText}
75
- </AtomicText>
76
- </LinearGradient>
77
- </TouchableOpacity>
78
- )}
79
- </View>
80
-
81
- <PaywallLegalFooter
82
- privacyUrl={privacyUrl}
83
- termsUrl={termsUrl}
84
- privacyText={privacyText}
85
- termsOfServiceText={termsOfServiceText}
86
- showRestoreButton={showRestoreButton}
87
- restoreButtonText={restoreButtonText}
88
- onRestore={onRestore}
89
- isProcessing={isProcessing || isLoading}
90
- />
91
- </View>
92
- );
93
- }
94
- );
95
-
96
- SubscriptionFooter.displayName = "SubscriptionFooter";
97
-
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
- });
@@ -1,168 +0,0 @@
1
- /**
2
- * Subscription Modal Component
3
- * Fullscreen subscription flow using BaseModal from design system
4
- */
5
-
6
- import React, { useMemo } from "react";
7
- import { View, StyleSheet, ScrollView } from "react-native";
8
- import { BaseModal, useResponsive } from "@umituz/react-native-design-system";
9
- import type { PurchasesPackage } from "react-native-purchases";
10
-
11
- import { SubscriptionModalHeader } from "./SubscriptionModalHeader";
12
- import { SubscriptionPackageList } from "./SubscriptionPackageList";
13
- import { SubscriptionFooter } from "./SubscriptionFooter";
14
- import { PaywallFeaturesList } from "./PaywallFeaturesList";
15
- import { useSubscriptionModal } from "../../hooks/useSubscriptionModal";
16
-
17
- export interface SubscriptionModalProps {
18
- visible: boolean;
19
- onClose: () => void;
20
- packages: PurchasesPackage[];
21
- onPurchase: (pkg: PurchasesPackage) => Promise<boolean>;
22
- onRestore: () => Promise<boolean>;
23
- title: string;
24
- subtitle?: string;
25
- isLoading?: boolean;
26
- purchaseButtonText: string;
27
- restoreButtonText: string;
28
- loadingText: string;
29
- emptyText: string;
30
- processingText: string;
31
- privacyUrl?: string;
32
- termsUrl?: string;
33
- privacyText?: string;
34
- termsOfServiceText?: string;
35
- showRestoreButton?: boolean;
36
- /** Optional: Map of product identifier to credit amount */
37
- creditAmounts?: Record<string, number>;
38
- /** Optional: Manually specify which package should show "Best Value" badge */
39
- bestValueIdentifier?: string;
40
- /** Optional: Text labels for accordion details */
41
- billingPeriodLabel?: string;
42
- totalPriceLabel?: string;
43
- perMonthLabel?: string;
44
- /** Optional: List of premium features to display */
45
- features?: Array<{ icon: string; text: string }>;
46
- }
47
-
48
- export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((props) => {
49
- const {
50
- visible,
51
- onClose,
52
- packages,
53
- onPurchase,
54
- onRestore,
55
- title,
56
- subtitle,
57
- isLoading = false,
58
- purchaseButtonText,
59
- restoreButtonText,
60
- loadingText,
61
- emptyText,
62
- processingText,
63
- privacyUrl,
64
- termsUrl,
65
- privacyText,
66
- termsOfServiceText,
67
- showRestoreButton = true,
68
- creditAmounts,
69
- bestValueIdentifier,
70
- billingPeriodLabel,
71
- totalPriceLabel,
72
- perMonthLabel,
73
- features = [],
74
- } = props;
75
-
76
- const {
77
- selectedPkg,
78
- setSelectedPkg,
79
- isProcessing,
80
- handlePurchase,
81
- handleRestore,
82
- } = useSubscriptionModal({
83
- onPurchase,
84
- onRestore,
85
- onClose,
86
- });
87
-
88
- const { spacingMultiplier } = useResponsive();
89
- const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
90
-
91
- return (
92
- <BaseModal visible={visible} onClose={onClose}>
93
- <View style={styles.container}>
94
- <SubscriptionModalHeader
95
- title={title}
96
- subtitle={subtitle}
97
- onClose={onClose}
98
- />
99
-
100
- <ScrollView
101
- style={styles.scrollView}
102
- contentContainerStyle={styles.scrollContent}
103
- showsVerticalScrollIndicator={false}
104
- bounces={false}
105
- >
106
- {features.length > 0 && (
107
- <PaywallFeaturesList
108
- features={features}
109
- containerStyle={styles.featuresList}
110
- />
111
- )}
112
-
113
- <SubscriptionPackageList
114
- packages={packages}
115
- isLoading={isLoading}
116
- selectedPkg={selectedPkg}
117
- onSelect={setSelectedPkg}
118
- loadingText={loadingText}
119
- emptyText={emptyText}
120
- creditAmounts={creditAmounts}
121
- bestValueIdentifier={bestValueIdentifier}
122
- billingPeriodLabel={billingPeriodLabel}
123
- totalPriceLabel={totalPriceLabel}
124
- perMonthLabel={perMonthLabel}
125
- />
126
- </ScrollView>
127
-
128
- <SubscriptionFooter
129
- isProcessing={isProcessing}
130
- isLoading={isLoading}
131
- processingText={processingText}
132
- purchaseButtonText={purchaseButtonText}
133
- hasPackages={packages.length > 0}
134
- selectedPkg={selectedPkg}
135
- restoreButtonText={restoreButtonText}
136
- showRestoreButton={showRestoreButton}
137
- privacyUrl={privacyUrl}
138
- termsUrl={termsUrl}
139
- privacyText={privacyText}
140
- termsOfServiceText={termsOfServiceText}
141
- onPurchase={handlePurchase}
142
- onRestore={handleRestore}
143
- />
144
- </View>
145
- </BaseModal>
146
- );
147
- });
148
-
149
- SubscriptionModal.displayName = "SubscriptionModal";
150
-
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
- });
@@ -1,78 +0,0 @@
1
- /**
2
- * Subscription Modal Header Component
3
- */
4
-
5
- import React from "react";
6
- import { View, StyleSheet, TouchableOpacity } from "react-native";
7
- import { AtomicText } from "@umituz/react-native-design-system";
8
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
9
-
10
- interface SubscriptionModalHeaderProps {
11
- title: string;
12
- subtitle?: string;
13
- onClose: () => void;
14
- }
15
-
16
- export const SubscriptionModalHeader: React.FC<SubscriptionModalHeaderProps> = ({
17
- title,
18
- subtitle,
19
- onClose,
20
- }) => {
21
- const tokens = useAppDesignTokens();
22
-
23
- return (
24
- <View style={styles.header}>
25
- <TouchableOpacity
26
- style={styles.closeButton}
27
- onPress={onClose}
28
- testID="subscription-modal-close-button"
29
- >
30
- <AtomicText style={[styles.closeIcon, { color: tokens.colors.textSecondary }]}>
31
- ×
32
- </AtomicText>
33
- </TouchableOpacity>
34
- <AtomicText
35
- type="headlineMedium"
36
- style={[styles.title, { color: tokens.colors.textPrimary }]}
37
- >
38
- {title}
39
- </AtomicText>
40
- {subtitle && (
41
- <AtomicText
42
- type="bodyMedium"
43
- style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
44
- >
45
- {subtitle}
46
- </AtomicText>
47
- )}
48
- </View>
49
- );
50
- };
51
-
52
- const styles = StyleSheet.create({
53
- header: {
54
- alignItems: "center",
55
- paddingHorizontal: 24,
56
- paddingTop: 16,
57
- paddingBottom: 16,
58
- },
59
- closeButton: {
60
- position: "absolute",
61
- top: 8,
62
- right: 16,
63
- padding: 8,
64
- zIndex: 1,
65
- },
66
- closeIcon: {
67
- fontSize: 28,
68
- fontWeight: "300",
69
- },
70
- title: {
71
- marginBottom: 8,
72
- textAlign: "center",
73
- },
74
- subtitle: {
75
- textAlign: "center",
76
- paddingHorizontal: 20,
77
- },
78
- });