@billdog.io/react-native 1.0.0-beta.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.
Files changed (309) hide show
  1. package/BillDogReactNative.podspec +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/android/build.gradle +99 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
  7. package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
  8. package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
  9. package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
  10. package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
  11. package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
  12. package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
  13. package/dist/AdvancedPaywallCache.d.ts +70 -0
  14. package/dist/AdvancedPaywallCache.js +344 -0
  15. package/dist/BillDog.d.ts +255 -0
  16. package/dist/BillDog.js +918 -0
  17. package/dist/BillDogConfiguration.d.ts +145 -0
  18. package/dist/BillDogConfiguration.js +229 -0
  19. package/dist/ComponentRenderer.d.ts +17 -0
  20. package/dist/ComponentRenderer.js +886 -0
  21. package/dist/LayoutEngine.d.ts +101 -0
  22. package/dist/LayoutEngine.js +394 -0
  23. package/dist/LocalizationResolver.d.ts +99 -0
  24. package/dist/LocalizationResolver.js +266 -0
  25. package/dist/NativePaywallView.d.ts +36 -0
  26. package/dist/NativePaywallView.js +59 -0
  27. package/dist/PaywallCache.d.ts +74 -0
  28. package/dist/PaywallCache.js +293 -0
  29. package/dist/PaywallStateManager.d.ts +62 -0
  30. package/dist/PaywallStateManager.js +147 -0
  31. package/dist/PaywallView.d.ts +42 -0
  32. package/dist/PaywallView.js +275 -0
  33. package/dist/ProductVariableProvider.d.ts +60 -0
  34. package/dist/ProductVariableProvider.js +239 -0
  35. package/dist/VariableResolver.d.ts +120 -0
  36. package/dist/VariableResolver.js +379 -0
  37. package/dist/WebViewPaywall.d.ts +75 -0
  38. package/dist/WebViewPaywall.js +360 -0
  39. package/dist/animations/presets.d.ts +49 -0
  40. package/dist/animations/presets.js +367 -0
  41. package/dist/components/AccoladeCpsComponent.d.ts +13 -0
  42. package/dist/components/AccoladeCpsComponent.js +248 -0
  43. package/dist/components/AccordionComponent.d.ts +10 -0
  44. package/dist/components/AccordionComponent.js +187 -0
  45. package/dist/components/AnimatedWrapper.d.ts +35 -0
  46. package/dist/components/AnimatedWrapper.js +277 -0
  47. package/dist/components/CarouselComponent.d.ts +23 -0
  48. package/dist/components/CarouselComponent.js +484 -0
  49. package/dist/components/ChartCurveComponent.d.ts +13 -0
  50. package/dist/components/ChartCurveComponent.js +333 -0
  51. package/dist/components/CheckboxComponent.d.ts +9 -0
  52. package/dist/components/CheckboxComponent.js +172 -0
  53. package/dist/components/ConfettiComponent.d.ts +9 -0
  54. package/dist/components/ConfettiComponent.js +172 -0
  55. package/dist/components/CountdownComponent.d.ts +16 -0
  56. package/dist/components/CountdownComponent.js +414 -0
  57. package/dist/components/DelayedXComponent.d.ts +12 -0
  58. package/dist/components/DelayedXComponent.js +240 -0
  59. package/dist/components/DrawerModalComponent.d.ts +20 -0
  60. package/dist/components/DrawerModalComponent.js +286 -0
  61. package/dist/components/GradientView.d.ts +38 -0
  62. package/dist/components/GradientView.js +126 -0
  63. package/dist/components/HapticFeedbackComponent.d.ts +16 -0
  64. package/dist/components/HapticFeedbackComponent.js +64 -0
  65. package/dist/components/HorizontalProductsComponent.d.ts +17 -0
  66. package/dist/components/HorizontalProductsComponent.js +284 -0
  67. package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
  68. package/dist/components/MultiChoiceGridComponent.js +234 -0
  69. package/dist/components/NPSScaleComponent.d.ts +16 -0
  70. package/dist/components/NPSScaleComponent.js +260 -0
  71. package/dist/components/NavigationComponent.d.ts +26 -0
  72. package/dist/components/NavigationComponent.js +302 -0
  73. package/dist/components/OAuthButtonComponent.d.ts +20 -0
  74. package/dist/components/OAuthButtonComponent.js +237 -0
  75. package/dist/components/ProgressLoaderComponent.d.ts +13 -0
  76. package/dist/components/ProgressLoaderComponent.js +308 -0
  77. package/dist/components/ProgressStepperComponent.d.ts +18 -0
  78. package/dist/components/ProgressStepperComponent.js +228 -0
  79. package/dist/components/SegmentedPickerComponent.d.ts +10 -0
  80. package/dist/components/SegmentedPickerComponent.js +221 -0
  81. package/dist/components/ShareButtonsComponent.d.ts +21 -0
  82. package/dist/components/ShareButtonsComponent.js +265 -0
  83. package/dist/components/ShareCodeComponent.d.ts +16 -0
  84. package/dist/components/ShareCodeComponent.js +274 -0
  85. package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
  86. package/dist/components/SingleChoiceGridComponent.js +210 -0
  87. package/dist/components/SliderComponent.d.ts +9 -0
  88. package/dist/components/SliderComponent.js +263 -0
  89. package/dist/components/StarRatingInputComponent.d.ts +20 -0
  90. package/dist/components/StarRatingInputComponent.js +198 -0
  91. package/dist/components/StickerCompositeComponent.d.ts +13 -0
  92. package/dist/components/StickerCompositeComponent.js +136 -0
  93. package/dist/components/TabsComponent.d.ts +19 -0
  94. package/dist/components/TabsComponent.js +431 -0
  95. package/dist/components/TextInputComponent.d.ts +18 -0
  96. package/dist/components/TextInputComponent.js +227 -0
  97. package/dist/components/TimelineComponent.d.ts +19 -0
  98. package/dist/components/TimelineComponent.js +104 -0
  99. package/dist/components/ToggleComponent.d.ts +14 -0
  100. package/dist/components/ToggleComponent.js +132 -0
  101. package/dist/components/VerticalProductsComponent.d.ts +17 -0
  102. package/dist/components/VerticalProductsComponent.js +311 -0
  103. package/dist/dynamicValues.d.ts +36 -0
  104. package/dist/dynamicValues.js +142 -0
  105. package/dist/filters/dateFilters.d.ts +50 -0
  106. package/dist/filters/dateFilters.js +390 -0
  107. package/dist/filters/index.d.ts +18 -0
  108. package/dist/filters/index.js +253 -0
  109. package/dist/hooks/useOfferings.d.ts +76 -0
  110. package/dist/hooks/useOfferings.js +99 -0
  111. package/dist/hooks/usePaywall.d.ts +27 -0
  112. package/dist/hooks/usePaywall.js +114 -0
  113. package/dist/hooks/usePlacement.d.ts +34 -0
  114. package/dist/hooks/usePlacement.js +89 -0
  115. package/dist/hooks/useSubscription.d.ts +40 -0
  116. package/dist/hooks/useSubscription.js +97 -0
  117. package/dist/index.d.ts +98 -0
  118. package/dist/index.js +198 -0
  119. package/dist/models/BillDogError.d.ts +40 -0
  120. package/dist/models/BillDogError.js +11 -0
  121. package/dist/models/CacheFetchPolicy.d.ts +8 -0
  122. package/dist/models/CacheFetchPolicy.js +4 -0
  123. package/dist/models/CustomProduct.d.ts +32 -0
  124. package/dist/models/CustomProduct.js +15 -0
  125. package/dist/models/CustomerInfo.d.ts +17 -0
  126. package/dist/models/CustomerInfo.js +11 -0
  127. package/dist/models/Delegate.d.ts +20 -0
  128. package/dist/models/Delegate.js +11 -0
  129. package/dist/models/EntitlementInfo.d.ts +19 -0
  130. package/dist/models/EntitlementInfo.js +2 -0
  131. package/dist/models/Placement.d.ts +54 -0
  132. package/dist/models/Placement.js +15 -0
  133. package/dist/models/PurchaseParams.d.ts +39 -0
  134. package/dist/models/PurchaseParams.js +60 -0
  135. package/dist/models/PurchaseResult.d.ts +30 -0
  136. package/dist/models/PurchaseResult.js +15 -0
  137. package/dist/models/RestoreResult.d.ts +30 -0
  138. package/dist/models/RestoreResult.js +22 -0
  139. package/dist/models/RestoredItem.d.ts +26 -0
  140. package/dist/models/RestoredItem.js +13 -0
  141. package/dist/modules/ABTest.d.ts +65 -0
  142. package/dist/modules/ABTest.js +113 -0
  143. package/dist/modules/Analytics.d.ts +91 -0
  144. package/dist/modules/Analytics.js +182 -0
  145. package/dist/modules/Survey.d.ts +65 -0
  146. package/dist/modules/Survey.js +82 -0
  147. package/dist/modules/VirtualCurrency.d.ts +63 -0
  148. package/dist/modules/VirtualCurrency.js +147 -0
  149. package/dist/murmur.d.ts +23 -0
  150. package/dist/murmur.js +104 -0
  151. package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
  152. package/dist/notifications/LiveActivitiesManager.js +106 -0
  153. package/dist/notifications/NotificationScheduler.d.ts +55 -0
  154. package/dist/notifications/NotificationScheduler.js +199 -0
  155. package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
  156. package/dist/notifications/PurchaseNotificationHandler.js +55 -0
  157. package/dist/notifications/useDeviceToken.d.ts +36 -0
  158. package/dist/notifications/useDeviceToken.js +97 -0
  159. package/dist/notifications/useLiveActivity.d.ts +39 -0
  160. package/dist/notifications/useLiveActivity.js +104 -0
  161. package/dist/review/ReviewAnalytics.d.ts +34 -0
  162. package/dist/review/ReviewAnalytics.js +73 -0
  163. package/dist/review/ReviewManager.d.ts +83 -0
  164. package/dist/review/ReviewManager.js +339 -0
  165. package/dist/review/ReviewTimingManager.d.ts +82 -0
  166. package/dist/review/ReviewTimingManager.js +224 -0
  167. package/dist/survey-core/types.d.ts +215 -0
  168. package/dist/survey-core/types.js +8 -0
  169. package/dist/types.d.ts +438 -0
  170. package/dist/types.js +158 -0
  171. package/dist/utils/ApiClient.d.ts +65 -0
  172. package/dist/utils/ApiClient.js +132 -0
  173. package/dist/utils/CSSConverter.d.ts +49 -0
  174. package/dist/utils/CSSConverter.js +470 -0
  175. package/dist/utils/animations.d.ts +41 -0
  176. package/dist/utils/animations.js +194 -0
  177. package/dist/utils/calendarIntegration.d.ts +36 -0
  178. package/dist/utils/calendarIntegration.js +89 -0
  179. package/dist/utils/configurationResolver.d.ts +23 -0
  180. package/dist/utils/configurationResolver.js +78 -0
  181. package/dist/utils/deviceDetection.d.ts +38 -0
  182. package/dist/utils/deviceDetection.js +59 -0
  183. package/dist/utils/hapticFeedback.d.ts +17 -0
  184. package/dist/utils/hapticFeedback.js +53 -0
  185. package/dist/utils/iconMapper.d.ts +26 -0
  186. package/dist/utils/iconMapper.js +113 -0
  187. package/dist/utils/notificationPermissions.d.ts +13 -0
  188. package/dist/utils/notificationPermissions.js +51 -0
  189. package/dist/utils/priceFormatting.d.ts +19 -0
  190. package/dist/utils/priceFormatting.js +99 -0
  191. package/dist/utils/shareSheet.d.ts +17 -0
  192. package/dist/utils/shareSheet.js +42 -0
  193. package/dist/utils/subscriptionUpgrade.d.ts +84 -0
  194. package/dist/utils/subscriptionUpgrade.js +100 -0
  195. package/dist/valueUtils.d.ts +48 -0
  196. package/dist/valueUtils.js +129 -0
  197. package/dist/xFlowTags.d.ts +18 -0
  198. package/dist/xFlowTags.js +385 -0
  199. package/ios/BillDogABTest.m +50 -0
  200. package/ios/BillDogABTest.swift +188 -0
  201. package/ios/BillDogAnalytics.m +105 -0
  202. package/ios/BillDogAnalytics.swift +324 -0
  203. package/ios/BillDogBridge.m +222 -0
  204. package/ios/BillDogBridge.swift +715 -0
  205. package/ios/BillDogLiveActivities.m +41 -0
  206. package/ios/BillDogLiveActivities.swift +104 -0
  207. package/ios/BillDogPaywallViewManager.m +39 -0
  208. package/ios/BillDogPaywallViewManager.swift +361 -0
  209. package/ios/BillDogSurvey.m +34 -0
  210. package/ios/BillDogSurvey.swift +257 -0
  211. package/ios/BillDogVirtualCurrencyBridge.m +63 -0
  212. package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
  213. package/junit.xml +25 -0
  214. package/package.json +61 -0
  215. package/react-native.config.js +13 -0
  216. package/src/AdvancedPaywallCache.ts +433 -0
  217. package/src/BillDog.ts +983 -0
  218. package/src/BillDogConfiguration.ts +318 -0
  219. package/src/ComponentRenderer.tsx +1325 -0
  220. package/src/LayoutEngine.ts +459 -0
  221. package/src/LocalizationResolver.ts +311 -0
  222. package/src/NativePaywallView.tsx +132 -0
  223. package/src/PaywallCache.ts +358 -0
  224. package/src/PaywallStateManager.ts +154 -0
  225. package/src/PaywallView.tsx +328 -0
  226. package/src/ProductVariableProvider.ts +282 -0
  227. package/src/VariableResolver.ts +516 -0
  228. package/src/WebViewPaywall.tsx +454 -0
  229. package/src/animations/presets.ts +421 -0
  230. package/src/components/AccoladeCpsComponent.tsx +259 -0
  231. package/src/components/AccordionComponent.tsx +213 -0
  232. package/src/components/AnimatedWrapper.tsx +316 -0
  233. package/src/components/CarouselComponent.tsx +615 -0
  234. package/src/components/ChartCurveComponent.tsx +452 -0
  235. package/src/components/CheckboxComponent.tsx +185 -0
  236. package/src/components/ConfettiComponent.tsx +178 -0
  237. package/src/components/CountdownComponent.tsx +454 -0
  238. package/src/components/DelayedXComponent.tsx +266 -0
  239. package/src/components/DrawerModalComponent.tsx +324 -0
  240. package/src/components/GradientView.tsx +129 -0
  241. package/src/components/HapticFeedbackComponent.tsx +39 -0
  242. package/src/components/HorizontalProductsComponent.tsx +329 -0
  243. package/src/components/MultiChoiceGridComponent.tsx +265 -0
  244. package/src/components/NPSScaleComponent.tsx +294 -0
  245. package/src/components/NavigationComponent.tsx +329 -0
  246. package/src/components/OAuthButtonComponent.tsx +266 -0
  247. package/src/components/ProgressLoaderComponent.tsx +366 -0
  248. package/src/components/ProgressStepperComponent.tsx +250 -0
  249. package/src/components/SegmentedPickerComponent.tsx +245 -0
  250. package/src/components/ShareButtonsComponent.tsx +299 -0
  251. package/src/components/ShareCodeComponent.tsx +303 -0
  252. package/src/components/SingleChoiceGridComponent.tsx +229 -0
  253. package/src/components/SliderComponent.tsx +280 -0
  254. package/src/components/StarRatingInputComponent.tsx +223 -0
  255. package/src/components/StickerCompositeComponent.tsx +168 -0
  256. package/src/components/TabsComponent.tsx +496 -0
  257. package/src/components/TextInputComponent.tsx +258 -0
  258. package/src/components/TimelineComponent.tsx +129 -0
  259. package/src/components/ToggleComponent.tsx +142 -0
  260. package/src/components/VerticalProductsComponent.tsx +357 -0
  261. package/src/dynamicValues.ts +180 -0
  262. package/src/filters/dateFilters.ts +411 -0
  263. package/src/filters/index.ts +281 -0
  264. package/src/hooks/useOfferings.ts +192 -0
  265. package/src/hooks/usePaywall.ts +154 -0
  266. package/src/hooks/usePlacement.ts +143 -0
  267. package/src/hooks/useSubscription.ts +154 -0
  268. package/src/index.ts +245 -0
  269. package/src/models/BillDogError.ts +24 -0
  270. package/src/models/CacheFetchPolicy.ts +13 -0
  271. package/src/models/CustomProduct.ts +33 -0
  272. package/src/models/CustomerInfo.ts +26 -0
  273. package/src/models/Delegate.ts +22 -0
  274. package/src/models/EntitlementInfo.ts +27 -0
  275. package/src/models/Placement.ts +50 -0
  276. package/src/models/PurchaseParams.ts +81 -0
  277. package/src/models/PurchaseResult.ts +32 -0
  278. package/src/models/RestoreResult.ts +27 -0
  279. package/src/models/RestoredItem.ts +25 -0
  280. package/src/modules/ABTest.ts +131 -0
  281. package/src/modules/Analytics.ts +227 -0
  282. package/src/modules/Survey.ts +126 -0
  283. package/src/modules/VirtualCurrency.ts +223 -0
  284. package/src/murmur.ts +102 -0
  285. package/src/notifications/LiveActivitiesManager.ts +140 -0
  286. package/src/notifications/NotificationScheduler.ts +292 -0
  287. package/src/notifications/PurchaseNotificationHandler.ts +67 -0
  288. package/src/notifications/useDeviceToken.ts +116 -0
  289. package/src/notifications/useLiveActivity.ts +125 -0
  290. package/src/review/ReviewAnalytics.ts +89 -0
  291. package/src/review/ReviewManager.ts +355 -0
  292. package/src/review/ReviewTimingManager.ts +265 -0
  293. package/src/survey-core/types.ts +244 -0
  294. package/src/types/ambient.d.ts +9 -0
  295. package/src/types.ts +680 -0
  296. package/src/utils/ApiClient.ts +163 -0
  297. package/src/utils/CSSConverter.ts +552 -0
  298. package/src/utils/animations.ts +232 -0
  299. package/src/utils/calendarIntegration.ts +113 -0
  300. package/src/utils/configurationResolver.ts +106 -0
  301. package/src/utils/deviceDetection.ts +83 -0
  302. package/src/utils/hapticFeedback.ts +55 -0
  303. package/src/utils/iconMapper.ts +102 -0
  304. package/src/utils/notificationPermissions.ts +48 -0
  305. package/src/utils/priceFormatting.ts +119 -0
  306. package/src/utils/shareSheet.ts +55 -0
  307. package/src/utils/subscriptionUpgrade.ts +183 -0
  308. package/src/valueUtils.ts +108 -0
  309. package/src/xFlowTags.ts +463 -0
