@adobe/react-native-aepmessaging 7.2.1 → 7.4.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.
Files changed (293) hide show
  1. package/RCTAEPMessaging.podspec +1 -1
  2. package/README.md +145 -16
  3. package/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java +3 -0
  4. package/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java +103 -33
  5. package/babel.config.js +3 -0
  6. package/dist/module/Messaging.js +334 -0
  7. package/dist/module/Messaging.js.map +1 -0
  8. package/dist/module/index.js +30 -0
  9. package/dist/module/index.js.map +1 -0
  10. package/dist/module/models/ContentCard.js +24 -0
  11. package/dist/module/models/ContentCard.js.map +1 -0
  12. package/dist/{models → module/models}/HTMLProposition.js +8 -9
  13. package/dist/module/models/HTMLProposition.js.map +1 -0
  14. package/dist/module/models/InAppMessage.js +4 -0
  15. package/dist/module/models/InAppMessage.js.map +1 -0
  16. package/dist/module/models/JSONProposition.js +22 -0
  17. package/dist/module/models/JSONProposition.js.map +1 -0
  18. package/dist/module/models/Message.js +182 -0
  19. package/dist/module/models/Message.js.map +1 -0
  20. package/dist/module/models/MessagingDelegate.js +4 -0
  21. package/dist/module/models/MessagingDelegate.js.map +1 -0
  22. package/dist/module/models/MessagingEdgeEventType.js +24 -0
  23. package/dist/module/models/MessagingEdgeEventType.js.map +1 -0
  24. package/dist/module/models/MessagingProposition.js +57 -0
  25. package/dist/module/models/MessagingProposition.js.map +1 -0
  26. package/dist/module/models/MessagingPropositionItem.js +4 -0
  27. package/dist/module/models/MessagingPropositionItem.js.map +1 -0
  28. package/dist/module/models/PersonalizationSchema.js +26 -0
  29. package/dist/module/models/PersonalizationSchema.js.map +1 -0
  30. package/dist/module/models/PropositionItem.js +113 -0
  31. package/dist/module/models/PropositionItem.js.map +1 -0
  32. package/dist/module/models/ScopeDetails.js +2 -0
  33. package/dist/module/models/ScopeDetails.js.map +1 -0
  34. package/dist/{models/JSONProposition.js → module/models/index.js} +14 -12
  35. package/dist/module/models/index.js.map +1 -0
  36. package/dist/module/ui/components/Button/Button.js +57 -0
  37. package/dist/module/ui/components/Button/Button.js.map +1 -0
  38. package/dist/module/ui/components/Button/Button.spec.js +476 -0
  39. package/dist/module/ui/components/Button/Button.spec.js.map +1 -0
  40. package/dist/module/ui/components/ContentCardView/ContentCardView.js +257 -0
  41. package/dist/module/ui/components/ContentCardView/ContentCardView.js.map +1 -0
  42. package/dist/module/ui/components/ContentCardView/ContentCardView.spec.js +363 -0
  43. package/dist/module/ui/components/ContentCardView/ContentCardView.spec.js.map +1 -0
  44. package/dist/module/ui/components/DismissButton/DismissButton.js +70 -0
  45. package/dist/module/ui/components/DismissButton/DismissButton.js.map +1 -0
  46. package/dist/module/ui/components/DismissButton/DismissButton.spec.js +279 -0
  47. package/dist/module/ui/components/DismissButton/DismissButton.spec.js.map +1 -0
  48. package/dist/module/ui/components/FullScreenCenterView/FullScreenCenterView.js +34 -0
  49. package/dist/module/ui/components/FullScreenCenterView/FullScreenCenterView.js.map +1 -0
  50. package/dist/module/ui/components/Inbox/EmptyState.js +64 -0
  51. package/dist/module/ui/components/Inbox/EmptyState.js.map +1 -0
  52. package/dist/module/ui/components/Inbox/Inbox.js +235 -0
  53. package/dist/module/ui/components/Inbox/Inbox.js.map +1 -0
  54. package/dist/module/ui/components/Inbox/Inbox.spec.js +847 -0
  55. package/dist/module/ui/components/Inbox/Inbox.spec.js.map +1 -0
  56. package/dist/module/ui/components/Pagination/Pagination.js +176 -0
  57. package/dist/module/ui/components/Pagination/Pagination.js.map +1 -0
  58. package/dist/module/ui/components/Pagination/Pagination.spec.js +193 -0
  59. package/dist/module/ui/components/Pagination/Pagination.spec.js.map +1 -0
  60. package/dist/module/ui/components/UnreadIcon/UnreadIcon.js +184 -0
  61. package/dist/module/ui/components/UnreadIcon/UnreadIcon.js.map +1 -0
  62. package/dist/module/ui/components/UnreadIcon/UnreadIcon.spec.js +815 -0
  63. package/dist/module/ui/components/UnreadIcon/UnreadIcon.spec.js.map +1 -0
  64. package/dist/{models/ContentCard.js → module/ui/components/index.js} +12 -12
  65. package/dist/module/ui/components/index.js.map +1 -0
  66. package/dist/module/ui/hooks/index.js +18 -0
  67. package/dist/module/ui/hooks/index.js.map +1 -0
  68. package/dist/module/ui/hooks/useAspectRatio.js +33 -0
  69. package/dist/module/ui/hooks/useAspectRatio.js.map +1 -0
  70. package/dist/module/ui/hooks/useAspectRatio.spec.js +65 -0
  71. package/dist/module/ui/hooks/useAspectRatio.spec.js.map +1 -0
  72. package/dist/module/ui/hooks/useContentCardUI.js +51 -0
  73. package/dist/module/ui/hooks/useContentCardUI.js.map +1 -0
  74. package/dist/module/ui/hooks/useContentCardUI.spec.js +85 -0
  75. package/dist/module/ui/hooks/useContentCardUI.spec.js.map +1 -0
  76. package/dist/module/ui/hooks/useInbox.js +49 -0
  77. package/dist/module/ui/hooks/useInbox.js.map +1 -0
  78. package/dist/module/ui/hooks/useInbox.spec.js +93 -0
  79. package/dist/module/ui/hooks/useInbox.spec.js.map +1 -0
  80. package/dist/module/ui/hooks/useInboxSettings.js +26 -0
  81. package/dist/module/ui/hooks/useInboxSettings.js.map +1 -0
  82. package/dist/module/ui/hooks/useInboxSettings.spec.js +50 -0
  83. package/dist/module/ui/hooks/useInboxSettings.spec.js.map +1 -0
  84. package/dist/module/ui/index.js +10 -0
  85. package/dist/module/ui/index.js.map +1 -0
  86. package/dist/module/ui/providers/InboxProvider.js +27 -0
  87. package/dist/module/ui/providers/InboxProvider.js.map +1 -0
  88. package/dist/module/ui/theme/Theme.js +2 -0
  89. package/dist/module/ui/theme/Theme.js.map +1 -0
  90. package/dist/module/ui/theme/ThemeProvider.js +112 -0
  91. package/dist/module/ui/theme/ThemeProvider.js.map +1 -0
  92. package/dist/{models/InAppMessage.js → module/ui/theme/index.js} +6 -3
  93. package/dist/module/ui/theme/index.js.map +1 -0
  94. package/dist/module/ui/types/ContentViewEvent.js +2 -0
  95. package/dist/module/ui/types/ContentViewEvent.js.map +1 -0
  96. package/dist/module/ui/types/Templates.js +26 -0
  97. package/dist/module/ui/types/Templates.js.map +1 -0
  98. package/dist/{models/ScopeDetails.js → module/ui/types/index.js} +6 -3
  99. package/dist/module/ui/types/index.js.map +1 -0
  100. package/dist/module/ui/utils/generateCardHash.js +50 -0
  101. package/dist/module/ui/utils/generateCardHash.js.map +1 -0
  102. package/dist/module/ui/utils/generateCardHash.spec.js +103 -0
  103. package/dist/module/ui/utils/generateCardHash.spec.js.map +1 -0
  104. package/dist/module/ui/utils/inboxStorage.js +65 -0
  105. package/dist/module/ui/utils/inboxStorage.js.map +1 -0
  106. package/dist/module/ui/utils/inboxStorage.spec.js +123 -0
  107. package/dist/module/ui/utils/inboxStorage.spec.js.map +1 -0
  108. package/dist/module/ui/utils/index.js +5 -0
  109. package/dist/module/ui/utils/index.js.map +1 -0
  110. package/dist/{Messaging.d.ts → typescript/Messaging.d.ts} +23 -7
  111. package/dist/typescript/Messaging.d.ts.map +1 -0
  112. package/dist/{index.d.ts → typescript/index.d.ts} +4 -2
  113. package/dist/typescript/index.d.ts.map +1 -0
  114. package/dist/typescript/models/ContentCard.d.ts +57 -0
  115. package/dist/typescript/models/ContentCard.d.ts.map +1 -0
  116. package/dist/{models → typescript/models}/HTMLProposition.d.ts +1 -0
  117. package/dist/typescript/models/HTMLProposition.d.ts.map +1 -0
  118. package/dist/{models → typescript/models}/InAppMessage.d.ts +1 -0
  119. package/dist/typescript/models/InAppMessage.d.ts.map +1 -0
  120. package/dist/{models → typescript/models}/JSONProposition.d.ts +1 -0
  121. package/dist/typescript/models/JSONProposition.d.ts.map +1 -0
  122. package/dist/{models → typescript/models}/Message.d.ts +14 -0
  123. package/dist/typescript/models/Message.d.ts.map +1 -0
  124. package/dist/{models → typescript/models}/MessagingDelegate.d.ts +1 -0
  125. package/dist/typescript/models/MessagingDelegate.d.ts.map +1 -0
  126. package/dist/{models → typescript/models}/MessagingEdgeEventType.d.ts +1 -0
  127. package/dist/typescript/models/MessagingEdgeEventType.d.ts.map +1 -0
  128. package/dist/{models → typescript/models}/MessagingProposition.d.ts +1 -0
  129. package/dist/typescript/models/MessagingProposition.d.ts.map +1 -0
  130. package/dist/{models → typescript/models}/MessagingPropositionItem.d.ts +1 -0
  131. package/dist/typescript/models/MessagingPropositionItem.d.ts.map +1 -0
  132. package/dist/{models → typescript/models}/PersonalizationSchema.d.ts +2 -0
  133. package/dist/typescript/models/PersonalizationSchema.d.ts.map +1 -0
  134. package/dist/{models → typescript/models}/PropositionItem.d.ts +1 -0
  135. package/dist/typescript/models/PropositionItem.d.ts.map +1 -0
  136. package/dist/{models → typescript/models}/ScopeDetails.d.ts +1 -0
  137. package/dist/typescript/models/ScopeDetails.d.ts.map +1 -0
  138. package/dist/typescript/models/index.d.ts +11 -0
  139. package/dist/typescript/models/index.d.ts.map +1 -0
  140. package/dist/typescript/ui/components/Button/Button.d.ts +14 -0
  141. package/dist/typescript/ui/components/Button/Button.d.ts.map +1 -0
  142. package/dist/typescript/ui/components/Button/Button.spec.d.ts +2 -0
  143. package/dist/typescript/ui/components/Button/Button.spec.d.ts.map +1 -0
  144. package/dist/typescript/ui/components/ContentCardView/ContentCardView.d.ts +39 -0
  145. package/dist/typescript/ui/components/ContentCardView/ContentCardView.d.ts.map +1 -0
  146. package/dist/typescript/ui/components/ContentCardView/ContentCardView.spec.d.ts +2 -0
  147. package/dist/typescript/ui/components/ContentCardView/ContentCardView.spec.d.ts.map +1 -0
  148. package/dist/typescript/ui/components/DismissButton/DismissButton.d.ts +13 -0
  149. package/dist/typescript/ui/components/DismissButton/DismissButton.d.ts.map +1 -0
  150. package/dist/typescript/ui/components/DismissButton/DismissButton.spec.d.ts +2 -0
  151. package/dist/typescript/ui/components/DismissButton/DismissButton.spec.d.ts.map +1 -0
  152. package/dist/typescript/ui/components/FullScreenCenterView/FullScreenCenterView.d.ts +5 -0
  153. package/dist/typescript/ui/components/FullScreenCenterView/FullScreenCenterView.d.ts.map +1 -0
  154. package/dist/typescript/ui/components/Inbox/EmptyState.d.ts +19 -0
  155. package/dist/typescript/ui/components/Inbox/EmptyState.d.ts.map +1 -0
  156. package/dist/typescript/ui/components/Inbox/Inbox.d.ts +21 -0
  157. package/dist/typescript/ui/components/Inbox/Inbox.d.ts.map +1 -0
  158. package/dist/typescript/ui/components/Inbox/Inbox.spec.d.ts +2 -0
  159. package/dist/typescript/ui/components/Inbox/Inbox.spec.d.ts.map +1 -0
  160. package/dist/typescript/ui/components/Pagination/Pagination.d.ts +14 -0
  161. package/dist/typescript/ui/components/Pagination/Pagination.d.ts.map +1 -0
  162. package/dist/typescript/ui/components/Pagination/Pagination.spec.d.ts +2 -0
  163. package/dist/typescript/ui/components/Pagination/Pagination.spec.d.ts.map +1 -0
  164. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.d.ts +14 -0
  165. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.d.ts.map +1 -0
  166. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.spec.d.ts +2 -0
  167. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.spec.d.ts.map +1 -0
  168. package/dist/typescript/ui/components/index.d.ts +10 -0
  169. package/dist/typescript/ui/components/index.d.ts.map +1 -0
  170. package/dist/typescript/ui/hooks/index.d.ts +4 -0
  171. package/dist/typescript/ui/hooks/index.d.ts.map +1 -0
  172. package/dist/typescript/ui/hooks/useAspectRatio.d.ts +3 -0
  173. package/dist/typescript/ui/hooks/useAspectRatio.d.ts.map +1 -0
  174. package/dist/typescript/ui/hooks/useAspectRatio.spec.d.ts +2 -0
  175. package/dist/typescript/ui/hooks/useAspectRatio.spec.d.ts.map +1 -0
  176. package/dist/typescript/ui/hooks/useContentCardUI.d.ts +14 -0
  177. package/dist/typescript/ui/hooks/useContentCardUI.d.ts.map +1 -0
  178. package/dist/typescript/ui/hooks/useContentCardUI.spec.d.ts +2 -0
  179. package/dist/typescript/ui/hooks/useContentCardUI.spec.d.ts.map +1 -0
  180. package/dist/typescript/ui/hooks/useInbox.d.ts +12 -0
  181. package/dist/typescript/ui/hooks/useInbox.d.ts.map +1 -0
  182. package/dist/typescript/ui/hooks/useInbox.spec.d.ts +2 -0
  183. package/dist/typescript/ui/hooks/useInbox.spec.d.ts.map +1 -0
  184. package/dist/typescript/ui/hooks/useInboxSettings.d.ts +7 -0
  185. package/dist/typescript/ui/hooks/useInboxSettings.d.ts.map +1 -0
  186. package/dist/typescript/ui/hooks/useInboxSettings.spec.d.ts +2 -0
  187. package/dist/typescript/ui/hooks/useInboxSettings.spec.d.ts.map +1 -0
  188. package/dist/typescript/ui/index.d.ts +8 -0
  189. package/dist/typescript/ui/index.d.ts.map +1 -0
  190. package/dist/typescript/ui/providers/InboxProvider.d.ts +56 -0
  191. package/dist/typescript/ui/providers/InboxProvider.d.ts.map +1 -0
  192. package/dist/typescript/ui/theme/Theme.d.ts +44 -0
  193. package/dist/typescript/ui/theme/Theme.d.ts.map +1 -0
  194. package/dist/typescript/ui/theme/ThemeProvider.d.ts +21 -0
  195. package/dist/typescript/ui/theme/ThemeProvider.d.ts.map +1 -0
  196. package/dist/typescript/ui/theme/index.d.ts +3 -0
  197. package/dist/typescript/ui/theme/index.d.ts.map +1 -0
  198. package/dist/typescript/ui/types/ContentViewEvent.d.ts +9 -0
  199. package/dist/typescript/ui/types/ContentViewEvent.d.ts.map +1 -0
  200. package/dist/typescript/ui/types/Templates.d.ts +43 -0
  201. package/dist/typescript/ui/types/Templates.d.ts.map +1 -0
  202. package/dist/typescript/ui/types/index.d.ts +3 -0
  203. package/dist/typescript/ui/types/index.d.ts.map +1 -0
  204. package/dist/typescript/ui/utils/generateCardHash.d.ts +21 -0
  205. package/dist/typescript/ui/utils/generateCardHash.d.ts.map +1 -0
  206. package/dist/typescript/ui/utils/generateCardHash.spec.d.ts +2 -0
  207. package/dist/typescript/ui/utils/generateCardHash.spec.d.ts.map +1 -0
  208. package/dist/typescript/ui/utils/inboxStorage.d.ts +20 -0
  209. package/dist/typescript/ui/utils/inboxStorage.d.ts.map +1 -0
  210. package/dist/typescript/ui/utils/inboxStorage.spec.d.ts +2 -0
  211. package/dist/typescript/ui/utils/inboxStorage.spec.d.ts.map +1 -0
  212. package/dist/typescript/ui/utils/index.d.ts +3 -0
  213. package/dist/typescript/ui/utils/index.d.ts.map +1 -0
  214. package/ios/src/RCTAEPMessaging.mm +15 -0
  215. package/ios/src/RCTAEPMessaging.swift +61 -3
  216. package/ios/src/RCTAEPMessagingConstants.swift +4 -1
  217. package/jest.config.js +15 -0
  218. package/package.json +33 -5
  219. package/src/Messaging.ts +288 -32
  220. package/src/index.ts +3 -3
  221. package/src/models/ContentCard.ts +52 -27
  222. package/src/models/HTMLProposition.ts +1 -1
  223. package/src/models/JSONProposition.ts +1 -1
  224. package/src/models/Message.ts +50 -0
  225. package/src/models/PersonalizationSchema.ts +1 -0
  226. package/src/models/index.ts +22 -0
  227. package/src/ui/components/Button/Button.spec.tsx +496 -0
  228. package/src/ui/components/Button/Button.tsx +76 -0
  229. package/src/ui/components/ContentCardView/ContentCardView.spec.tsx +278 -0
  230. package/src/ui/components/ContentCardView/ContentCardView.tsx +400 -0
  231. package/src/ui/components/DismissButton/DismissButton.spec.tsx +314 -0
  232. package/src/ui/components/DismissButton/DismissButton.tsx +100 -0
  233. package/src/ui/components/FullScreenCenterView/FullScreenCenterView.tsx +32 -0
  234. package/src/ui/components/Inbox/EmptyState.tsx +89 -0
  235. package/src/ui/components/Inbox/Inbox.spec.tsx +478 -0
  236. package/src/ui/components/Inbox/Inbox.tsx +275 -0
  237. package/src/ui/components/Pagination/Pagination.spec.tsx +159 -0
  238. package/src/ui/components/Pagination/Pagination.tsx +222 -0
  239. package/src/ui/components/UnreadIcon/UnreadIcon.spec.tsx +878 -0
  240. package/src/ui/components/UnreadIcon/UnreadIcon.tsx +234 -0
  241. package/src/ui/components/index.ts +22 -0
  242. package/{dist/models/MessagingPropositionItem.js → src/ui/hooks/index.ts} +5 -4
  243. package/src/ui/hooks/useAspectRatio.spec.tsx +66 -0
  244. package/src/ui/hooks/useAspectRatio.tsx +39 -0
  245. package/src/ui/hooks/useContentCardUI.spec.tsx +82 -0
  246. package/src/ui/hooks/useContentCardUI.ts +48 -0
  247. package/src/ui/hooks/useInbox.spec.tsx +87 -0
  248. package/src/ui/hooks/useInbox.ts +46 -0
  249. package/src/ui/hooks/useInboxSettings.spec.tsx +41 -0
  250. package/src/ui/hooks/useInboxSettings.ts +24 -0
  251. package/src/ui/index.ts +7 -0
  252. package/src/ui/providers/InboxProvider.tsx +79 -0
  253. package/src/ui/theme/Theme.ts +57 -0
  254. package/src/ui/theme/ThemeProvider.tsx +120 -0
  255. package/src/ui/theme/index.ts +14 -0
  256. package/src/ui/types/ContentViewEvent.ts +20 -0
  257. package/src/ui/types/Templates.ts +77 -0
  258. package/src/ui/types/index.ts +14 -0
  259. package/src/ui/utils/generateCardHash.spec.tsx +86 -0
  260. package/src/ui/utils/generateCardHash.ts +59 -0
  261. package/src/ui/utils/inboxStorage.spec.tsx +136 -0
  262. package/src/ui/utils/inboxStorage.ts +64 -0
  263. package/src/ui/utils/index.ts +3 -0
  264. package/tutorials/ContentCardCustomizationGuide.md +661 -0
  265. package/tutorials/ContentCards.md +419 -0
  266. package/tutorials/In-App Messaging.md +31 -0
  267. package/tutorials/Inbox.md +515 -0
  268. package/tutorials/resources/image-only-template.png +0 -0
  269. package/tutorials/resources/large-image-template.png +0 -0
  270. package/tutorials/resources/small-image-template.png +0 -0
  271. package/dist/Messaging.js +0 -151
  272. package/dist/Messaging.js.map +0 -1
  273. package/dist/index.js +0 -34
  274. package/dist/index.js.map +0 -1
  275. package/dist/models/ContentCard.d.ts +0 -51
  276. package/dist/models/ContentCard.js.map +0 -1
  277. package/dist/models/HTMLProposition.js.map +0 -1
  278. package/dist/models/InAppMessage.js.map +0 -1
  279. package/dist/models/JSONProposition.js.map +0 -1
  280. package/dist/models/Message.js +0 -114
  281. package/dist/models/Message.js.map +0 -1
  282. package/dist/models/MessagingDelegate.js +0 -14
  283. package/dist/models/MessagingDelegate.js.map +0 -1
  284. package/dist/models/MessagingEdgeEventType.js +0 -24
  285. package/dist/models/MessagingEdgeEventType.js.map +0 -1
  286. package/dist/models/MessagingProposition.js +0 -59
  287. package/dist/models/MessagingProposition.js.map +0 -1
  288. package/dist/models/MessagingPropositionItem.js.map +0 -1
  289. package/dist/models/PersonalizationSchema.js +0 -25
  290. package/dist/models/PersonalizationSchema.js.map +0 -1
  291. package/dist/models/PropositionItem.js +0 -78
  292. package/dist/models/PropositionItem.js.map +0 -1
  293. package/dist/models/ScopeDetails.js.map +0 -1
