@applicaster/zapp-react-native-ui-components 15.1.0-rc.8 → 16.0.0-alpha.1833861900

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 (157) hide show
  1. package/Components/BackgroundImage/BackgroundImage.tsx +42 -0
  2. package/Components/BackgroundImage/BackgroundImage.tv.android.tsx +28 -0
  3. package/Components/BackgroundImage/BackgroundImage.tv.ios.tsx +24 -0
  4. package/Components/BackgroundImage/index.ts +1 -0
  5. package/Components/BaseFocusable/index.ios.ts +12 -2
  6. package/Components/Cell/FocusableWrapper.tsx +3 -0
  7. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  8. package/Components/CellRendererResolver/index.ts +1 -1
  9. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  10. package/Components/Focusable/Focusable.tsx +4 -2
  11. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  12. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  13. package/Components/FocusableGroup/FocusableTvOS.tsx +41 -8
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  15. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  16. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  17. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  18. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  19. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  20. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
  21. package/Components/Layout/TV/index.tsx +5 -7
  22. package/Components/Layout/TV/index.web.tsx +5 -7
  23. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  24. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  32. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  38. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -10
  39. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  40. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  41. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  42. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  43. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  44. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  45. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  46. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  47. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  48. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  49. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  50. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  51. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  52. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  53. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  54. package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  56. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  57. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  58. package/Components/MasterCell/DefaultComponents/index.ts +9 -3
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
  66. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
  67. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  68. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
  69. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  70. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
  71. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  72. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  75. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  76. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  77. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  78. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  79. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  80. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  81. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  82. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  83. package/Components/MasterCell/README.md +4 -0
  84. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  85. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  86. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  87. package/Components/MasterCell/dataAdapter.ts +4 -1
  88. package/Components/MasterCell/elementMapper.tsx +52 -7
  89. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  90. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  91. package/Components/MasterCell/utils/index.ts +85 -15
  92. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  93. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  94. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  95. package/Components/PlayerContainer/PlayerContainer.tsx +15 -14
  96. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +284 -0
  97. package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -19
  98. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  99. package/Components/River/RefreshControl.tsx +19 -88
  100. package/Components/River/River.tsx +9 -82
  101. package/Components/River/TV/River.tsx +31 -14
  102. package/Components/River/TV/index.tsx +8 -4
  103. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  104. package/Components/River/TV/utils/index.ts +4 -0
  105. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  106. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
  107. package/Components/River/__tests__/componentsMap.test.js +38 -0
  108. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  109. package/Components/River/hooks/index.ts +1 -0
  110. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  111. package/Components/Screen/TV/index.web.tsx +4 -2
  112. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  113. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  114. package/Components/Screen/hooks.ts +2 -3
  115. package/Components/Screen/index.tsx +2 -3
  116. package/Components/Screen/orientationHandler.ts +3 -3
  117. package/Components/ScreenResolver/index.tsx +9 -5
  118. package/Components/ScreenRevealManager/Overlay.tsx +34 -0
  119. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  120. package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
  121. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  122. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +9 -20
  123. package/Components/Tabs/TabContent.tsx +7 -4
  124. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  125. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  126. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  127. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  128. package/Components/TopCutoffOverlay/index.tsx +55 -0
  129. package/Components/Transitioner/index.js +3 -3
  130. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  131. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  132. package/Components/VideoModal/utils.ts +18 -10
  133. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  134. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  135. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  136. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -6
  137. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  138. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  139. package/Contexts/ScreenContext/index.tsx +25 -18
  140. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  141. package/Decorators/Analytics/index.tsx +6 -5
  142. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  143. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  144. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  145. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  146. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  147. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  148. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  149. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  150. package/Helpers/DataSourceHelper/index.ts +19 -0
  151. package/Helpers/index.js +7 -1
  152. package/package.json +5 -5
  153. package/Components/Layout/TV/LayoutBackground.tsx +0 -28
  154. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  155. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  156. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  157. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -1,8 +1,10 @@
1
1
  import * as React from "react";
2
- import { Animated, StyleSheet } from "react-native";
2
+ import { Animated } from "react-native";
3
+
3
4
  import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
4
5
  import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
5
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
+ import { Overlay } from "./Overlay";
6
8
 
7
9
  import { ScreenRevealManager } from "./ScreenRevealManager";
