@applicaster/zapp-react-native-ui-components 15.1.0-rc.1 → 16.0.0-rc.1

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 (144) hide show
  1. package/Components/BaseFocusable/index.ios.ts +12 -2
  2. package/Components/Cell/FocusableWrapper.tsx +3 -0
  3. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  4. package/Components/Focusable/Focusable.tsx +4 -2
  5. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  6. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  8. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  9. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  10. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  11. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  12. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  13. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  14. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  15. package/Components/Layout/TV/index.tsx +3 -4
  16. package/Components/Layout/TV/index.web.tsx +3 -4
  17. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  18. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  19. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  20. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  21. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  22. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  23. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  24. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  31. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -10
  32. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  33. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  34. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  35. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  36. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  37. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  38. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  39. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  40. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  41. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  42. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  43. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  44. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  45. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  46. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  47. package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
  48. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  49. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  50. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  51. package/Components/MasterCell/DefaultComponents/index.ts +9 -3
  52. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  53. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  54. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  55. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
  56. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  57. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  66. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  67. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  68. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  69. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  70. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  71. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  72. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  75. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  76. package/Components/MasterCell/README.md +4 -0
  77. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  78. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  79. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  80. package/Components/MasterCell/dataAdapter.ts +4 -1
  81. package/Components/MasterCell/elementMapper.tsx +52 -7
  82. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  83. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  84. package/Components/MasterCell/utils/index.ts +85 -15
  85. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  86. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  87. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  88. package/Components/PlayerContainer/PlayerContainer.tsx +14 -13
  89. package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -19
  90. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  91. package/Components/River/RefreshControl.tsx +19 -88
  92. package/Components/River/River.tsx +9 -82
  93. package/Components/River/TV/River.tsx +31 -14
  94. package/Components/River/TV/index.tsx +8 -4
  95. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  96. package/Components/River/TV/utils/index.ts +4 -0
  97. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  98. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
  99. package/Components/River/__tests__/componentsMap.test.js +38 -0
  100. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  101. package/Components/River/hooks/index.ts +1 -0
  102. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  103. package/Components/Screen/TV/index.web.tsx +4 -2
  104. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  105. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  106. package/Components/Screen/hooks.ts +2 -3
  107. package/Components/Screen/index.tsx +2 -3
  108. package/Components/Screen/orientationHandler.ts +3 -3
  109. package/Components/ScreenResolver/index.tsx +9 -5
  110. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  111. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  112. package/Components/Tabs/TabContent.tsx +7 -4
  113. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  114. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  115. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  116. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  117. package/Components/TopCutoffOverlay/index.tsx +55 -0
  118. package/Components/Transitioner/index.js +3 -3
  119. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  120. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  121. package/Components/VideoModal/utils.ts +12 -9
  122. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  123. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  124. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  125. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -6
  126. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  127. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  128. package/Contexts/ScreenContext/index.tsx +25 -18
  129. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  130. package/Decorators/Analytics/index.tsx +6 -5
  131. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  132. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  133. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  134. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  135. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  136. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  137. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  138. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  139. package/Helpers/DataSourceHelper/index.ts +19 -0
  140. package/package.json +5 -5
  141. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  142. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  143. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  144. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -1,19 +1,21 @@
1
1
  import * as React from "react";
2
- import { path } from "ramda";
3
2
 
4
3
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
5
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
6
5
  import { extractAsset } from "./utils";
7
6
 
8
7
  type Return = { uri: string } | null;
8
+ type Source = { context?: string; uri?: string } | null | undefined;
9
9
 
10
- const getSourceContext = path(["source", "context"]);
11
- const getSourceUri = path(["source", "uri"]);
12
- const getState = path(["state"]);
10
+ const getSourceContext = (source: Source) => source?.context;
11
+ const getSourceUri = (source: Source) => source?.uri;
13
12
 