@@ -0,0 +1,266 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { View, Text, StyleSheet, Animated, TouchableOpacity } from 'react-native';
3
+ import { PaywallComponent, PaywallConfiguration } from '../types';
4
+
5
+ interface DelayedXComponentProps {
6
+ component: PaywallComponent;
7
+ configuration?: PaywallConfiguration;
8
+ renderChild?: (child: PaywallComponent) => JSX.Element;
9
+ stateManager?: any;
10
+ isPreviewMode?: boolean;
11
+ onButtonPress?: (componentId: string, action: string) => void;
12
+ }
13
+
14
+ type AnimationType = 'fade' | 'scale' | 'slide' | 'none';
15
+ type CountdownStyle = 'circular' | 'text' | 'none';
16
+ type Position = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
17
+
18
+ export const DelayedXComponent: React.FC<DelayedXComponentProps> = ({
19
+ component,
20
+ configuration,
21
+ renderChild,
22
+ stateManager,
23
+ isPreviewMode = false,
24
+ onButtonPress,
25
+ }) => {
26
+ const props = component.properties as any;
27
+
28
+ // Properties with full parity
29
+ const delay = props.delay ?? 3;
30
+ const showCountdown = props.showCountdown ?? false;
31
+ const countdownStyle: CountdownStyle = props.countdownStyle ?? 'circular';
32
+ const animationType: AnimationType = props.animationType ?? 'scale';
33
+ const disabled = props.disabled ?? false;
34
+ const position: Position = props.position ?? 'top-right';
35
+ const buttonSize = props.buttonSize ?? 32;
36
+ const backgroundColor = props.backgroundColor ?? 'rgba(0, 0, 0, 0.3)';
37
+ const iconColor = props.iconColor ?? '#FFFFFF';
38
+ const iconSize = props.iconSize ?? 14;
39
+
40
+ const [isVisible, setIsVisible] = useState(!isPreviewMode);
41
+ const [remainingTime, setRemainingTime] = useState(delay);
42
+ const [hasError, setHasError] = useState(false);
43
+ const fadeAnim = useRef(new Animated.Value(0)).current;
44
+ const scaleAnim = useRef(new Animated.Value(0.8)).current;
45
+ const slideAnim = useRef(new Animated.Value(-50)).current;
46
+
47
+ // Expose state to state manager
48
+ useEffect(() => {
49
+ if (stateManager) {
50
+ stateManager.setState?.(`element:${component.id}.isVisible`, isVisible);
51
+ stateManager.setState?.(`element:${component.id}.remainingTime`, remainingTime);
52
+ }
53
+ }, [isVisible, remainingTime, component.id, stateManager]);
54
+
55
+ useEffect(() => {
56
+ // If disabled, never show
57
+ if (disabled) {
58
+ setIsVisible(false);
59
+ return;
60
+ }
61
+
62
+ // In builder mode, always show content
63
+ if (!isPreviewMode) {
64
+ setIsVisible(true);
65
+ fadeAnim.setValue(1);
66
+ scaleAnim.setValue(1);
67
+ slideAnim.setValue(0);
68
+ return;
69
+ }
70
+
71
+ setIsVisible(false);
72
+ setRemainingTime(delay);
73
+ fadeAnim.setValue(0);
74
+ scaleAnim.setValue(0.8);
75
+ slideAnim.setValue(-50);
76
+
77
+ let countdownInterval: NodeJS.Timeout;
78
+ let showTimeout: NodeJS.Timeout;
79
+
80
+ try {
81
+ // Countdown timer
82
+ countdownInterval = setInterval(() => {
83
+ setRemainingTime((prev: number) => {
84
+ if (prev <= 1) {
85
+ clearInterval(countdownInterval);
86
+ return 0;
87
+ }
88
+ return prev - 1;
89
+ });
90
+ }, 1000);
91
+
92
+ // Show after delay with animation
93
+ showTimeout = setTimeout(() => {
94
+ setIsVisible(true);
95
+ runAppearAnimation();
96
+ }, delay * 1000);
97
+ } catch (error) {
98
+ // Error handling: show immediately if timer fails
99
+ console.warn('DelayedXComponent timer error, showing immediately:', error);
100
+ setHasError(true);
101
+ setIsVisible(true);
102
+ fadeAnim.setValue(1);
103
+ scaleAnim.setValue(1);
104
+ slideAnim.setValue(0);
105
+ }
106
+
107
+ return () => {
108
+ clearInterval(countdownInterval);
109
+ clearTimeout(showTimeout);
110
+ };
111
+ }, [delay, isPreviewMode, disabled]);
112
+
113
+ const runAppearAnimation = () => {
114
+ const animations: Animated.CompositeAnimation[] = [];
115
+
116
+ if (animationType === 'fade' || animationType === 'scale') {
117
+ animations.push(
118
+ Animated.timing(fadeAnim, {
119
+ toValue: 1,
120
+ duration: 200,
121
+ useNativeDriver: true,
122
+ })
123
+ );
124
+ }
125
+
126
+ if (animationType === 'scale') {
127
+ animations.push(
128
+ Animated.spring(scaleAnim, {
129
+ toValue: 1,
130
+ useNativeDriver: true,
131
+ tension: 50,
132
+ friction: 8,
133
+ })
134
+ );
135
+ }
136
+
137
+ if (animationType === 'slide') {
138
+ animations.push(
139
+ Animated.timing(slideAnim, {
140
+ toValue: 0,
141
+ duration: 200,
142
+ useNativeDriver: true,
143
+ })
144
+ );
145
+ animations.push(
146
+ Animated.timing(fadeAnim, {
147
+ toValue: 1,
148
+ duration: 200,
149
+ useNativeDriver: true,
150
+ })
151
+ );
152
+ }
153
+
154
+ if (animationType === 'none') {
155
+ fadeAnim.setValue(1);
156
+ scaleAnim.setValue(1);
157
+ slideAnim.setValue(0);
158
+ } else {
159
+ Animated.parallel(animations).start();
160
+ }
161
+ };
162
+
163
+ const getAnimatedStyle = () => {
164
+ switch (animationType) {
165
+ case 'fade':
166
+ return { opacity: fadeAnim };
167
+ case 'scale':
168
+ return {
169
+ opacity: fadeAnim,
170
+ transform: [{ scale: scaleAnim }],
171
+ };
172
+ case 'slide':
173
+ return {
174
+ opacity: fadeAnim,
175
+ transform: [{ translateY: slideAnim }],
176
+ };
177
+ case 'none':
178
+ default:
179
+ return {};
180
+ }
181
+ };
182
+
183
+ const getPositionStyle = () => {
184
+ const base = { position: 'absolute' as const };
185
+ switch (position) {
186
+ case 'top-left':
187
+ return { ...base, top: 16, left: 16 };
188
+ case 'top-right':
189
+ return { ...base, top: 16, right: 16 };
190
+ case 'bottom-left':
191
+ return { ...base, bottom: 16, left: 16 };
192
+ case 'bottom-right':
193
+ return { ...base, bottom: 16, right: 16 };
194
+ default:
195
+ return { ...base, top: 16, right: 16 };
196
+ }
197
+ };
198
+
199
+ const handlePress = () => {
200
+ onButtonPress?.(component.id, 'close');
201
+ };
202
+
203
+ // If disabled, render nothing
204
+ if (disabled) {
205
+ return null;
206
+ }
207
+
208
+ // In builder mode, always render children
209
+ if (!isPreviewMode) {
210
+ return (
211
+ <View style={styles.container}>
212
+ {component.children?.map((child) =>
213
+ renderChild ? renderChild(child) : null
214
+ )}
215
+ </View>
216
+ );
217
+ }
218
+
219
+ if (isVisible) {
220
+ return (
221
+ <Animated.View style={[styles.container, getAnimatedStyle()]}>
222
+ {component.children?.map((child) =>
223
+ renderChild ? renderChild(child) : null
224
+ )}
225
+ </Animated.View>
226
+ );
227
+ }
228
+
229
+ // Render countdown based on style
230
+ if (showCountdown && countdownStyle !== 'none') {
231
+ if (countdownStyle === 'circular') {
232
+ return (
233
+ <View style={[styles.countdownContainer, { width: buttonSize, height: buttonSize, borderRadius: buttonSize / 2, backgroundColor }]}>
234
+ <Text style={[styles.countdownText, { color: iconColor, fontSize: iconSize }]}>{remainingTime}</Text>
235
+ </View>
236
+ );
237
+ }
238
+
239
+ if (countdownStyle === 'text') {
240
+ return (
241
+ <Text style={[styles.countdownTextOnly, { color: iconColor, fontSize: iconSize }]}>
242
+ {remainingTime}s
243
+ </Text>
244
+ );
245
+ }
246
+ }
247
+
248
+ return null;
249
+ };
250
+
251
+ const styles = StyleSheet.create({
252
+ container: {
253
+ // Container inherits layout from parent
254
+ },
255
+ countdownContainer: {
256
+ alignItems: 'center',
257
+ justifyContent: 'center',
258
+ },
259
+ countdownText: {
260
+ fontWeight: '600',
261
+ },
262
+ countdownTextOnly: {
263
+ fontWeight: '600',
264
+ opacity: 0.6,
265
+ },
266
+ });
@@ -0,0 +1,324 @@
1
+ /**
2
+ * DrawerModalComponent - React Native
3
+ *
4
+ * Slide-in drawer/modal with overlay, animation, and gesture support.
5
+ * Features:
6
+ * - State integration for open/close control
7
+ * - Overlay tap to dismiss
8
+ * - Pan gesture to dismiss
9
+ * - Smooth animations
10
+ */
11
+
12
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
13
+ import {
14
+ View,
15
+ TouchableWithoutFeedback,
16
+ Animated,
17
+ PanResponder,
18
+ Dimensions,
19
+ StyleSheet,
20
+ Modal,
21
+ } from 'react-native';
22
+ import { PaywallComponent } from '../types';
23
+
24
+ interface DrawerModalComponentProps {
25
+ component: PaywallComponent;
26
+ stateManager?: any;
27
+ isPreviewMode?: boolean;
28
+ renderComponent?: (component: PaywallComponent) => React.ReactNode;
29
+ }
30
+
31
+ type Direction = 'left' | 'right' | 'top' | 'bottom';
32
+
33
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
34
+
35
+ export const DrawerModalComponent: React.FC<DrawerModalComponentProps> = ({
36
+ component,
37
+ stateManager,
38
+ isPreviewMode = false,
39
+ renderComponent,
40
+ }) => {
41
+ const props = component.properties as any;
42
+ const componentId = component.id;
43
+
44
+ // Configuration
45
+ const direction: Direction = props.direction || 'bottom';
46
+ const showOverlay = props.showOverlay !== false;
47
+ const overlayColor = props.overlayColor || '#000000';
48
+ const overlayOpacity = (props.overlayOpacity ?? 50) / 100;
49
+ const closeOnOverlayClick = props.closeOnOverlayClick !== false;
50
+ const dismissType = props.dismissType || 'interactive';
51
+
52
+ // Animation settings
53
+ const animationDuration = props.animationDuration || 300;
54
+
55
+ // Sizing
56
+ const drawerWidthValue = props.drawerWidth?.value || 85;
57
+ const drawerWidthUnit = props.drawerWidth?.unit || '%';
58
+ const drawerHeightValue = props.drawerHeight?.value || 400;
59
+ const drawerHeightUnit = props.drawerHeight?.unit || 'px';
60
+
61
+ // Calculate actual dimensions
62
+ const drawerWidth = drawerWidthUnit === '%'
63
+ ? (SCREEN_WIDTH * drawerWidthValue / 100)
64
+ : drawerWidthValue;
65
+ const drawerHeight = drawerHeightUnit === '%'
66
+ ? (SCREEN_HEIGHT * drawerHeightValue / 100)
67
+ : drawerHeightValue;
68
+
69
+ // Corner radius
70
+ const cornerRadius = props.cornerRadius?.value || 16;
71
+
72
+ // Background
73
+ const backgroundColor = props.backgroundColor?.color || '#FFFFFF';
74
+
75
+ // State
76
+ const [isOpen, setIsOpen] = useState(props.isOpen !== false);
77
+ const animatedValue = useRef(new Animated.Value(isOpen ? 1 : 0)).current;
78
+ const panValue = useRef(new Animated.Value(0)).current;
79
+
80
+ // Get translation values based on direction
81
+ const getTranslation = useCallback(() => {
82
+ const isHorizontal = direction === 'left' || direction === 'right';
83
+ const size = isHorizontal ? drawerWidth : drawerHeight;
84
+ const multiplier = direction === 'right' || direction === 'bottom' ? 1 : -1;
85
+
86
+ return {
87
+ isHorizontal,
88
+ size,
89
+ multiplier,
90
+ closedValue: size * multiplier,
91
+ };
92
+ }, [direction, drawerWidth, drawerHeight]);
93
+
94
+ // Subscribe to state manager
95
+ useEffect(() => {
96
+ if (stateManager) {
97
+ // Initialize state
98
+ stateManager.updateState(`element:${componentId}.isOpen`, isOpen);
99
+
100
+ return stateManager.subscribeToState(
101
+ `element:${componentId}.isOpen`,
102
+ (value: boolean) => {
103
+ if (value !== isOpen) {
104
+ setIsOpen(value);
105
+ }
106
+ }
107
+ );
108
+ }
109
+ }, [componentId, stateManager]);
110
+
111
+ // Animate when isOpen changes
112
+ useEffect(() => {
113
+ Animated.timing(animatedValue, {
114
+ toValue: isOpen ? 1 : 0,
115
+ duration: animationDuration,
116
+ useNativeDriver: true,
117
+ }).start();
118
+
119
+ if (stateManager) {
120
+ stateManager.updateState(`element:${componentId}.isOpen`, isOpen);
121
+ stateManager.updateState(`element:${componentId}.direction`, direction);
122
+ }
123
+ }, [isOpen, animationDuration, animatedValue, componentId, stateManager, direction]);
124
+
125
+ // Handle close
126
+ const handleClose = useCallback(() => {
127
+ setIsOpen(false);
128
+ if (stateManager) {
129
+ stateManager.updateState(`element:${componentId}.isOpen`, false);
130
+ }
131
+ }, [componentId, stateManager]);
132
+
133
+ // Pan responder for interactive dismiss
134
+ const panResponder = useRef(
135
+ PanResponder.create({
136
+ onStartShouldSetPanResponder: () => dismissType === 'interactive',
137
+ onMoveShouldSetPanResponder: (_, gestureState) => {
138
+ if (dismissType !== 'interactive') return false;
139
+
140
+ const { dx, dy } = gestureState;
141
+ const { isHorizontal, multiplier } = getTranslation();
142
+
143
+ // Only respond if dragging in the correct direction
144
+ if (isHorizontal) {
145
+ return Math.abs(dx) > Math.abs(dy) && dx * multiplier > 0;
146
+ } else {
147
+ return Math.abs(dy) > Math.abs(dx) && dy * multiplier > 0;
148
+ }
149
+ },
150
+ onPanResponderMove: (_, gestureState) => {
151
+ const { isHorizontal, multiplier } = getTranslation();
152
+ const delta = isHorizontal ? gestureState.dx : gestureState.dy;
153
+
154
+ // Only allow dragging in dismiss direction
155
+ if (delta * multiplier > 0) {
156
+ panValue.setValue(delta);
157
+ }
158
+ },
159
+ onPanResponderRelease: (_, gestureState) => {
160
+ const { isHorizontal, size, multiplier } = getTranslation();
161
+ const delta = isHorizontal ? gestureState.dx : gestureState.dy;
162
+ const velocity = isHorizontal ? gestureState.vx : gestureState.vy;
163
+
164
+ // Dismiss if dragged more than 30% or with sufficient velocity
165
+ if ((Math.abs(delta) > size * 0.3) || (Math.abs(velocity) > 0.5 && delta * multiplier > 0)) {
166
+ // Animate out then close
167
+ Animated.timing(panValue, {
168
+ toValue: size * multiplier,
169
+ duration: 200,
170
+ useNativeDriver: true,
171
+ }).start(() => {
172
+ panValue.setValue(0);
173
+ handleClose();
174
+ });
175
+ } else {
176
+ // Snap back
177
+ Animated.spring(panValue, {
178
+ toValue: 0,
179
+ useNativeDriver: true,
180
+ }).start();
181
+ }
182
+ },
183
+ })
184
+ ).current;
185
+
186
+ // Calculate animated styles
187
+ const { isHorizontal, closedValue } = getTranslation();
188
+
189
+ const overlayAnimatedStyle = {
190
+ opacity: animatedValue.interpolate({
191
+ inputRange: [0, 1],
192
+ outputRange: [0, overlayOpacity],
193
+ }),
194
+ };
195
+
196
+ const drawerBaseTranslate = animatedValue.interpolate({
197
+ inputRange: [0, 1],
198
+ outputRange: [closedValue, 0],
199
+ });
200
+
201
+ const drawerTransform = isHorizontal
202
+ ? { translateX: Animated.add(drawerBaseTranslate, panValue) }
203
+ : { translateY: Animated.add(drawerBaseTranslate, panValue) };
204
+
205
+ // Position styles based on direction
206
+ const getPositionStyle = (): any => {
207
+ switch (direction) {
208
+ case 'left':
209
+ return { left: 0, top: 0, bottom: 0, width: drawerWidth };
210
+ case 'right':
211
+ return { right: 0, top: 0, bottom: 0, width: drawerWidth };
212
+ case 'top':
213
+ return { top: 0, left: 0, right: 0, height: drawerHeight };
214
+ case 'bottom':
215
+ default:
216
+ return { bottom: 0, left: 0, right: 0, height: drawerHeight };
217
+ }
218
+ };
219
+
220
+ // Border radius based on direction
221
+ const getBorderRadius = () => {
222
+ switch (direction) {
223
+ case 'left':
224
+ return { borderTopRightRadius: cornerRadius, borderBottomRightRadius: cornerRadius };
225
+ case 'right':
226
+ return { borderTopLeftRadius: cornerRadius, borderBottomLeftRadius: cornerRadius };
227
+ case 'top':
228
+ return { borderBottomLeftRadius: cornerRadius, borderBottomRightRadius: cornerRadius };
229
+ case 'bottom':
230
+ default:
231
+ return { borderTopLeftRadius: cornerRadius, borderTopRightRadius: cornerRadius };
232
+ }
233
+ };
234
+
235
+ // Don't render if not open and animation complete
236
+ if (!isOpen && (animatedValue as any)._value === 0) {
237
+ return null;
238
+ }
239
+
240
+ return (
241
+ <Modal
242
+ visible={isOpen}
243
+ transparent
244
+ animationType="none"
245
+ onRequestClose={handleClose}
246
+ >
247
+ <View style={styles.container}>
248
+ {/* Overlay */}
249
+ {showOverlay && (
250
+ <TouchableWithoutFeedback
251
+ onPress={closeOnOverlayClick ? handleClose : undefined}
252
+ >
253
+ <Animated.View
254
+ style={[
255
+ styles.overlay,
256
+ { backgroundColor: overlayColor },
257
+ overlayAnimatedStyle,
258
+ ]}
259
+ />
260
+ </TouchableWithoutFeedback>
261
+ )}
262
+
263
+ {/* Drawer */}
264
+ <Animated.View
265
+ style={[
266
+ styles.drawer,
267
+ getPositionStyle(),
268
+ getBorderRadius(),
269
+ { backgroundColor },
270
+ { transform: [drawerTransform] },
271
+ ]}
272
+ {...panResponder.panHandlers}
273
+ >
274
+ {/* Drag handle for bottom/top drawers */}
275
+ {dismissType === 'interactive' && (direction === 'bottom' || direction === 'top') && (
276
+ <View style={styles.dragHandleContainer}>
277
+ <View style={styles.dragHandle} />
278
+ </View>
279
+ )}
280
+
281
+ {/* Children */}
282
+ <View style={styles.content}>
283
+ {component.children?.map((child, index) => (
284
+ <View key={child.id || index}>
285
+ {renderComponent?.(child)}
286
+ </View>
287
+ ))}
288
+ </View>
289
+ </Animated.View>
290
+ </View>
291
+ </Modal>
292
+ );
293
+ };
294
+
295
+ const styles = StyleSheet.create({
296
+ container: {
297
+ flex: 1,
298
+ },
299
+ overlay: {
300
+ ...StyleSheet.absoluteFillObject,
301
+ },
302
+ drawer: {
303
+ position: 'absolute',
304
+ shadowColor: '#000',
305
+ shadowOffset: { width: 0, height: -4 },
306
+ shadowOpacity: 0.25,
307
+ shadowRadius: 12,
308
+ elevation: 16,
309
+ },
310
+ dragHandleContainer: {
311
+ alignItems: 'center',
312
+ paddingVertical: 8,
313
+ },
314
+ dragHandle: {
315
+ width: 36,
316
+ height: 4,
317
+ backgroundColor: '#D1D5DB',
318
+ borderRadius: 2,
319
+ },
320
+ content: {
321
+ flex: 1,
322
+ padding: 16,
323
+ },
324
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * GradientView Component
3
+ * Wraps content with a gradient background using LinearGradient
4
+ * Falls back to solid color if LinearGradient is not available
5
+ */
6
+
7
+ import React, { useMemo } from 'react';
8
+ import { View, ViewStyle, StyleProp } from 'react-native';
9
+ import { GradientConfig, angleToGradientPoints } from '../utils/CSSConverter';
10
+
11
+ // Try to import LinearGradient - user must have expo-linear-gradient or react-native-linear-gradient installed
12
+ let LinearGradient: any = null;
13
+
14
+ try {
15
+ // Try expo-linear-gradient first (most common in Expo projects)
16
+ LinearGradient = require('expo-linear-gradient').LinearGradient;
17
+ } catch {
18
+ try {
19
+ // Fall back to react-native-linear-gradient
20
+ LinearGradient = require('react-native-linear-gradient').default;
21
+ } catch {
22
+ // LinearGradient not available - will use fallback
23
+ LinearGradient = null;
24
+ }
25
+ }
26
+
27
+ export interface GradientViewProps {
28
+ gradient: GradientConfig;
29
+ fallbackColor?: string;
30
+ style?: StyleProp<ViewStyle>;
31
+ children?: React.ReactNode;
32
+ /** Border radius to apply to gradient */
33
+ borderRadius?: number;
34
+ /** Border width */
35
+ borderWidth?: number;
36
+ /** Border color */
37
+ borderColor?: string;
38
+ }
39
+
40
+ /**
41
+ * GradientView renders a gradient background
42
+ * Uses LinearGradient if available, falls back to first stop color otherwise
43
+ */
44
+ export const GradientView: React.FC<GradientViewProps> = ({
45
+ gradient,
46
+ fallbackColor,
47
+ style,
48
+ children,
49
+ borderRadius,
50
+ borderWidth,
51
+ borderColor,
52
+ }) => {
53
+ // Calculate gradient colors and positions
54
+ const { colors, locations, start, end } = useMemo(() => {
55
+ const colors = gradient.stops.map(stop => stop.color);
56
+ const locations = gradient.stops.map(stop => {
57
+ const pos = stop.position ?? 0;
58
+ return pos <= 1 ? pos : pos / 100;
59
+ });
60
+
61
+ // Get start/end points based on angle
62
+ const points = gradient.type === 'linear'
63
+ ? angleToGradientPoints(gradient.angle ?? 180)
64
+ : { start: { x: 0.5, y: 0.5 }, end: { x: 1, y: 1 } }; // Radial approximation
65
+
66
+ return { colors, locations, ...points };
67
+ }, [gradient]);
68
+
69
+ // Container style with border properties
70
+ const containerStyle: ViewStyle = {
71
+ borderRadius,
72
+ borderWidth,
73
+ borderColor,
74
+ overflow: 'hidden', // Ensure gradient respects borderRadius
75
+ };
76
+
77
+ // If LinearGradient is available, use it
78
+ if (LinearGradient && gradient.type === 'linear') {
79
+ return (
80
+ <LinearGradient
81
+ colors={colors}
82
+ locations={locations}
83
+ start={start}
84
+ end={end}
85
+ style={[style, containerStyle]}
86
+ >
87
+ {children}
88
+ </LinearGradient>
89
+ );
90
+ }
91
+
92
+ // For radial gradients or if LinearGradient isn't available, use fallback
93
+ const backgroundColor = fallbackColor || colors[0] || 'transparent';
94
+
95
+ return (
96
+ <View style={[style, containerStyle, { backgroundColor }]}>
97
+ {children}
98
+ </View>
99
+ );
100
+ };
101
+
102
+ /**
103
+ * Check if LinearGradient is available
104
+ */
105
+ export function isGradientSupported(): boolean {
106
+ return LinearGradient !== null;
107
+ }
108
+
109
+ /**
110
+ * Get gradient background style or fallback color
111
+ * Use this when you need to decide between GradientView or regular View
112
+ */
113
+ export function getGradientStyleOrFallback(
114
+ gradient: GradientConfig | undefined,
115
+ fallbackColor?: string
116
+ ): { useGradient: boolean; backgroundColor: string; gradient?: GradientConfig } {
117
+ if (!gradient || !LinearGradient) {
118
+ return {
119
+ useGradient: false,
120
+ backgroundColor: fallbackColor || gradient?.stops[0]?.color || 'transparent',
121
+ };
122
+ }
123
+
124
+ return {
125
+ useGradient: true,
126
+ backgroundColor: fallbackColor || gradient.stops[0]?.color || 'transparent',
127
+ gradient,
128
+ };
129
+ }