@@ -0,0 +1,275 @@
1
+ /*
2
+ Copyright 2026 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the
4
+ "License"); you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
7
+ or agreed to in writing, software distributed under the License is
8
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
9
+ ANY KIND, either express or implied. See the License for the specific
10
+ language governing permissions and limitations under the License.
11
+ */
12
+
13
+ import React, { cloneElement, ReactElement, useCallback, useEffect, useMemo, useState } from "react";
14
+ import {
15
+ ActivityIndicator,
16
+ FlatList,
17
+ FlatListProps,
18
+ ListRenderItem,
19
+ ScrollView,
20
+ StyleSheet,
21
+ Text,
22
+ useWindowDimensions,
23
+ View,
24
+ } from "react-native";
25
+ import { useContentCardUI } from "../../hooks";
26
+ import InboxProvider, {
27
+ InboxSettings
28
+ } from "../../providers/InboxProvider";
29
+ import { useTheme } from "../../theme";
30
+ import { ContentViewEvent } from "../../types/ContentViewEvent";
31
+ import { ContentTemplate } from "../../types/Templates";
32
+ import { generateCardHash } from "../../utils/generateCardHash";
33
+ import { loadInboxState, saveInboxState } from "../../utils/inboxStorage";
34
+ import { ContentCardView, ContentViewProps } from "../ContentCardView/ContentCardView";
35
+ import EmptyState from "./EmptyState";
36
+
37
+ // TODO: consider localizing in the future
38
+ const DEFAULT_EMPTY_MESSAGE = 'No Content Available';
39
+
40
+ const cardStatusStore = new Map<string, { dismissed: Set<string>; interacted: Set<string> }>();
41
+
42
+ export interface InboxProps<T> extends Partial<FlatListProps<T>> {
43
+ LoadingComponent?: ReactElement | null;
44
+ ErrorComponent?: ReactElement | null;
45
+ FallbackComponent?: ReactElement | null;
46
+ EmptyComponent?: ReactElement | null;
47
+ surface: string;
48
+ settings: InboxSettings | null;
49
+ isLoading?: boolean;
50
+ error?: boolean;
51
+ CardProps?: Partial<ContentViewProps>;
52
+ }
53
+
54
+ function InboxInner<T extends ContentTemplate>({
55
+ contentContainerStyle,
56
+ LoadingComponent = <ActivityIndicator />,
57
+ ErrorComponent = null,
58
+ FallbackComponent = null,
59
+ EmptyComponent,
60
+ settings,
61
+ surface,
62
+ style,
63
+ CardProps,
64
+ ListHeaderComponent,
65
+ ...props
66
+ }: InboxProps<T> & {
67
+ settings: InboxSettings;
68
+ }) {
69
+ const { colors, isDark } = useTheme();
70
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
71
+ const isLandscape = windowWidth > windowHeight;
72
+ const { content, error, isLoading } = useContentCardUI(surface);
73
+
74
+ const { content: contentSettings } = settings;
75
+ const { capacity, heading, layout, emptyStateSettings, isUnreadEnabled } = contentSettings;
76
+
77
+ const getStore = useCallback(() => {
78
+ if (!cardStatusStore.has(surface)) {
79
+ cardStatusStore.set(surface, { dismissed: new Set(), interacted: new Set() });
80
+ }
81
+ return cardStatusStore.get(surface)!;
82
+ }, [surface]);
83
+
84
+ const [dismissedIds, setDismissedIds] = useState<Set<string>>(() => new Set(getStore().dismissed));
85
+ const [interactedIds, setInteractedIds] = useState<Set<string>>(() =>
86
+ isUnreadEnabled ? new Set(getStore().interacted) : new Set());
87
+
88
+ const [storageLoaded, setStorageLoaded] = useState(false);
89
+ useEffect(() => {
90
+ const activityId = settings.activityId;
91
+ if (!activityId) {
92
+ setStorageLoaded(true);
93
+ return;
94
+ }
95
+ let cancelled = false;
96
+ loadInboxState(activityId).then((persisted) => {
97
+ if (cancelled) return;
98
+ const store = getStore();
99
+ persisted.dismissed.forEach((id) => store.dismissed.add(id));
100
+ persisted.interacted.forEach((id) => store.interacted.add(id));
101
+ setDismissedIds(new Set(store.dismissed));
102
+ if (isUnreadEnabled) setInteractedIds(new Set(store.interacted));
103
+ setStorageLoaded(true);
104
+ });
105
+ return () => { cancelled = true; };
106
+ }, [settings.activityId, isUnreadEnabled, getStore]);
107
+
108
+ useEffect(() => {
109
+ if (!storageLoaded) return;
110
+ const store = getStore();
111
+ setDismissedIds(new Set(store.dismissed));
112
+ if (isUnreadEnabled) setInteractedIds(new Set(store.interacted));
113
+ }, [content, isUnreadEnabled, getStore, storageLoaded]);
114
+
115
+ const isHorizontal = layout?.orientation === 'horizontal';
116
+
117
+ const displayCards = useMemo(() => {
118
+ if (!content) return [] as T[];
119
+ return content
120
+ .map((it) => ({ item: it, hash: generateCardHash(it) }))
121
+ .filter(({ hash }) => !dismissedIds.has(hash))
122
+ .map(({ item, hash }) => {
123
+ const shouldBeRead =
124
+ isUnreadEnabled && (interactedIds.has(hash) || item.isRead === true);
125
+ return shouldBeRead ? { ...item, isRead: true } : item;
126
+ })
127
+ .slice(0, capacity) as T[];
128
+ }, [content, capacity, isUnreadEnabled, dismissedIds, interactedIds]);
129
+
130
+ const handleCardEvent = useCallback((event?: ContentViewEvent, data?: ContentTemplate, nativeEvent?: any) => {
131
+ const cardHash = generateCardHash(data);
132
+ const store = getStore();
133
+ if (event === 'onDismiss' && !store.dismissed.has(cardHash)) {
134
+ store.dismissed.add(cardHash);
135
+ setDismissedIds((prev) => new Set(prev).add(cardHash));
136
+ if (settings.activityId) {
137
+ saveInboxState(settings.activityId, {
138
+ dismissed: [...store.dismissed],
139
+ interacted: [...store.interacted],
140
+ });
141
+ }
142
+ } else if (event === 'onInteract' && isUnreadEnabled && !store.interacted.has(cardHash)) {
143
+ store.interacted.add(cardHash);
144
+ setInteractedIds((prev) => new Set(prev).add(cardHash));
145
+ if (settings.activityId) {
146
+ saveInboxState(settings.activityId, {
147
+ dismissed: [...store.dismissed],
148
+ interacted: [...store.interacted],
149
+ });
150
+ }
151
+ }
152
+ CardProps?.listener?.(event, data, nativeEvent);
153
+ }, [CardProps, isUnreadEnabled, getStore, settings.activityId]);
154
+
155
+ const renderItem: ListRenderItem<T> = useCallback(({ item }) => (
156
+ <ContentCardView
157
+ template={item}
158
+ {...CardProps}
159
+ listener={handleCardEvent}
160
+ style={isHorizontal ? [styles.horizontalCardStyles, { width: Math.floor(windowWidth * 0.75) }] : undefined}
161
+ />
162
+ ), [isHorizontal, CardProps, windowWidth, handleCardEvent]);
163
+
164
+ const EmptyList = useCallback(() => EmptyComponent ?
165
+ cloneElement(EmptyComponent, { ...emptyStateSettings }) as React.ReactElement :
166
+ <EmptyState
167
+ image={isDark ? emptyStateSettings?.image?.darkUrl ?? '' : emptyStateSettings?.image?.url ?? ''}
168
+ text={emptyStateSettings?.message?.content || DEFAULT_EMPTY_MESSAGE}
169
+ />, [isDark, emptyStateSettings, EmptyComponent]);
170
+
171
+ if (isLoading) return LoadingComponent;
172
+ if (error) return ErrorComponent;
173
+ if (!content) return FallbackComponent;
174
+
175
+ const topOfInbox =
176
+ <View>
177
+ {ListHeaderComponent && ListHeaderComponent as React.ReactElement}
178
+ {heading?.content ? (
179
+ <Text
180
+ accessibilityRole="header"
181
+ style={[styles.heading, { color: colors.textPrimary }]}
182
+ >
183
+ {heading.content}
184
+ </Text>
185
+ ) : null}
186
+ </View>
187
+
188
+
189
+ const listBody = (
190
+ <FlatList
191
+ {...props}
192
+ data={displayCards}
193
+ keyExtractor={(item: { id: string }) => item.id}
194
+ contentContainerStyle={[
195
+ contentContainerStyle,
196
+ isHorizontal && styles.horizontalListContent,
197
+ styles.inbox
198
+ ]}
199
+ horizontal={isHorizontal}
200
+ renderItem={renderItem}
201
+ ListEmptyComponent={<EmptyList />}
202
+ ListHeaderComponent={!isHorizontal ? topOfInbox : undefined}
203
+ />
204
+ );
205
+
206
+ const horizontalChrome =
207
+ isHorizontal ? (
208
+ isLandscape ? (
209
+ <ScrollView
210
+ contentContainerStyle={{ paddingBottom: 8 }}
211
+ keyboardShouldPersistTaps="handled"
212
+ nestedScrollEnabled
213
+ >
214
+ {topOfInbox}
215
+ {listBody}
216
+ </ScrollView>
217
+ ) : (
218
+ <View>
219
+ {topOfInbox}
220
+ {listBody}
221
+ </View>
222
+ )
223
+ ) : listBody;
224
+
225
+ return <InboxProvider settings={settings}>{horizontalChrome}</InboxProvider>;
226
+ }
227
+
228
+ /**
229
+ * @experimental First React Native inbox UI
230
+ */
231
+ export function Inbox<T extends ContentTemplate>({
232
+ LoadingComponent = <ActivityIndicator />,
233
+ ErrorComponent = null,
234
+ FallbackComponent = null,
235
+ surface,
236
+ settings,
237
+ isLoading,
238
+ error,
239
+ ...props
240
+ }: InboxProps<T>): React.ReactElement {
241
+
242
+ if (isLoading) return LoadingComponent as React.ReactElement;
243
+ if (error) return ErrorComponent as React.ReactElement;
244
+ if (!settings) return FallbackComponent as React.ReactElement;
245
+
246
+ return (
247
+ <InboxInner
248
+ settings={settings}
249
+ surface={surface}
250
+ LoadingComponent={LoadingComponent}
251
+ ErrorComponent={ErrorComponent}
252
+ FallbackComponent={FallbackComponent}
253
+ {...props}
254
+ />
255
+ );
256
+ }
257
+
258
+ const styles = StyleSheet.create({
259
+ inbox: {
260
+ flexGrow: 1
261
+ },
262
+ heading: {
263
+ fontWeight: '600',
264
+ fontSize: 18,
265
+ lineHeight: 28,
266
+ textAlign: 'center',
267
+ marginBottom: 16
268
+ },
269
+ horizontalCardStyles: {
270
+ flex: 0
271
+ },
272
+ horizontalListContent: {
273
+ alignItems: 'center'
274
+ }
275
+ });
@@ -0,0 +1,159 @@
1
+ /*
2
+ Copyright 2026 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the
4
+ "License"); you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
7
+ or agreed to in writing, software distributed under the License is
8
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
9
+ ANY KIND, either express or implied. See the License for the specific
10
+ language governing permissions and limitations under the License.
11
+ */
12
+ import { fireEvent, render, screen } from '@testing-library/react-native';
13
+ import React from 'react';
14
+ import { Animated } from 'react-native';
15
+ import { Pagination } from './Pagination';
16
+
17
+ // Use fake timers for animation timing
18
+ jest.useFakeTimers();
19
+
20
+ // Stub Animated APIs to avoid act warnings and native errors
21
+ beforeAll(() => {
22
+ jest.spyOn(Animated, 'timing').mockReturnValue({ start: (cb?: any) => cb && cb() } as any);
23
+ jest.spyOn(Animated, 'spring').mockReturnValue({ start: (cb?: any) => cb && cb() } as any);
24
+ jest.spyOn(Animated, 'parallel').mockReturnValue({ start: (cb?: any) => cb && cb() } as any);
25
+ jest.spyOn(require('react-native'), 'useColorScheme').mockReturnValue('light');
26
+ });
27
+
28
+ afterAll(() => {
29
+ (Animated.timing as unknown as jest.Mock)?.mockRestore?.();
30
+ (Animated.spring as unknown as jest.Mock)?.mockRestore?.();
31
+ (Animated.parallel as unknown as jest.Mock)?.mockRestore?.();
32
+ });
33
+
34
+ describe('Pagination - rendering', () => {
35
+ it('renders nothing when totalPages <= 1', () => {
36
+ const { toJSON } = render(
37
+ <Pagination currentPage={0} totalPages={1} onPageChange={() => {}} />
38
+ );
39
+ expect(toJSON()).toBeNull();
40
+ });
41
+
42
+ });
43
+
44
+ describe('Pagination - interactions', () => {
45
+ it('renders dots and calls onPageChange when a dot is pressed', () => {
46
+ const onPageChange = jest.fn();
47
+ render(<Pagination currentPage={1} totalPages={3} onPageChange={onPageChange} />);
48
+ const dots = screen.getAllByRole('button');
49
+ expect(dots.length).toBe(3);
50
+ // press the last dot (page index 2)
51
+ fireEvent.press(dots[2]);
52
+ expect(onPageChange).toHaveBeenCalledWith(2);
53
+ });
54
+
55
+ });
56
+
57
+ describe('Pagination - theming and sizing', () => {
58
+ it('uses theme default colors when activeColor/inactiveColor not provided (light)', () => {
59
+ jest.spyOn(require('react-native'), 'useColorScheme').mockReturnValue('light');
60
+ render(<Pagination currentPage={0} totalPages={2} onPageChange={() => {}} />);
61
+ const views = screen.UNSAFE_getAllByType(Animated.View);
62
+ // Filter to dot views by borderRadius style (unique to dot style)
63
+ const dotViews = views.filter((v) => {
64
+ const styles = (Array.isArray(v.props.style) ? v.props.style : [v.props.style]).flat(Infinity).filter(Boolean);
65
+ return styles.some((s: any) => s && s.borderRadius === 50);
66
+ });
67
+ const dot0Styles = (Array.isArray(dotViews[0].props.style) ? dotViews[0].props.style : [dotViews[0].props.style]).flat(Infinity);
68
+ const dot1Styles = (Array.isArray(dotViews[1].props.style) ? dotViews[1].props.style : [dotViews[1].props.style]).flat(Infinity);
69
+ expect(dot0Styles.some((s: any) => s && s.backgroundColor === '#0a7ea4')).toBe(true); // active
70
+ expect(dot1Styles.some((s: any) => s && s.backgroundColor === '#687076')).toBe(true); // inactive
71
+ });
72
+
73
+ it('uses theme default colors when activeColor/inactiveColor not provided (dark)', () => {
74
+ jest.spyOn(require('react-native'), 'useColorScheme').mockReturnValue('dark');
75
+ render(<Pagination currentPage={0} totalPages={2} onPageChange={() => {}} />);
76
+ const views = screen.UNSAFE_getAllByType(Animated.View);
77
+ const dotViews = views.filter((v) => {
78
+ const styles = (Array.isArray(v.props.style) ? v.props.style : [v.props.style]).flat(Infinity).filter(Boolean);
79
+ return styles.some((s: any) => s && s.borderRadius === 50);
80
+ });
81
+ const dot0Styles = (Array.isArray(dotViews[0].props.style) ? dotViews[0].props.style : [dotViews[0].props.style]).flat(Infinity);
82
+ const dot1Styles = (Array.isArray(dotViews[1].props.style) ? dotViews[1].props.style : [dotViews[1].props.style]).flat(Infinity);
83
+ expect(dot0Styles.some((s: any) => s && s.backgroundColor === '#fff')).toBe(true); // active
84
+ expect(dot1Styles.some((s: any) => s && s.backgroundColor === '#9BA1A6')).toBe(true); // inactive
85
+ });
86
+
87
+ it('respects custom active/inactive colors and dot size/spacing', () => {
88
+ render(
89
+ <Pagination
90
+ currentPage={0}
91
+ totalPages={2}
92
+ onPageChange={() => {}}
93
+ activeColor="red"
94
+ inactiveColor="gray"
95
+ dotSize={12}
96
+ spacing={10}
97
+ />
98
+ );
99
+ const views = screen.UNSAFE_getAllByType(Animated.View);
100
+ const dotViews = views.filter((v) => {
101
+ const styles = (Array.isArray(v.props.style) ? v.props.style : [v.props.style]).flat(Infinity).filter(Boolean);
102
+ return styles.some((s: any) => s && s.borderRadius === 50);
103
+ });
104
+ const dot0Styles = (Array.isArray(dotViews[0].props.style) ? dotViews[0].props.style : [dotViews[0].props.style]).flat(Infinity);
105
+ expect(dot0Styles.some((s: any) => s && s.backgroundColor === 'red')).toBe(true);
106
+ expect(dot0Styles.some((s: any) => s && s.width === 12 && s.height === 12)).toBe(true);
107
+ });
108
+
109
+ });
110
+
111
+ describe('Pagination - sliding and windowing', () => {
112
+ it('slides when the visible window changes (Animated.timing called)', () => {
113
+ const timingSpy = jest.spyOn(Animated, 'timing');
114
+ const { rerender } = render(
115
+ <Pagination currentPage={0} totalPages={10} maxVisibleDots={5} onPageChange={() => {}} />
116
+ );
117
+ // Move within the same window (no slide expected)
118
+ rerender(<Pagination currentPage={4} totalPages={10} maxVisibleDots={5} onPageChange={() => {}} />);
119
+ const callsBefore = (timingSpy as unknown as jest.Mock).mock.calls.length;
120
+ // Move to next window (should slide)
121
+ rerender(<Pagination currentPage={5} totalPages={10} maxVisibleDots={5} onPageChange={() => {}} />);
122
+ const callsAfter = (timingSpy as unknown as jest.Mock).mock.calls.length;
123
+ expect(callsAfter).toBeGreaterThan(callsBefore);
124
+ });
125
+
126
+ it('adjusts start page near the end (endPage === totalPages - 1 branch)', () => {
127
+ const onPageChange = jest.fn();
128
+ render(
129
+ <Pagination currentPage={9} totalPages={10} maxVisibleDots={5} onPageChange={onPageChange} />
130
+ );
131
+ // Expect visible pages to be [5,6,7,8,9]; pressing first dot should send 5
132
+ const dots = screen.getAllByRole('button');
133
+ expect(dots.length).toBe(5);
134
+ fireEvent.press(dots[0]);
135
+ expect(onPageChange).toHaveBeenCalledWith(5);
136
+ fireEvent.press(dots[4]);
137
+ expect(onPageChange).toHaveBeenCalledWith(9);
138
+ });
139
+
140
+ it('slides backward when the visible window shifts left (direction -1)', () => {
141
+ const timingSpy = jest.spyOn(Animated, 'timing');
142
+ const setValueSpy = jest.spyOn((Animated.Value as any).prototype, 'setValue');
143
+
144
+ const { rerender } = render(
145
+ <Pagination currentPage={9} totalPages={10} maxVisibleDots={5} onPageChange={() => {}} />
146
+ );
147
+ rerender(<Pagination currentPage={4} totalPages={10} maxVisibleDots={5} onPageChange={() => {}} />);
148
+
149
+ expect((timingSpy as unknown as jest.Mock).mock.calls.length).toBeGreaterThan(0);
150
+
151
+ const calls = (setValueSpy as unknown as jest.Mock).mock.calls as any[];
152
+ const lastNumericArg = [...calls].reverse().map(c => c[0]).find(a => typeof a === 'number');
153
+ expect(lastNumericArg).toBeLessThan(0); // negative offset indicates sliding left
154
+
155
+ (setValueSpy as unknown as jest.Mock).mockRestore?.();
156
+ });
157
+ });
158
+
159
+
@@ -0,0 +1,222 @@
1
+ /*
2
+ Copyright 2026 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the
4
+ "License"); you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
7
+ or agreed to in writing, software distributed under the License is
8
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
9
+ ANY KIND, either express or implied. See the License for the specific
10
+ language governing permissions and limitations under the License.
11
+ */
12
+
13
+ import React, { useEffect, useRef } from "react";
14
+ import {
15
+ Animated,
16
+ Pressable,
17
+ StyleSheet,
18
+ View
19
+ } from "react-native";
20
+ import { useTheme } from "../../theme";
21
+
22
+ const PaginationDot = ({
23
+ page,
24
+ isActive,
25
+ finalInactiveColor,
26
+ finalActiveColor,
27
+ dotSize,
28
+ onPageChange,
29
+ }: {
30
+ page: number;
31
+ isActive: boolean;
32
+ finalInactiveColor?: string;
33
+ finalActiveColor?: string;
34
+ dotSize: number;
35
+ onPageChange: (page: number) => void;
36
+ }) => {
37
+ const scaleAnim = useRef(new Animated.Value(isActive ? 1.2 : 1)).current;
38
+ const opacityAnim = useRef(new Animated.Value(isActive ? 1 : 0.6)).current;
39
+
40
+ useEffect(() => {
41
+ const animation = Animated.parallel([
42
+ Animated.spring(scaleAnim, {
43
+ toValue: isActive ? 1.2 : 1,
44
+ useNativeDriver: true,
45
+ tension: 100,
46
+ friction: 8,
47
+ }),
48
+ Animated.timing(opacityAnim, {
49
+ toValue: isActive ? 1 : 0.6,
50
+ duration: 200,
51
+ useNativeDriver: true,
52
+ }),
53
+ ]);
54
+ animation.start();
55
+ return () => {
56
+ // stop both if unmounted
57
+ scaleAnim.stopAnimation();
58
+ opacityAnim.stopAnimation();
59
+ };
60
+ }, [isActive, scaleAnim, opacityAnim]);
61
+
62
+ return (
63
+ <Pressable
64
+ accessibilityRole="button"
65
+ accessibilityState={{ selected: isActive }}
66
+ accessibilityLabel={`Page ${page + 1}`}
67
+ onPress={() => onPageChange(page)}
68
+ style={({ pressed }) => [
69
+ styles.dotContainer,
70
+ { opacity: pressed ? 0.7 : 1 },
71
+ ]}
72
+ >
73
+ <Animated.View
74
+ style={[
75
+ styles.dot,
76
+ {
77
+ width: dotSize,
78
+ height: dotSize,
79
+ backgroundColor: isActive ? finalActiveColor : finalInactiveColor,
80
+ opacity: opacityAnim,
81
+ transform: [{ scale: scaleAnim }],
82
+ },
83
+ ]}
84
+ />
85
+ </Pressable>
86
+ );
87
+ };
88
+
89
+ export interface PaginationProps {
90
+ currentPage: number;
91
+ totalPages: number;
92
+ onPageChange: (page: number) => void;
93
+ maxVisibleDots?: number;
94
+ activeColor?: string;
95
+ inactiveColor?: string;
96
+ dotSize?: number;
97
+ spacing?: number;
98
+ }
99
+
100
+ export const Pagination: React.FC<PaginationProps> = ({
101
+ currentPage,
102
+ totalPages,
103
+ onPageChange,
104
+ maxVisibleDots = 5,
105
+ activeColor,
106
+ inactiveColor,
107
+ dotSize = 8,
108
+ spacing = 8,
109
+ }) => {
110
+ const { colors } = useTheme();
111
+
112
+ const slideAnim = useRef(new Animated.Value(0)).current;
113
+ const prevVisiblePagesRef = useRef<number[]>([]);
114
+
115
+ const finalActiveColor = activeColor || colors.activeColor;
116
+ const finalInactiveColor = inactiveColor || colors.inactiveColor;
117
+
118
+ // Calculate which dots to show
119
+ const getVisiblePages = () => {
120
+ if (totalPages <= maxVisibleDots) {
121
+ return Array.from({ length: totalPages }, (_, i) => i);
122
+ }
123
+
124
+ const halfVisible = Math.floor(maxVisibleDots / 2);
125
+ let startPage = Math.max(0, currentPage - halfVisible);
126
+ let endPage = Math.min(totalPages - 1, startPage + maxVisibleDots - 1);
127
+
128
+ // Adjust start if we're near the end
129
+ if (endPage === totalPages - 1) {
130
+ startPage = Math.max(0, endPage - maxVisibleDots + 1);
131
+ }
132
+
133
+ return Array.from(
134
+ { length: endPage - startPage + 1 },
135
+ (_, i) => startPage + i
136
+ );
137
+ };
138
+
139
+ const visiblePages = getVisiblePages();
140
+
141
+ // Animate sliding when visible pages change
142
+ useEffect(() => {
143
+ const prevVisiblePages = prevVisiblePagesRef.current;
144
+ let started = false;
145
+
146
+ if (prevVisiblePages.length > 0 && totalPages > maxVisibleDots) {
147
+ const prevStartPage = prevVisiblePages[0];
148
+ const currentStartPage = visiblePages[0];
149
+
150
+ if (prevStartPage !== currentStartPage) {
151
+ const direction = currentStartPage > prevStartPage ? 1 : -1;
152
+ const dotWidth = dotSize + spacing;
153
+ slideAnim.setValue(direction * dotWidth);
154
+ Animated.timing(slideAnim, {
155
+ toValue: 0,
156
+ duration: 300,
157
+ useNativeDriver: true,
158
+ }).start();
159
+ started = true;
160
+ }
161
+ }
162
+
163
+ prevVisiblePagesRef.current = visiblePages;
164
+
165
+ return () => {
166
+ if (started) slideAnim.stopAnimation();
167
+ };
168
+ }, [visiblePages, slideAnim, dotSize, spacing, maxVisibleDots, totalPages]);
169
+
170
+ if (totalPages <= 1) {
171
+ return null;
172
+ }
173
+
174
+ return (
175
+ <View style={styles.container}>
176
+ <Animated.View
177
+ style={[
178
+ styles.dotsContainer,
179
+ {
180
+ gap: spacing,
181
+ transform: [{ translateX: slideAnim }]
182
+ }
183
+ ]}
184
+ >
185
+ {visiblePages.map((page) => (
186
+ <PaginationDot
187
+ key={page}
188
+ page={page}
189
+ isActive={page === currentPage}
190
+ finalInactiveColor={finalInactiveColor}
191
+ finalActiveColor={finalActiveColor}
192
+ dotSize={dotSize}
193
+ onPageChange={onPageChange}
194
+ />
195
+ ))}
196
+ </Animated.View>
197
+ </View>
198
+ );
199
+ }
200
+
201
+ const styles = StyleSheet.create({
202
+ container: {
203
+ alignItems: "center",
204
+ justifyContent: "center",
205
+ overflow: "hidden",
206
+ flex: 1,
207
+ },
208
+ dotsContainer: {
209
+ flexDirection: "row",
210
+ alignItems: "center",
211
+ justifyContent: "center",
212
+ },
213
+ dotContainer: {
214
+ alignItems: "center",
215
+ justifyContent: "center",
216
+ },
217
+ dot: {
218
+ borderRadius: 50,
219
+ },
220
+ });
221
+
222
+ export default Pagination;