8
10
  import {
@@ -10,13 +12,6 @@ import {
10
12
  emitScreenRevealManagerIsNotReadyToShow,
11
13
  } from "./utils";
12
14
 
13
- const styles = StyleSheet.create({
14
- container: {
15
- ...StyleSheet.absoluteFillObject,
16
- position: "absolute",
17
- },
18
- });
19
-
20
15
  export const TIMEOUT = 300; // 300 ms
21
16
 
22
17
  const HIDDEN = 0; // opacity = 0
@@ -77,7 +72,7 @@ export const withScreenRevealManager = (Component) => {
77
72
 
78
73
  if (isFirstComponentScreenPicker(componentsToRender)) {
79
74
  // for screen-picker with have additional internal ComponentsMap, no need to add this wrapper
80
- return <Component {...props} initialNumberToLoad={1} />;
75
+ return <Component {...props} />;
81
76
  }
82
77
 
83
78
  return (
@@ -93,17 +88,11 @@ export const withScreenRevealManager = (Component) => {
93
88
  onLoadFailedFromScreenRevealManager={managerRef.current.onLoadFailed}
94
89
  />
95
90
  {isShowOverlay ? (
96
- <Animated.View
97
- style={[
98
- styles.container,
99
- {
100
- opacity: opacityRef.current,
101
- // TODO: we should support background image as well, but for now we will use background color from theme
102
- backgroundColor:
103
- props.backgroundColor ?? theme.app_background_color,
104
- },
105
- ]}
106
- testID="animated-component"
91
+ <Overlay
92
+ opacity={opacityRef.current}
93
+ backgroundColor={
94
+ props.backgroundColor ?? theme.app_background_color
95
+ }
107
96
  />
108
97
  ) : null}
109
98
  </>
@@ -1,11 +1,10 @@
1
1
  import React from "react";
2
- import { View, ViewStyle } from "react-native";
2
+ import { View, StyleSheet } from "react-native";
3
3
 
4
4
  import { River } from "@applicaster/zapp-react-native-ui-components/Components";
5
5
  import { withNestedNavigationContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
6
6
 
7
7
  type Props = {
8
- styles: Record<string, ViewStyle>;
9
8
  minHeight: number;
10
9
  changingTab: boolean;
11
10
  feedUrl: string;
@@ -14,9 +13,14 @@ type Props = {
14
13
  backgroundColor: string;
15
14
  };
16
15
 
16
+ const styles = StyleSheet.create({
17
+ riverWrapper: {
18
+ flex: 1,
19
+ },
20
+ });
21
+
17
22
  function TabContentComponent(props: Props) {
18
23
  const {
19
- styles,
20
24
  minHeight,
21
25
  backgroundColor,
22
26
  changingTab,
@@ -29,7 +33,6 @@ function TabContentComponent(props: Props) {
29
33
  <View
30
34
  style={[
31
35
  styles.riverWrapper,
32
-
33
36
  {
34
37
  backgroundColor,
35
38
  minHeight,
@@ -0,0 +1,201 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+
5
+ jest.mock(
6
+ "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration",
7
+ () => ({
8
+ useScreenConfiguration: jest.fn(),
9
+ })
10
+ );
11
+
12
+ jest.mock(
13
+ "@applicaster/zapp-react-native-ui-components/Components/TopCutoffOverlay/hooks",
14
+ () => ({
15
+ useMarginTop: jest.fn(),
16
+ })
17
+ );
18
+
19
+ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
20
+ useTheme: jest.fn(),
21
+ }));
22
+
23
+ import { TopCutoffOverlay } from "../index";
24
+ import { useMarginTop } from "../hooks";
25
+ import { useScreenConfiguration } from "../../River/useScreenConfiguration";
26
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
27
+
28
+ // Recursively find a View with position: absolute in the JSON tree
29
+ const findOverlayStyle = (node: any): Record<string, any> | null => {
30
+ if (!node) return null;
31
+
32
+ // Style can be a plain object or an array of style objects
33
+ const styles = Array.isArray(node.props?.style)
34
+ ? node.props.style
35
+ : node.props?.style
36
+ ? [node.props.style]
37
+ : [];
38
+
39
+ const styleObj = styles.find((s: any) => s?.position === "absolute");
40
+ if (styleObj) return styleObj;
41
+
42
+ const children = node.children || [];
43
+
44
+ if (Array.isArray(children)) {
45
+ for (const child of children) {
46
+ const result = findOverlayStyle(child);
47
+ if (result) return result;
48
+ }
49
+ }
50
+
51
+ const propChildren = node.props?.children;
52
+
53
+ if (propChildren && Array.isArray(propChildren)) {
54
+ for (const child of propChildren) {
55
+ const result = findOverlayStyle(child);
56
+ if (result) return result;
57
+ }
58
+ }
59
+
60
+ if (
61
+ typeof propChildren === "object" &&
62
+ propChildren !== null &&
63
+ "type" in propChildren
64
+ ) {
65
+ return findOverlayStyle(propChildren);
66
+ }
67
+
68
+ return null;
69
+ };
70
+
71
+ describe("TopCutoffOverlay", () => {
72
+ const mockUseMarginTop = useMarginTop as jest.MockedFunction<
73
+ typeof useMarginTop
74
+ >;
75
+
76
+ const mockUseScreenConfig = useScreenConfiguration as jest.MockedFunction<
77
+ typeof useScreenConfiguration
78
+ >;
79
+
80
+ const mockUseTheme = useTheme as jest.MockedFunction<typeof useTheme>;
81
+
82
+ beforeEach(() => {
83
+ jest.clearAllMocks();
84
+ mockUseMarginTop.mockReturnValue(60);
85
+ mockUseScreenConfig.mockReturnValue({ backgroundColor: "#1a1a1a" });
86
+ mockUseTheme.mockReturnValue({ app_background_color: "#222222" });
87
+ });
88
+
89
+ it("renders children when applyTopCutoff is false", () => {
90
+ const { getByText } = render(
91
+ <TopCutoffOverlay applyTopCutoff={false}>
92
+ <Text>Child content</Text>
93
+ </TopCutoffOverlay>
94
+ );
95
+
96
+ expect(getByText("Child content")).toBeTruthy();
97
+ });
98
+
99
+ it("does not render overlay when applyTopCutoff is false", () => {
100
+ const tree = render(
101
+ <TopCutoffOverlay applyTopCutoff={false}>
102
+ <Text>Child content</Text>
103
+ </TopCutoffOverlay>
104
+ );
105
+
106
+ expect(findOverlayStyle(tree.toJSON())).toBeNull();
107
+ });
108
+
109
+ it("renders overlay when applyTopCutoff is true (default)", () => {
110
+ const tree = render(
111
+ <TopCutoffOverlay>
112
+ <Text>Child content</Text>
113
+ </TopCutoffOverlay>
114
+ );
115
+
116
+ expect(findOverlayStyle(tree.toJSON())).not.toBeNull();
117
+ });
118
+
119
+ it("renders overlay with correct height from useMarginTop", () => {
120
+ const tree = render(
121
+ <TopCutoffOverlay>
122
+ <Text>Child content</Text>
123
+ </TopCutoffOverlay>
124
+ );
125
+
126
+ expect(findOverlayStyle(tree.toJSON())?.height).toBe(60);
127
+ });
128
+
129
+ it("uses screenBackgroundColor from useScreenConfiguration when available", () => {
130
+ mockUseScreenConfig.mockReturnValue({ backgroundColor: "#ff0000" });
131
+
132
+ const tree = render(
133
+ <TopCutoffOverlay>
134
+ <Text>Child content</Text>
135
+ </TopCutoffOverlay>
136
+ );
137
+
138
+ expect(findOverlayStyle(tree.toJSON())?.backgroundColor).toBe("#ff0000");
139
+ });
140
+
141
+ it("falls back to themeBackgroundColor when screenBackgroundColor is not available", () => {
142
+ mockUseScreenConfig.mockReturnValue({ backgroundColor: undefined });
143
+
144
+ const tree = render(
145
+ <TopCutoffOverlay>
146
+ <Text>Child content</Text>
147
+ </TopCutoffOverlay>
148
+ );
149
+
150
+ expect(findOverlayStyle(tree.toJSON())?.backgroundColor).toBe("#222222");
151
+ });
152
+
153
+ it("falls back to transparent when neither screenBackgroundColor nor themeBackgroundColor is available", () => {
154
+ mockUseScreenConfig.mockReturnValue({ backgroundColor: undefined });
155
+ mockUseTheme.mockReturnValue({});
156
+
157
+ const tree = render(
158
+ <TopCutoffOverlay>
159
+ <Text>Child content</Text>
160
+ </TopCutoffOverlay>
161
+ );
162
+
163
+ expect(findOverlayStyle(tree.toJSON())?.backgroundColor).toBe(
164
+ "transparent"
165
+ );
166
+ });
167
+
168
+ it("passes targetScreenId to useMarginTop and useScreenConfiguration", () => {
169
+ render(
170
+ <TopCutoffOverlay targetScreenId="custom-screen-1">
171
+ <Text>Child content</Text>
172
+ </TopCutoffOverlay>
173
+ );
174
+
175
+ expect(mockUseMarginTop).toHaveBeenCalledWith("custom-screen-1");
176
+ expect(mockUseScreenConfig).toHaveBeenCalledWith("custom-screen-1");
177
+ });
178
+
179
+ it("positions overlay at top with left/right 0", () => {
180
+ const tree = render(
181
+ <TopCutoffOverlay>
182
+ <Text>Child content</Text>
183
+ </TopCutoffOverlay>
184
+ );
185
+
186
+ const style = findOverlayStyle(tree.toJSON());
187
+ expect(style?.top).toBe(0);
188
+ expect(style?.left).toBe(0);
189
+ expect(style?.right).toBe(0);
190
+ });
191
+
192
+ it("renders children inside the container", () => {
193
+ const { getByText } = render(
194
+ <TopCutoffOverlay>
195
+ <Text>Child content</Text>
196
+ </TopCutoffOverlay>
197
+ );
198
+
199
+ expect(getByText("Child content")).toBeTruthy();
200
+ });
201
+ });
@@ -0,0 +1,130 @@
1
+ import { renderHook } from "@testing-library/react-native";
2
+ import { useMarginTop } from "../useMarginTop";
3
+
4
+ // Mocks
5
+ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
6
+ useTheme: jest.fn(),
7
+ }));
8
+
9
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
10
+ useCurrentScreenData: jest.fn(),
11
+ }));
12
+
13
+ jest.mock("@applicaster/zapp-react-native-utils/componentsUtils", () => ({
14
+ isFirstComponentScreenPicker: jest.fn(),
15
+ }));
16
+
17
+ // Imports after mocks
18
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
19
+ import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks";
20
+ import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
21
+
22
+ describe("useMarginTop", () => {
23
+ const mockTheme = {
24
+ screen_margin_top: 10,
25
+ };
26
+
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+
30
+ (useTheme as jest.Mock).mockReturnValue(mockTheme);
31
+ });
32
+
33
+ it("returns 0 when ScreenPicker is first component", () => {
34
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
35
+ ui_components: [],
36
+ });
37
+
38
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(true);
39
+
40
+ const { result } = renderHook(() => useMarginTop("screen1"));
41
+
42
+ expect(result.current).toBe(0);
43
+ });
44
+
45
+ it("returns 0 for player screen", () => {
46
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
47
+ plugin_type: "player",
48
+ });
49
+
50
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(false);
51
+
52
+ const { result } = renderHook(() => useMarginTop("screen1"));
53
+
54
+ expect(result.current).toBe(0);
55
+ });
56
+
57
+ it("returns theme value when margin is empty string", () => {
58
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
59
+ styles: {
60
+ screen_margin_top: "",
61
+ },
62
+ });
63
+
64
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(false);
65
+
66
+ const { result } = renderHook(() => useMarginTop("screen1"));
67
+
68
+ expect(result.current).toBe(10);
69
+ });
70
+
71
+ it("returns theme value when undefined and general content screen", () => {
72
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
73
+ plugin_type: "general_content",
74
+ styles: {
75
+ screen_margin_top: undefined,
76
+ },
77
+ });
78
+
79
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(false);
80
+
81
+ const { result } = renderHook(() => useMarginTop("screen1"));
82
+
83
+ expect(result.current).toBe(10);
84
+ });
85
+
86
+ it("returns theme value when undefined and supports ui_components", () => {
87
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
88
+ ui_components: ["something"],
89
+ styles: {
90
+ screen_margin_top: undefined,
91
+ },
92
+ });
93
+
94
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(false);
95
+
96
+ const { result } = renderHook(() => useMarginTop("screen1"));
97
+
98
+ expect(result.current).toBe(10);
99
+ });
100
+
101
+ it("returns 0 when undefined and not general content and no ui_components", () => {
102
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
103
+ plugin_type: "other",
104
+ ui_components: null,
105
+ styles: {
106
+ screen_margin_top: undefined,
107
+ },
108
+ });
109
+
110
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(false);
111
+
112
+ const { result } = renderHook(() => useMarginTop("screen1"));
113
+
114
+ expect(result.current).toBe(0);
115
+ });
116
+
117
+ it("returns value from screenData when defined", () => {
118
+ (useCurrentScreenData as jest.Mock).mockReturnValue({
119
+ styles: {
120
+ screen_margin_top: 25,
121
+ },
122
+ });
123
+
124
+ (isFirstComponentScreenPicker as jest.Mock).mockReturnValue(false);
125
+
126
+ const { result } = renderHook(() => useMarginTop("screen1"));
127
+
128
+ expect(result.current).toBe(25);
129
+ });
130
+ });
@@ -0,0 +1 @@
1
+ export { useMarginTop } from "./useMarginTop";
@@ -0,0 +1,59 @@
1
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
2
+ import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks";
3
+ import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
4
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
5
+
6
+ /**
7
+ * The MarginTop is essentially a feature used for managing the visibility of components on your screen.
8
+ * A more accurate term for this might be something like a 'component visibility threshold' or 'cut-off point'.
9
+ * In practical terms, MarginTop determines a specific vertical position (given in the 'y' coordinate)
10
+ * on your screen above which components aren't shown.
11
+ * You might visualize this as a horizontal line across your screen, and any component crossing this line becomes invisible.
12
+ *
13
+ * Classic use case for this feature is making sure that component aren't displayed underneath the navigation bar.
14
+ */
15
+
16
+ export const useMarginTop = (targetScreenId: string): number => {
17
+ const theme = useTheme<BaseThemePropertiesTV>();
18
+ const screenData = useCurrentScreenData(targetScreenId);
19
+ const isGeneralContentScreen = screenData?.plugin_type === "general_content";
20
+ const supportsUiComponents = screenData?.ui_components;
21
+
22
+ /**
23
+ * ScreenPicker is a component but should really be a screen.
24
+ * We need to skip margin top for it as it's already applied to the target screen
25
+ **/
26
+
27
+ // ignore margin on screenPicker
28
+ if (isFirstComponentScreenPicker(screenData?.ui_components)) {
29
+ return 0;
30
+ }
31
+
32
+ const isPlayer = screenData?.plugin_type === "player";
33
+
34
+ // ignore margin on inlinePlayer (remove if better way of identifying cases for plugins that don't have marginTop)
35
+ if (isPlayer) {
36
+ return 0;
37
+ }
38
+
39
+ // Empty string means that value is blank in the CMS. Fallback to theme
40
+ if (String(screenData?.styles?.screen_margin_top) === "") {
41
+ return toNumberWithDefaultZero(theme.screen_margin_top);
42
+ }
43
+
44
+ /**
45
+ * If value is undefined it means one of three things
46
+ * 1. Screen is not a general content screen and it doesn't handle ui components (return 0)
47
+ * 2. Screen is a general content screen but it doesn't have a margin top value (return theme value)
48
+ * 3. Screen isn't general content screen but it handles the ui components (return theme value)
49
+ */
50
+ if (screenData?.styles?.screen_margin_top === undefined) {
51
+ if (isGeneralContentScreen || supportsUiComponents) {
52
+ return toNumberWithDefaultZero(theme.screen_margin_top);
53
+ }
54
+
55
+ return 0;
56
+ }
57
+
58
+ return toNumberWithDefaultZero(screenData?.styles?.screen_margin_top);
59
+ };
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
4
+ import { useScreenConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration";
5
+
6
+ import { useMarginTop } from "./hooks";
7
+
8
+ interface IProps {
9
+ targetScreenId?: string;
10
+ children?: React.ReactNode;
11
+ applyTopCutoff?: boolean;
12
+ }
13
+
14
+ const styles = StyleSheet.create({
15
+ container: {
16
+ flex: 1,
17
+ },
18
+ });
19
+
20
+ export const TopCutoffOverlay: React.FC<IProps> = ({
21
+ targetScreenId,
22
+ children,
23
+ applyTopCutoff = true,
24
+ }: IProps) => {
25
+ const cutoffHeight = useMarginTop(targetScreenId);
26
+
27
+ const { backgroundColor: screenBackgroundColor } =
28
+ useScreenConfiguration(targetScreenId);
29
+
30
+ const theme = useTheme();
31
+ const themeBackgroundColor = theme?.app_background_color;
32
+
33
+ if (!applyTopCutoff) {
34
+ return children;
35
+ }
36
+
37
+ return (
38
+ <View style={styles.container}>
39
+ {children}
40
+
41
+ <View
42
+ style={{
43
+ position: "absolute",
44
+ top: 0,
45
+ left: 0,
46
+ right: 0,
47
+ height: cutoffHeight,
48
+
49
+ backgroundColor:
50
+ screenBackgroundColor || themeBackgroundColor || "transparent",
51
+ }}
52
+ />
53
+ </View>
54
+ );
55
+ };
@@ -1,7 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
3
3
  import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
+ import { useAppData } from "@applicaster/zapp-react-native-redux";
5
+
5
6
  import { TransitionerComponent } from "./Transitioner";
6
7
 
7
8
  export const Transitioner = (props) => {
@@ -16,8 +17,7 @@ export const Transitioner = (props) => {
16
17
  deviceInfo: true,
17
18
  });
18
19
 
19
- const { appData } = usePickFromState(["appData"]);
20
- const isTabletPortrait = appData?.isTabletPortrait;
20
+ const { isTabletPortrait } = useAppData();
21
21
 
22
22
  return (
23
23
  <TransitionerComponent
@@ -4,7 +4,10 @@ import { Animated, Dimensions } from "react-native";
4
4
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
5
5
 
6
6
  import { useConfiguration } from "../utils";
7
- import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
7
+ import {
8
+ useAppData,
9
+ usePlugins,
10
+ } from "@applicaster/zapp-react-native-redux/hooks";
8
11
  import { isBottomTabVisible } from "../../Screen/navigationHandler";
9
12
 
10
13
  import {
@@ -18,7 +21,6 @@ import {
18
21
  import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
19
22
  import { PROGRESS_BAR_HEIGHT } from "./utils";
20
23
  import { useIsTablet as getIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
21
- import { useAppSelector } from "@applicaster/zapp-react-native-redux";
22
24
 
23
25
  export type ModalAnimationContextT = {
24
26
  yTranslate: React.MutableRefObject<Animated.AnimatedInterpolation<number>>;
@@ -64,9 +66,7 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
64
66
  screenData,
65
67
  } = useNavigation();
66
68
 
67
- const isTabletPortrait = useAppSelector(
68
- (state) => state.appData.isTabletPortrait
69
- );
69
+ const { isTabletPortrait } = useAppData();
70
70
 
71
71
  const plugins = usePlugins();
72
72
 
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-hooks";
1
+ import { act, renderHook, waitFor } from "@testing-library/react-native";
2
2
  import { useDelayedPlayerDetails } from "../useDelayedPlayerDetails";
3
3
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
4
4
 
@@ -13,16 +13,20 @@ describe("useDelayedPlayerDetails", () => {
13
13
  jest.clearAllMocks();
14
14
  });
15
15
 
16
- it("should return false initially, that changes after 1 second", () => {
16
+ it("should return false initially, that changes after 1 second", async () => {
17
17
  const { result } = renderHook(() =>
18
18
  useDelayedPlayerDetails({ isInline: true, isDocked: false, isPip: false })
19
19
  );
20
20
 
21
21
  expect(result.current).toBe(false);
22
22
 
23
- jest.advanceTimersByTime(1000);
23
+ act(() => {
24
+ jest.advanceTimersByTime(1000);
25
+ });
24
26
 
25
- expect(result.current).toBe(true);
27
+ await waitFor(() => {
28
+ expect(result.current).toBe(true);
29
+ });
26
30
  });
27
31
 
28
32
  it("should return false when isPip is true", () => {
@@ -41,7 +45,7 @@ describe("useDelayedPlayerDetails", () => {
41
45
  expect(result.current).toBe(false);
42
46
  });
43
47
 
44
- it("should return true for tablet regardless of other flags", () => {
48
+ it("should return true for tablet regardless of other flags", async () => {
45
49
  (useIsTablet as jest.Mock).mockReturnValue(true);
46
50
 
47
51
  const { result } = renderHook(() =>
@@ -52,8 +56,12 @@ describe("useDelayedPlayerDetails", () => {
52
56
  })
53
57
  );
54
58
 
55
- jest.advanceTimersByTime(1000);
59
+ act(() => {
60
+ jest.advanceTimersByTime(1000);
61
+ });
56
62
 
57
- expect(result.current).toBe(true);
63
+ await waitFor(() => {
64
+ expect(result.current).toBe(true);
65
+ });
58
66
  });
59
67
  });