@umituz/react-native-subscription 2.2.28 → 2.2.30

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.2.28",
3
+ "version": "2.2.30",
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/index.ts CHANGED
@@ -94,10 +94,15 @@ export {
94
94
  export {
95
95
  SubscriptionModal,
96
96
  type SubscriptionModalProps,
97
+ type SubscriptionModalStyles,
97
98
  } from "./presentation/components/paywall/SubscriptionModal";
98
99
 
99
100
  export { SubscriptionModalHeader } from "./presentation/components/paywall/SubscriptionModalHeader";
100
- export { SubscriptionModalOverlay, type SubscriptionModalVariant } from "./presentation/components/paywall/SubscriptionModalOverlay";
101
+ export {
102
+ SubscriptionModalOverlay,
103
+ type SubscriptionModalVariant,
104
+ type ModalLayoutConfig,
105
+ } from "./presentation/components/paywall/SubscriptionModalOverlay";
101
106
 
102
107
  export { SubscriptionPlanCard } from "./presentation/components/paywall/SubscriptionPlanCard";
103
108
  export { PaywallFeaturesList } from "./presentation/components/paywall/PaywallFeaturesList";
@@ -12,14 +12,17 @@ interface PaywallHeaderProps {
12
12
  title: string;
13
13
  subtitle?: string;
14
14
  onClose: () => void;
15
+ variant?: "bottom-sheet" | "fullscreen" | "dialog";
15
16
  }
16
17
 
17
18
  export const PaywallHeader: React.FC<PaywallHeaderProps> = React.memo(
18
- ({ title, subtitle, onClose }) => {
19
+ ({ title, subtitle, onClose, variant = "bottom-sheet" }) => {
19
20
  const tokens = useAppDesignTokens();
20
21
 
22
+ const containerStyle = variant === "fullscreen" ? styles.containerFullscreen : styles.container;
23
+
21
24
  return (
22
- <View style={styles.container}>
25
+ <View style={containerStyle}>
23
26
  <View style={styles.titleContainer}>
24
27
  <AtomicText
25
28
  type="headlineLarge"
@@ -62,6 +65,14 @@ const styles = StyleSheet.create({
62
65
  paddingTop: 8,
63
66
  paddingBottom: 16,
64
67
  },
68
+ containerFullscreen: {
69
+ flexDirection: "row",
70
+ justifyContent: "space-between",
71
+ alignItems: "flex-start",
72
+ paddingHorizontal: 24,
73
+ paddingTop: 48,
74
+ paddingBottom: 16,
75
+ },
65
76
  titleContainer: {
66
77
  flex: 1,
67
78
  marginRight: 16,
@@ -110,6 +110,7 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo(
110
110
  title={displayTitle}
111
111
  subtitle={displaySubtitle}
112
112
  onClose={onClose}
113
+ variant="fullscreen"
113
114
  />
114
115
 
115
116
  <PaywallTabBar
@@ -9,12 +9,22 @@ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
9
  import type { PurchasesPackage } from "react-native-purchases";
10
10
 
11
11
  import { SubscriptionModalHeader } from "./SubscriptionModalHeader";
12
- import { SubscriptionModalOverlay, SubscriptionModalVariant } from "./SubscriptionModalOverlay";
12
+ import {
13
+ SubscriptionModalOverlay,
14
+ SubscriptionModalVariant,
15
+ ModalLayoutConfig,
16
+ } from "./SubscriptionModalOverlay";
13
17
  import { PaywallFeaturesList } from "./PaywallFeaturesList";
14
18
  import { SubscriptionPackageList } from "./SubscriptionPackageList";
15
19
  import { SubscriptionFooter } from "./SubscriptionFooter";
16
20
  import { useSubscriptionModal } from "../../hooks/useSubscriptionModal";
17
21
 
22
+ export interface SubscriptionModalStyles {
23
+ headerTopPadding?: number;
24
+ contentHorizontalPadding?: number;
25
+ contentBottomPadding?: number;
26
+ }
27
+
18
28
  export interface SubscriptionModalProps {
19
29
  visible: boolean;
20
30
  onClose: () => void;
@@ -36,9 +46,16 @@ export interface SubscriptionModalProps {
36
46
  termsOfServiceText?: string;
37
47
  showRestoreButton?: boolean;
38
48
  variant?: SubscriptionModalVariant;
39
- BackgroundComponent?: React.ComponentType<any>;
49
+ layoutConfig?: ModalLayoutConfig;
50
+ styleConfig?: SubscriptionModalStyles;
40
51
  }
41
52
 
53
+ const DEFAULT_STYLES: SubscriptionModalStyles = {
54
+ headerTopPadding: 48,
55
+ contentHorizontalPadding: 24,
56
+ contentBottomPadding: 32,
57
+ };
58
+
42
59
  export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((props) => {
43
60
  const {
44
61
  visible,
@@ -61,9 +78,13 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
61
78
  termsOfServiceText,
62
79
  showRestoreButton = true,
63
80
  variant = "bottom-sheet",
81
+ layoutConfig,
82
+ styleConfig,
64
83
  } = props;
65
84
 
66
85
  const tokens = useAppDesignTokens();
86
+ const styles_ = { ...DEFAULT_STYLES, ...styleConfig };
87
+
67
88
  const {
68
89
  selectedPkg,
69
90
  setSelectedPkg,
@@ -76,19 +97,47 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
76
97
  onClose,
77
98
  });
78
99
 
100
+ if (__DEV__) {
101
+ console.log("[SubscriptionModal] Rendering", {
102
+ visible,
103
+ variant,
104
+ packagesCount: packages.length,
105
+ featuresCount: features.length,
106
+ isLoading,
107
+ layoutConfig,
108
+ styleConfig: styles_,
109
+ });
110
+ }
111
+
79
112
  if (!visible) return null;
80
113
 
81
- const ContentWrapper = View;
82
- const containerStyle = variant === "fullscreen" ? styles.fullscreenContainer : styles.container;
114
+ const isFullscreen = variant === "fullscreen";
115
+ const containerPaddingTop = isFullscreen ? styles_.headerTopPadding : 0;
83
116
 
84
117
  return (
85
- <SubscriptionModalOverlay visible={visible} onClose={onClose} variant={variant}>
86
- <ContentWrapper style={containerStyle}>
87
- <SubscriptionModalHeader title={title} subtitle={subtitle} onClose={onClose} />
118
+ <SubscriptionModalOverlay
119
+ visible={visible}
120
+ onClose={onClose}
121
+ variant={variant}
122
+ layoutConfig={layoutConfig}
123
+ >
124
+ <View style={[styles.container, { paddingTop: containerPaddingTop }]}>
125
+ <SubscriptionModalHeader
126
+ title={title}
127
+ subtitle={subtitle}
128
+ onClose={onClose}
129
+ variant={variant}
130
+ />
88
131
 
89
132
  <ScrollView
90
133
  style={styles.scrollView}
91
- contentContainerStyle={styles.scrollContent}
134
+ contentContainerStyle={[
135
+ styles.scrollContent,
136
+ {
137
+ paddingHorizontal: styles_.contentHorizontalPadding,
138
+ paddingBottom: styles_.contentBottomPadding,
139
+ }
140
+ ]}
92
141
  showsVerticalScrollIndicator={false}
93
142
  bounces={false}
94
143
  >
@@ -124,7 +173,7 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
124
173
  onPurchase={handlePurchase}
125
174
  onRestore={handleRestore}
126
175
  />
127
- </ContentWrapper>
176
+ </View>
128
177
  </SubscriptionModalOverlay>
129
178
  );
130
179
  });
@@ -136,19 +185,11 @@ const styles = StyleSheet.create({
136
185
  flex: 1,
137
186
  width: "100%",
138
187
  },
139
- fullscreenContainer: {
140
- flex: 1,
141
- width: "100%",
142
- paddingTop: 20,
143
- paddingBottom: 20,
144
- },
145
188
  scrollView: {
146
189
  flex: 1,
147
190
  width: "100%",
148
191
  },
149
192
  scrollContent: {
150
- paddingHorizontal: 24,
151
- paddingBottom: 32,
152
193
  flexGrow: 1,
153
194
  },
154
195
  featuresSection: {
@@ -11,17 +11,19 @@ interface SubscriptionModalHeaderProps {
11
11
  title: string;
12
12
  subtitle?: string;
13
13
  onClose: () => void;
14
+ variant?: "bottom-sheet" | "fullscreen" | "dialog";
14
15
  }
15
16
 
16
17
  export const SubscriptionModalHeader: React.FC<SubscriptionModalHeaderProps> = ({
17
18
  title,
18
19
  subtitle,
19
20
  onClose,
21
+ variant = "bottom-sheet",
20
22
  }) => {
21
23
  const tokens = useAppDesignTokens();
22
24
 
23
25
  if (__DEV__) {
24
- console.log("[SubscriptionModalHeader] Rendering title:", title);
26
+ console.log("[SubscriptionModalHeader] Rendering", { title, variant, hasSubtitle: !!subtitle });
25
27
  }
26
28
 
27
29
  return (
@@ -57,7 +59,7 @@ const styles = StyleSheet.create({
57
59
  header: {
58
60
  alignItems: "center",
59
61
  paddingHorizontal: 24,
60
- paddingTop: 8,
62
+ paddingTop: 16,
61
63
  paddingBottom: 16,
62
64
  },
63
65
  closeButton: {
@@ -10,7 +10,8 @@ import {
10
10
  StyleSheet,
11
11
  TouchableOpacity,
12
12
  Dimensions,
13
- Platform
13
+ Platform,
14
+ ViewStyle,
14
15
  } from "react-native";
15
16
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
16
17
 
@@ -18,11 +19,30 @@ const { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get("window");
18
19
 
19
20
  export type SubscriptionModalVariant = "bottom-sheet" | "fullscreen" | "dialog";
20
21
 
22
+ export interface ModalLayoutConfig {
23
+ maxWidth?: number;
24
+ maxHeightPercent?: number;
25
+ widthPercent?: number;
26
+ borderRadius?: number;
27
+ backdropOpacity?: number;
28
+ horizontalPadding?: number;
29
+ }
30
+
31
+ const DEFAULT_LAYOUT: ModalLayoutConfig = {
32
+ maxWidth: 480,
33
+ maxHeightPercent: 0.88,
34
+ widthPercent: 0.92,
35
+ borderRadius: 32,
36
+ backdropOpacity: 0.85,
37
+ horizontalPadding: 20,
38
+ };
39
+
21
40
  interface SubscriptionModalOverlayProps {
22
41
  visible: boolean;
23
42
  onClose: () => void;
24
43
  children: React.ReactNode;
25
44
  variant: SubscriptionModalVariant;
45
+ layoutConfig?: ModalLayoutConfig;
26
46
  }
27
47
 
28
48
  export const SubscriptionModalOverlay: React.FC<SubscriptionModalOverlayProps> = ({
@@ -30,15 +50,42 @@ export const SubscriptionModalOverlay: React.FC<SubscriptionModalOverlayProps> =
30
50
  onClose,
31
51
  children,
32
52
  variant,
53
+ layoutConfig,
33
54
  }) => {
34
55
  const tokens = useAppDesignTokens();
56
+ const config = { ...DEFAULT_LAYOUT, ...layoutConfig };
35
57
 
36
58
  if (__DEV__) {
37
- console.log("[SubscriptionModalOverlay] Rendering variant:", variant);
59
+ console.log("[SubscriptionModalOverlay] Rendering", {
60
+ variant,
61
+ visible,
62
+ layoutConfig: config,
63
+ });
38
64
  }
39
65
 
40
66
  const isFullScreen = variant === "fullscreen";
41
67
 
68
+ const getFullscreenContentStyle = (): ViewStyle => ({
69
+ width: Math.min(SCREEN_WIDTH * (config.widthPercent ?? 0.92), config.maxWidth ?? 480),
70
+ maxHeight: SCREEN_HEIGHT * (config.maxHeightPercent ?? 0.88),
71
+ borderRadius: config.borderRadius ?? 32,
72
+ overflow: "hidden",
73
+ borderWidth: 1,
74
+ borderColor: "rgba(255, 255, 255, 0.08)",
75
+ backgroundColor: tokens.colors.surface,
76
+ ...Platform.select({
77
+ ios: {
78
+ shadowColor: "#000",
79
+ shadowOffset: { width: 0, height: 16 },
80
+ shadowOpacity: 0.5,
81
+ shadowRadius: 32,
82
+ },
83
+ android: {
84
+ elevation: 16,
85
+ },
86
+ }),
87
+ });
88
+
42
89
  if (isFullScreen) {
43
90
  return (
44
91
  <Modal
@@ -47,13 +94,16 @@ export const SubscriptionModalOverlay: React.FC<SubscriptionModalOverlayProps> =
47
94
  animationType="fade"
48
95
  onRequestClose={onClose}
49
96
  >
50
- <View style={styles.fullscreenOverlay}>
97
+ <View style={[styles.fullscreenOverlay, { paddingHorizontal: config.horizontalPadding }]}>
51
98
  <TouchableOpacity
52
- style={styles.fullscreenBackdrop}
99
+ style={[
100
+ styles.fullscreenBackdrop,
101
+ { backgroundColor: `rgba(0, 0, 0, ${config.backdropOpacity ?? 0.85})` }
102
+ ]}
53
103
  activeOpacity={1}
54
104
  onPress={onClose}
55
105
  />
56
- <View style={[styles.fullscreenContent, { backgroundColor: tokens.colors.surface }]}>
106
+ <View style={getFullscreenContentStyle()}>
57
107
  {children}
58
108
  </View>
59
109
  </View>
@@ -104,30 +154,9 @@ const styles = StyleSheet.create({
104
154
  flex: 1,
105
155
  justifyContent: "center",
106
156
  alignItems: "center",
107
- paddingHorizontal: 20,
108
157
  },
109
158
  fullscreenBackdrop: {
110
159
  ...StyleSheet.absoluteFillObject,
111
- backgroundColor: "rgba(0, 0, 0, 0.85)",
112
- },
113
- fullscreenContent: {
114
- width: Math.min(SCREEN_WIDTH * 0.92, 480),
115
- maxHeight: SCREEN_HEIGHT * 0.88,
116
- borderRadius: 32,
117
- overflow: "hidden",
118
- borderWidth: 1,
119
- borderColor: "rgba(255, 255, 255, 0.08)",
120
- ...Platform.select({
121
- ios: {
122
- shadowColor: "#000",
123
- shadowOffset: { width: 0, height: 16 },
124
- shadowOpacity: 0.5,
125
- shadowRadius: 32,
126
- },
127
- android: {
128
- elevation: 16,
129
- },
130
- }),
131
160
  },
132
161
  bottomSheet: {
133
162
  borderTopLeftRadius: 32,