14
- export const useImageSource = ({ uri, entry, otherProps }): Return => {
15
- const uriContext = getSourceContext(otherProps);
16
- const uriState = getState(otherProps);
13
+ export const useImageSource = ({
14
+ uri,
15
+ entry,
16
+ otherProps: { source, state: uriState },
17
+ }): Return => {
18
+ const uriContext = getSourceContext(source);
17
19
 
18
20
  const action = useActions(uriContext);
19
21
 
@@ -38,7 +40,7 @@ export const useImageSource = ({ uri, entry, otherProps }): Return => {
38
40
  return { uri };
39
41
  }
40
42
 
41
- const uriFromSource = getSourceUri(otherProps);
43
+ const uriFromSource = getSourceUri(source);
42
44
 
43
45
  if (uriFromSource) {
44
46
  return { uri: uriFromSource };
@@ -47,7 +49,7 @@ export const useImageSource = ({ uri, entry, otherProps }): Return => {
47
49
  return null;
48
50
  };
49
51
 
50
- const getSource = (uri, showDefault, placeholderImage, otherProps) => {
52
+ const getSource = (uri, showDefault, placeholderImage, source) => {
51
53
  const placeholderName = placeholderImage || "";
52
54
 
53
55
  const defaultPath = {
@@ -60,7 +62,7 @@ const getSource = (uri, showDefault, placeholderImage, otherProps) => {
60
62
  return { uri };
61
63
  }
62
64
 
63
- const uriFromSource = getSourceUri(otherProps);
65
+ const uriFromSource = getSourceUri(source);
64
66
 
65
67
  if (uriFromSource) {
66
68
  return { uri: uriFromSource };
@@ -74,10 +76,9 @@ export const useImageSourceWithDefault = ({
74
76
  entry,
75
77
  showDefault,
76
78
  placeholderImage,
77
- otherProps,
79
+ otherProps: { state: uriState, source },
78
80
  }): Return => {
79
- const uriContext = getSourceContext(otherProps);
80
- const uriState = getState(otherProps);
81
+ const uriContext = getSourceContext(source);
81
82
 
82
83
  const action = useActions(uriContext);
83
84
 
@@ -98,5 +99,5 @@ export const useImageSourceWithDefault = ({
98
99
  return extractAsset(!isTV(), entryStateLocal.asset, uriState);
99
100
  }
100
101
 
101
- return getSource(uri, showDefault, placeholderImage, otherProps);
102
+ return getSource(uri, showDefault, placeholderImage, source);
102
103
  };
@@ -1,6 +1,5 @@
1
1
  import * as React from "react";
2
- import merge from "lodash/merge";
3
- import { clone } from "@applicaster/zapp-react-native-utils/utils";
2
+ import { merge, clone } from "@applicaster/zapp-react-native-utils/utils";
4
3
  import { Platform, View, ViewStyle } from "react-native";
5
4
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
6
5
  import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import { TouchableOpacity } from "react-native";
3
+
4
+ type Props = {
5
+ children?: React.ReactNode;
6
+ style?: Record<string, unknown>;
7
+ testID?: string;
8
+ accessibilityLabel?: string;
9
+ accessibilityHint?: string;
10
+ onPress?: () => void;
11
+ };
12
+
13
+ export function PressableView({
14
+ children,
15
+ style = {},
16
+ testID,
17
+ accessibilityLabel,
18
+ accessibilityHint,
19
+ onPress,
20
+ }: Props) {
21
+ return (
22
+ <TouchableOpacity
23
+ activeOpacity={1}
24
+ onPress={onPress}
25
+ testID={testID}
26
+ accessibilityLabel={accessibilityLabel}
27
+ accessibilityHint={accessibilityHint}
28
+ accessible={!!(testID || accessibilityLabel)}
29
+ style={style}
30
+ >
31
+ {children}
32
+ </TouchableOpacity>
33
+ );
34
+ }
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-hooks";
1
+ import { renderHook, waitFor } from "@testing-library/react-native";
2
2
  import { Image } from "react-native";
3
3
 
4
4
  import { useGetImageDimensions } from "../useGetImageDimensions";
@@ -13,15 +13,16 @@ jest.spyOn(Image, "getSize").mockImplementation((_uri, success) => {
13
13
 
14
14
  describe("useGetImageDimensions", () => {
15
15
  it("should return aspect ration initially when known dimensions", async () => {
16
- const { result, waitForNextUpdate } = renderHook(() =>
16
+ const { result } = renderHook(() =>
17
17
  useGetImageDimensions("https://some_url.com", WIDTH, undefined)
18
18
  );
19
19
 
20
20
  expect(result.current).toBeUndefined();
21
- await waitForNextUpdate();
22
21
 
23
- expect(result.current).toEqual(
24
- getDimension({ width: WIDTH, height: HEIGTH })
25
- );
22
+ await waitFor(() => {
23
+ expect(result.current).toEqual(
24
+ getDimension({ width: WIDTH, height: HEIGTH })
25
+ );
26
+ });
26
27
  });
27
28
  });
@@ -45,6 +45,7 @@ export const useTextLabel = ({ label, entry }): string => {
45
45
  const [entryStateLocal, setEntryStateLocal] =
46
46
  React.useState(initialEntryState);
47
47
 
48
+ // For favourites
48
49
  React.useEffect(() => {
49
50
  return action?.addListeners?.(({ entryState, entry: actionEntry }) => {
50
51
  if (entry.id === actionEntry.id) {
@@ -53,6 +54,16 @@ export const useTextLabel = ({ label, entry }): string => {
53
54
  });
54
55
  }, []);
55
56
 
57
+ // For rest actions
58
+ React.useEffect(() => {
59
+ // Update entryStateLocal when action state changes. Example: state change when pressing the download button.
60
+ if (typeof action?.addListener === "function") {
61
+ return action.addListener(String(entry?.id), (nextState) => {
62
+ setEntryStateLocal(nextState);
63
+ });
64
+ }
65
+ }, []);
66
+
56
67
  if (context && name && action) {
57
68
  return prepareHebrewText(extractLabel(entryStateLocal.label, name), isRTL);
58
69
  }
@@ -0,0 +1,141 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+
4
+ import { defaultComponents } from "../index";
5
+ import { elementMapper } from "../../elementMapper";
6
+
7
+ const WrapperProbe = jest.fn(({ children }) => <>{children}</>);
8
+ const LeafProbe = jest.fn(() => null);
9
+
10
+ const components = {
11
+ ...defaultComponents,
12
+ View: WrapperProbe,
13
+ Text: LeafProbe,
14
+ };
15
+
16
+ const entry = { id: "entry-1" };
17
+ const item = { id: "item-1" };
18
+
19
+ const renderNode = (node) =>
20
+ render(
21
+ <React.Fragment>{elementMapper(components)(node as never)}</React.Fragment>
22
+ );
23
+
24
+ describe("DataProvider", () => {
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ });
28
+
29
+ it("injects resolved runtime props under dataProviderProps into the direct child and its direct children only", () => {
30
+ renderNode({
31
+ type: "DataProvider",
32
+ props: { _dataKey: "entry", entry },
33
+ elements: [
34
+ {
35
+ type: "View",
36
+ props: {},
37
+ elements: [
38
+ {
39
+ type: "View",
40
+ props: {},
41
+ elements: [{ type: "Text", props: {} }],
42
+ },
43
+ ],
44
+ },
45
+ ],
46
+ });
47
+
48
+ expect(WrapperProbe).toHaveBeenNthCalledWith(
49
+ 1,
50
+ expect.objectContaining({
51
+ dataProviderProps: expect.objectContaining({ entry }),
52
+ }),
53
+ expect.anything()
54
+ );
55
+
56
+ expect(WrapperProbe).toHaveBeenNthCalledWith(
57
+ 2,
58
+ expect.objectContaining({
59
+ dataProviderProps: expect.objectContaining({ entry }),
60
+ }),
61
+ expect.anything()
62
+ );
63
+
64
+ expect(LeafProbe).toHaveBeenCalledWith(
65
+ expect.not.objectContaining({
66
+ dataProviderProps: expect.anything(),
67
+ }),
68
+ expect.anything()
69
+ );
70
+ });
71
+
72
+ it("derives the namespaced values from the resolved runtime props", () => {
73
+ renderNode({
74
+ type: "DataProvider",
75
+ props: { _dataKey: "item", item },
76
+ elements: [
77
+ {
78
+ type: "View",
79
+ props: {},
80
+ elements: [{ type: "View", props: {} }],
81
+ },
82
+ ],
83
+ });
84
+
85
+ expect(WrapperProbe).toHaveBeenNthCalledWith(
86
+ 1,
87
+ expect.objectContaining({
88
+ dataProviderProps: expect.objectContaining({ item }),
89
+ }),
90
+ expect.anything()
91
+ );
92
+
93
+ expect(WrapperProbe).toHaveBeenNthCalledWith(
94
+ 2,
95
+ expect.objectContaining({
96
+ dataProviderProps: expect.objectContaining({ item }),
97
+ }),
98
+ expect.anything()
99
+ );
100
+ });
101
+
102
+ it("does not overwrite explicit dataProviderProps on the direct child", () => {
103
+ const explicitEntry = { id: "explicit" };
104
+
105
+ renderNode({
106
+ type: "DataProvider",
107
+ props: { _dataKey: "entry", entry },
108
+ elements: [
109
+ {
110
+ type: "View",
111
+ props: {
112
+ dataProviderProps: {
113
+ entry: explicitEntry,
114
+ source: "child",
115
+ },
116
+ },
117
+ elements: [{ type: "View", props: {} }],
118
+ },
119
+ ],
120
+ });
121
+
122
+ expect(WrapperProbe).toHaveBeenNthCalledWith(
123
+ 1,
124
+ expect.objectContaining({
125
+ dataProviderProps: expect.objectContaining({
126
+ entry: explicitEntry,
127
+ source: "child",
128
+ }),
129
+ }),
130
+ expect.anything()
131
+ );
132
+
133
+ expect(WrapperProbe).toHaveBeenNthCalledWith(
134
+ 2,
135
+ expect.objectContaining({
136
+ dataProviderProps: expect.objectContaining({ entry }),
137
+ }),
138
+ expect.anything()
139
+ );
140
+ });
141
+ });
@@ -6,26 +6,29 @@ import PureImage from "./Image";
6
6
  import { CollapsibleTextContainer } from "../SharedUI/CollapsibleTextContainer/CollapsibleTextContainer";
7
7
  import { Toggle } from "./Toggle";
8
8
  import { Button } from "./Button";
9
- import { FocusableView } from "./FocusableView";
10
9
  import ProgressBar from "../SharedUI/ProgressBar/ProgressBar";
11
10
  import { LiveImage } from "./LiveImage";
12
11
  import { DynamicBadge } from "./DynamicBadge";
13
12
  import { SecondaryImage } from "./SecondaryImage/Image";
14
13
  import { BorderContainerView } from "./BorderContainerView";
15
14
  import { CellBadge } from "./CellBadge";
15
+ import { DataProvider } from "./DataProvider";
16
16
  import { TvActionButtons } from "./tv/TvActionButtons";
17
- import { ButtonContainerView } from "./tv/ButtonContainerView";
17
+ import { TvActionButton } from "./tv/TvActionButtons/TvActionButton";
18
+ import { ButtonContainerView } from "./ButtonContainerView";
18
19
  import { ImageBorderContainer } from "./ImageBorderContainer";
20
+ import { PressableView } from "./PressableView";
21
+ import { ActionButton as MobileActionButton } from "./mobile/MobileActionButtons/ActionButton";
19
22
 
20
23
  export const defaultComponents = {
21
24
  View,
22
25
  CollapsibleTextContainer,
23
26
  Text,
24
27
  Button,
28
+ PressableView,
25
29
  Image,
26
30
  PureImage,
27
31
  ButtonContainerView,
28
- FocusableView,
29
32
  ProgressBar,
30
33
  Toggle,
31
34
  LiveImage,
@@ -34,5 +37,8 @@ export const defaultComponents = {
34
37
  DynamicBadge,
35
38
  SecondaryImage,
36
39
  TvActionButtons,
40
+ TvActionButton,
41
+ MobileActionButton,
37
42
  CellBadge,
43
+ DataProvider,
38
44
  };
@@ -0,0 +1,135 @@
1
+ import React from "react";
2
+ import { PressableView } from "../../PressableView";
3
+
4
+ import { ActionButtonController } from "../../ActionButtonsCore/components";
5
+
6
+ import { masterCellLogger } from "../../../logger";
7
+ import { resolveLabelText, selectByAssetFlavour } from "./utils";
8
+
9
+ type ChildElementProps = {
10
+ children?: React.ReactNode;
11
+ mobileActionRole?: "asset" | "label" | "label_container";
12
+ uri?: string;
13
+ asset?: React.ReactNode;
14
+ state?: "default" | "focused";
15
+ entry?: any;
16
+ };
17
+
18
+ const isValidElement = (
19
+ child: React.ReactNode
20
+ ): child is React.ReactElement<ChildElementProps> =>
21
+ React.isValidElement(child);
22
+
23
+ export const ActionButton = ({ style, children, action, entry, ...props }) => {
24
+ return (
25
+ <ActionButtonController
26
+ action={action}
27
+ entry={entry}
28
+ onMissingActionContext={(identifier) => {
29
+ masterCellLogger.warning(
30
+ `You're missing an action plugin(${identifier || action?.identifier}) required by your mobile action button.`
31
+ );
32
+ }}
33
+ >
34
+ {({ actionContext, actionState, isActive, onPress }) => {
35
+ const supportsEntryState =
36
+ typeof actionContext?.initialEntryState === "function";
37
+
38
+ const shouldRenderAsset = Boolean(
39
+ supportsEntryState ? actionState?.mobileButtonAssets : false
40
+ );
41
+
42
+ const shouldRenderLabel = Boolean(
43
+ supportsEntryState && resolveLabelText(actionState?.label)
44
+ );
45
+
46
+ const _cloneChildrenWithState = (
47
+ nodes?: React.ReactNode
48
+ ): React.ReactNode => {
49
+ return React.Children.map(nodes, (child) => {
50
+ if (!isValidElement(child)) {
51
+ return child;
52
+ }
53
+
54
+ const role = child.props.mobileActionRole;
55
+
56
+ const nextChildren = _cloneChildrenWithState(child.props.children);
57
+
58
+ // Prevents of the asset rendering when the action doesn't provide asset for current entry state
59
+ if (role === "asset" && !shouldRenderAsset) {
60
+ return null;
61
+ }
62
+
63
+ // Prevents of the label rendering when the action doesn't provide label for current entry state.
64
+ // Also prevents of the label container when it doesn't have children to avoid unnecessary empty space.
65
+ if (role === "label" && !shouldRenderLabel) {
66
+ return null;
67
+ }
68
+
69
+ // Prevents of the label container rendering when it doesn't have children to avoid unnecessary empty space.
70
+ if (
71
+ role === "label_container" &&
72
+ React.Children.count(nextChildren) === 0
73
+ ) {
74
+ return null;
75
+ }
76
+
77
+ const nextProps: Partial<ChildElementProps> = {};
78
+
79
+ // Inject asset or uri to Asset component with role asset
80
+ if (role === "asset") {
81
+ const resolvedAsset = selectByAssetFlavour(
82
+ actionState,
83
+ action.flavour,
84
+ isActive
85
+ );
86
+
87
+ if (typeof resolvedAsset === "function") {
88
+ const AssetComponent =
89
+ resolvedAsset as CellActionAssetComponent;
90
+
91
+ nextProps.asset = (
92
+ <AssetComponent
93
+ flavour={action.flavour || "flavour_1"}
94
+ width={action.width}
95
+ height={action.height}
96
+ />
97
+ );
98
+ } else {
99
+ nextProps.uri = resolvedAsset;
100
+ }
101
+ }
102
+
103
+ // Inject state and entry to Text component with role label
104
+ if (role === "label") {
105
+ nextProps.state = isActive ? "focused" : "default";
106
+ nextProps.entry = entry;
107
+ }
108
+
109
+ if (nextChildren !== child.props.children) {
110
+ nextProps.children = nextChildren;
111
+ }
112
+
113
+ return React.cloneElement(child, nextProps);
114
+ });
115
+ };
116
+
117
+ if (!shouldRenderAsset && !shouldRenderLabel) {
118
+ return null;
119
+ }
120
+
121
+ return (
122
+ <PressableView
123
+ testID={props.testID || `${entry?.id}`}
124
+ style={isActive ? { ...style, ...props.focusedStyles } : style}
125
+ onPress={onPress}
126
+ accessibilityLabel={props.accessibilityLabel || `${entry?.id}`}
127
+ accessibilityHint={props.accessibilityHint}
128
+ >
129
+ {_cloneChildrenWithState(children)}
130
+ </PressableView>
131
+ );
132
+ }}
133
+ </ActionButtonController>
134
+ );
135
+ };
@@ -0,0 +1,33 @@
1
+ import { AssetComponent } from "./AssetComponent";
2
+
3
+ type Props = {
4
+ testID?: string;
5
+ style?: Record<string, unknown>;
6
+ };
7
+
8
+ type AssetNode = {
9
+ type: "ReactComponent";
10
+ style?: Record<string, unknown>;
11
+ additionalProps: {
12
+ component: typeof AssetComponent;
13
+ requiresCellUUID: true;
14
+ renderChildren: false;
15
+ mobileActionRole: "asset";
16
+ testID?: string;
17
+ };
18
+ };
19
+
20
+ /** Asset used in conjunction with ActionButton inside mobile action buttons, uri is provided by ActionButton */
21
+ export const Asset = ({ testID, style }: Props): AssetNode => {
22
+ return {
23
+ type: "ReactComponent",
24
+ style,
25
+ additionalProps: {
26
+ component: AssetComponent,
27
+ requiresCellUUID: true,
28
+ renderChildren: false,
29
+ mobileActionRole: "asset",
30
+ testID: testID ? `${testID}-asset` : undefined,
31
+ },
32
+ };
33
+ };
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { ImageContainer } from "../../ImageContainer";
4
+
5
+ type Props = {
6
+ style?: Record<string, unknown>;
7
+ uri?: string;
8
+ asset?: React.ReactNode;
9
+ testID?: string;
10
+ cellUUID?: string;
11
+ };
12
+
13
+ export const AssetComponent = (props: Props & Record<string, unknown>) => {
14
+ return props.asset && React.isValidElement(props.asset) ? (
15
+ <View style={props.style} testID={props.testID}>
16
+ {React.cloneElement(props.asset, { cellUUID: props.cellUUID })}
17
+ </View>
18
+ ) : (
19
+ // @ts-ignore
20
+ <ImageContainer {...props} uri={props.uri} />
21
+ );
22
+ };
@@ -0,0 +1,125 @@
1
+ import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
2
+ import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
3
+
4
+ import { Asset } from "./Asset";
5
+ import { TextLabelsContainer } from "./TextLabelsContainer";
6
+ import {
7
+ getAssetStyles,
8
+ getContentDirection,
9
+ getContentsAlignment,
10
+ getPressableStyles,
11
+ getTextLabelStyles,
12
+ } from "./utils";
13
+
14
+ const displayModeStyle = (value) => {
15
+ const mode = value("display_mode") || "dynamic";
16
+
17
+ if (mode === "fixed") {
18
+ return {
19
+ width: toNumberWithDefault(140, value("width")),
20
+ };
21
+ }
22
+
23
+ if (mode === "fill") {
24
+ return {
25
+ flex: 1,
26
+ };
27
+ }
28
+
29
+ return {};
30
+ };
31
+
32
+ type Props = {
33
+ index: number;
34
+ value: Function;
35
+ stylePrefix: string;
36
+ specificPrefix: string;
37
+ spacingStyle: Record<string, unknown>;
38
+ };
39
+
40
+ export const Button = ({
41
+ index,
42
+ value,
43
+ stylePrefix,
44
+ specificPrefix,
45
+ spacingStyle,
46
+ }: Props) => {
47
+ // Retrives values depending to the slot
48
+ const getSlotBasedValue = (property: string) =>
49
+ value(`${specificPrefix}_${property}`);
50
+
51
+ /**
52
+ * Retrieves values depending to the independ_style toggle:
53
+ * (e.g for button value button_2_label_enabled returns button_1_label_enabled when independent_style: false)
54
+ */
55
+ const getValue = (property: string) => value(`${stylePrefix}_${property}`);
56
+
57
+ const assetEnabled = getValue("asset_enabled");
58
+ const labelEnabled = getValue("label_enabled");
59
+
60
+ if (!getSlotBasedValue("button_enabled")) {
61
+ return null;
62
+ }
63
+
64
+ if (!assetEnabled && !labelEnabled) {
65
+ return null;
66
+ }
67
+
68
+ const testID = `mobile_action_button_${index + 1}`;
69
+ const actionIdentifier = getSlotBasedValue("assign_action");
70
+ const assetAlignment = getValue("asset_alignment") || "left";
71
+ const actionAssetFlavour = getValue("action_asset_flavour");
72
+ const contentsAlignment = getValue("contents_alignment") || "center";
73
+
74
+ return {
75
+ type: "MobileActionButton",
76
+ style: {
77
+ flexDirection: getContentDirection(assetAlignment),
78
+ alignContent: "center",
79
+ alignItems: getContentsAlignment(contentsAlignment, assetAlignment),
80
+ justifyContent: getContentsAlignment(contentsAlignment, assetAlignment),
81
+ ...getPressableStyles(getValue),
82
+ ...displayModeStyle(getValue),
83
+ ...spacingStyle,
84
+ },
85
+ additionalProps: {
86
+ action: {
87
+ identifier: actionIdentifier,
88
+ flavour: actionAssetFlavour,
89
+ width: toNumberWithDefault(24, getValue("asset_width")),
90
+ height: toNumberWithDefault(24, getValue("asset_height")),
91
+ },
92
+ focusedStyles: {
93
+ backgroundColor: getValue("focused_background_color"),
94
+ borderColor: getValue("focused_border_color"),
95
+ },
96
+ testID,
97
+ },
98
+ elements: compact([
99
+ assetEnabled
100
+ ? Asset({
101
+ style: getAssetStyles(getValue),
102
+ testID,
103
+ })
104
+ : null,
105
+
106
+ labelEnabled
107
+ ? TextLabelsContainer({
108
+ style: getTextLabelStyles(getValue),
109
+ extraProps: {
110
+ normalStyles: {
111
+ color: getValue("font_color"),
112
+ },
113
+ focusedStyles: {
114
+ color: getValue("focused_font_color"),
115
+ },
116
+ transformText: getValue("text_transform") || "default",
117
+ numberOfLines: getValue("number_of_lines"),
118
+ },
119
+ actionIdentifier,
120
+ testID,
121
+ })
122
+ : null,
123
+ ]),
124
+ };
125
+ };
@@ -0,0 +1,16 @@
1
+ type Props = {
2
+ enabled?: boolean;
3
+ };
4
+
5
+ export const Spacer = ({ enabled = false }: Props = {}) => {
6
+ if (!enabled) {
7
+ return null;
8
+ }
9
+
10
+ return {
11
+ type: "View",
12
+ style: {
13
+ flex: 1,
14
+ },
15
+ };
16
+ };