@applicaster/zapp-react-native-ui-components 15.0.0-rc.129 → 15.0.0-rc.131

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 (30) hide show
  1. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  2. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  3. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  4. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  5. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  6. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  7. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  8. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  9. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  10. package/Components/MasterCell/DefaultComponents/PressableView.tsx +196 -0
  11. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  12. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +46 -0
  13. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +126 -0
  14. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -0
  15. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  16. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  17. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +32 -0
  18. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +191 -0
  19. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
  20. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
  21. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  22. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
  23. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  24. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
  25. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
  26. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -52
  27. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +35 -171
  28. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +36 -145
  29. package/Components/MasterCell/elementMapper.tsx +1 -0
  30. package/package.json +5 -5
@@ -0,0 +1,104 @@
1
+ import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
2
+ import {
3
+ insertButtonsBetweenLabels,
4
+ insertButtonsBetweenLabelContainers,
5
+ mobileOverImagePositionStyles,
6
+ } from "./utils";
7
+ import { Button } from "./Button";
8
+ import { ButtonContainerView } from "./ButtonContainerView";
9
+ import { buildActionButtonsModel } from "../../ActionButtonsCore/model";
10
+
11
+ const CONTAINER_PREFIX = "mobile_buttons_container";
12
+ const BUTTON_PREFIX = "mobile_button";
13
+
14
+ type MobileActionButtonsProps = {
15
+ value: (key: string) => unknown;
16
+ configuration: Record<string, unknown>;
17
+ placement: "labels" | "over_image"; // Indicates where the component was placed, to apply specific display rules
18
+ };
19
+
20
+ export const MobileActionButtons = ({
21
+ value,
22
+ configuration,
23
+ placement,
24
+ }: MobileActionButtonsProps) => {
25
+ const position = value(`${CONTAINER_PREFIX}_position`);
26
+
27
+ if (placement === "labels" && position === "over_image") {
28
+ return null;
29
+ }
30
+
31
+ if (placement === "over_image" && position !== "over_image") {
32
+ return null;
33
+ }
34
+
35
+ const model = buildActionButtonsModel({
36
+ configuration,
37
+ value,
38
+ containerPrefix: CONTAINER_PREFIX,
39
+ buttonPrefix: BUTTON_PREFIX,
40
+ });
41
+
42
+ if (!model) {
43
+ return null;
44
+ }
45
+
46
+ const elements = compact(
47
+ model.buttons.map(({ renderIndex, specificPrefix, stylePrefix }) => {
48
+ const isNotLast = renderIndex < model.buttons.length - 1;
49
+
50
+ const {
51
+ container: { stacking, verticalGutter, horizontalGutter },
52
+ } = model;
53
+
54
+ const isVertical = stacking === "vertical";
55
+ const gutter = isVertical ? verticalGutter : horizontalGutter;
56
+
57
+ const spacingStyle = {
58
+ [isVertical ? "marginBottom" : "marginRight"]: isNotLast ? gutter : 0,
59
+ };
60
+
61
+ return Button({
62
+ index: renderIndex,
63
+ value,
64
+ stylePrefix,
65
+ specificPrefix,
66
+ spacingStyle,
67
+ });
68
+ })
69
+ );
70
+
71
+ return ButtonContainerView({
72
+ style: {
73
+ alignItems: "center",
74
+ ...(placement === "over_image"
75
+ ? {
76
+ position: "absolute",
77
+ zIndex: 10,
78
+ top: 0,
79
+ left: 0,
80
+ right: 0,
81
+ bottom: 0,
82
+ ...mobileOverImagePositionStyles(
83
+ value(`${CONTAINER_PREFIX}_over_image_position`) as string
84
+ ),
85
+ }
86
+ : {}),
87
+ },
88
+ contentStyle: {
89
+ flexDirection: model.container.stacking === "vertical" ? "column" : "row",
90
+ alignSelf:
91
+ placement !== "over_image"
92
+ ? model.container.horizontalAlign
93
+ : undefined,
94
+ alignItems: model.container.horizontalAlign,
95
+ marginTop: model.container.margins.top,
96
+ marginRight: model.container.margins.right,
97
+ marginBottom: model.container.margins.bottom,
98
+ marginLeft: model.container.margins.left,
99
+ },
100
+ elements,
101
+ });
102
+ };
103
+
104
+ export { insertButtonsBetweenLabels, insertButtonsBetweenLabelContainers };
@@ -0,0 +1,118 @@
1
+ import {
2
+ insertButtonsBetweenLabelContainers,
3
+ insertButtonsBetweenLabels,
4
+ mobileOverImagePositionStyles,
5
+ } from "..";
6
+
7
+ describe("mobile action insertion helpers", () => {
8
+ const labels = [{ name: "text_label_1" }, { name: "text_label_2" }];
9
+ const buttons = { type: "View", name: "buttons" };
10
+
11
+ it("inserts label buttons below target label", () => {
12
+ const result = insertButtonsBetweenLabels(
13
+ { mobile_buttons_container_position: "below_text_label_1" },
14
+ buttons,
15
+ labels
16
+ );
17
+
18
+ expect(result).toEqual([labels[0], buttons, labels[1]]);
19
+ });
20
+
21
+ it("returns labels unchanged when the flat-label target is unknown", () => {
22
+ const result = insertButtonsBetweenLabels(
23
+ { mobile_buttons_container_position: "unknown" },
24
+ buttons,
25
+ labels
26
+ );
27
+
28
+ expect(result).toEqual([labels[0], labels[1]]);
29
+ });
30
+
31
+ it("inserts label-container buttons below nested target", () => {
32
+ const labelContainers = [
33
+ {
34
+ elements: [{ elements: [{ name: "top_text_label_1" }] }],
35
+ },
36
+ ];
37
+
38
+ const result = insertButtonsBetweenLabelContainers(
39
+ { mobile_buttons_container_position: "top_text_label_1" },
40
+ buttons,
41
+ labelContainers
42
+ );
43
+
44
+ expect(result).toEqual([
45
+ {
46
+ elements: [{ elements: [{ name: "top_text_label_1" }, buttons] }],
47
+ },
48
+ ]);
49
+ });
50
+
51
+ it("inserts label-container buttons below direct target in two-level structure", () => {
52
+ const labelContainers = [
53
+ {
54
+ elements: [{ name: "top_text_label_1" }],
55
+ },
56
+ ];
57
+
58
+ const result = insertButtonsBetweenLabelContainers(
59
+ { mobile_buttons_container_position: "top_text_label_1" },
60
+ buttons,
61
+ labelContainers
62
+ );
63
+
64
+ expect(result).toEqual([
65
+ {
66
+ elements: [{ name: "top_text_label_1" }, buttons],
67
+ },
68
+ ]);
69
+ });
70
+
71
+ it("appends container buttons into the last container when target is unknown", () => {
72
+ const labelContainers = [
73
+ {
74
+ elements: [{ elements: [{ name: "top_text_label_1" }] }],
75
+ },
76
+ {
77
+ elements: [{ elements: [{ name: "bottom_text_label_1" }] }],
78
+ },
79
+ ];
80
+
81
+ const result = insertButtonsBetweenLabelContainers(
82
+ { mobile_buttons_container_position: "unknown" },
83
+ buttons,
84
+ labelContainers
85
+ );
86
+
87
+ expect(result).toEqual([
88
+ labelContainers[0],
89
+ {
90
+ elements: [...labelContainers[1].elements, buttons],
91
+ },
92
+ ]);
93
+ });
94
+ });
95
+
96
+ describe("mobileOverImagePositionStyles", () => {
97
+ it("maps bottom left anchor to absolute positioning", () => {
98
+ const result = mobileOverImagePositionStyles("bottom_left");
99
+
100
+ expect(result).toEqual({
101
+ justifyContent: "flex-end",
102
+ alignItems: "flex-start",
103
+ });
104
+ });
105
+
106
+ it("maps center anchor to absolute center positioning", () => {
107
+ const result = mobileOverImagePositionStyles("center");
108
+
109
+ expect(result).toEqual({
110
+ justifyContent: "center",
111
+ alignItems: "center",
112
+ top: 0,
113
+ left: 0,
114
+ right: 0,
115
+ bottom: 0,
116
+ });
117
+ });
118
+ });
@@ -0,0 +1,73 @@
1
+ import {
2
+ insertBetweenLabelContainers,
3
+ insertBetweenLabels,
4
+ } from "../../../ActionButtonsCore/placement";
5
+
6
+ export const insertButtonsBetweenLabels = (
7
+ configuration: Record<string, unknown>,
8
+ buttons,
9
+ labels = []
10
+ ) =>
11
+ insertBetweenLabels(
12
+ {
13
+ position: configuration?.mobile_buttons_container_position as
14
+ | string
15
+ | undefined,
16
+ allowOnTop: false,
17
+ appendWhenMissing: false,
18
+ },
19
+ buttons,
20
+ labels // "text_label_1", "text_label_2", "text_label_3", "text_label_4"
21
+ );
22
+
23
+ export const insertButtonsBetweenLabelContainers = (
24
+ configuration: Record<string, unknown>,
25
+ buttons,
26
+ labelContainers = []
27
+ ) =>
28
+ insertBetweenLabelContainers(
29
+ {
30
+ position: configuration?.mobile_buttons_container_position as
31
+ | string
32
+ | undefined,
33
+ allowOnTop: false,
34
+ appendWhenMissing: true,
35
+ },
36
+ buttons,
37
+ labelContainers // top_label_1, top_label_2, bottom_label_1, bottom_label_2, etc.
38
+ );
39
+
40
+ export const mobileOverImagePositionStyles = (position: string) => {
41
+ switch (position) {
42
+ case "top_left":
43
+ return {
44
+ justifyContent: "flex-start",
45
+ alignItems: "flex-start",
46
+ };
47
+ case "top_right":
48
+ return {
49
+ justifyContent: "flex-start",
50
+ alignItems: "flex-end",
51
+ };
52
+ case "bottom_left":
53
+ return {
54
+ justifyContent: "flex-end",
55
+ alignItems: "flex-start",
56
+ };
57
+ case "bottom_right":
58
+ return {
59
+ justifyContent: "flex-end",
60
+ alignItems: "flex-end",
61
+ };
62
+ case "center":
63
+ default:
64
+ return {
65
+ justifyContent: "center",
66
+ alignItems: "center",
67
+ top: 0,
68
+ left: 0,
69
+ right: 0,
70
+ bottom: 0,
71
+ };
72
+ }
73
+ };
@@ -0,0 +1,86 @@
1
+ import { TvActionButtons } from "..";
2
+
3
+ describe("TvActionButtons", () => {
4
+ const baseConfiguration = {
5
+ tv_buttons_container_buttons_enabled: true,
6
+ tv_buttons_container_align: "middle",
7
+ tv_buttons_container_margin_top: 1,
8
+ tv_buttons_container_margin_right: 2,
9
+ tv_buttons_container_margin_bottom: 3,
10
+ tv_buttons_container_margin_left: 4,
11
+ tv_buttons_container_horizontal_gutter: 10,
12
+ tv_buttons_container_independent_styles: false,
13
+ tv_buttons_button_1_button_enabled: true,
14
+ tv_buttons_button_1_assign_action: "action_1",
15
+ tv_buttons_button_1_background_padding_top: 11,
16
+ tv_buttons_button_1_background_padding_right: 12,
17
+ tv_buttons_button_1_background_padding_bottom: 13,
18
+ tv_buttons_button_1_background_padding_left: 14,
19
+ tv_buttons_button_3_button_enabled: true,
20
+ tv_buttons_button_3_assign_action: "action_3",
21
+ tv_buttons_button_3_background_padding_top: 31,
22
+ tv_buttons_button_3_background_padding_right: 32,
23
+ tv_buttons_button_3_background_padding_bottom: 33,
24
+ tv_buttons_button_3_background_padding_left: 34,
25
+ };
26
+
27
+ const platformValue = jest.fn();
28
+
29
+ it("renders sparse enabled slots with slot-based action lookup and ids", () => {
30
+ const value = (key) => baseConfiguration[key];
31
+
32
+ const result = TvActionButtons({
33
+ value,
34
+ platformValue,
35
+ configuration: baseConfiguration,
36
+ state: "focused",
37
+ skipButtons: false,
38
+ });
39
+
40
+ expect(result.style.justifyContent).toBe("center");
41
+ expect(result.additionalProps.buttonsCount).toBe(2);
42
+ expect(result.elements).toHaveLength(2);
43
+
44
+ expect(result.elements[0].additionalProps.pluginIdentifier).toBe(
45
+ "action_1"
46
+ );
47
+
48
+ expect(result.elements[0].additionalProps.suffixId).toBe(
49
+ "tv_buttons_button_1"
50
+ );
51
+
52
+ expect(result.elements[0].additionalProps.preferredFocus).toBe(true);
53
+
54
+ expect(result.elements[1].additionalProps.pluginIdentifier).toBe(
55
+ "action_3"
56
+ );
57
+
58
+ expect(result.elements[1].additionalProps.suffixId).toBe(
59
+ "tv_buttons_button_3"
60
+ );
61
+
62
+ expect(result.elements[1].additionalProps.preferredFocus).toBe(false);
63
+ });
64
+
65
+ it("keeps shared button styles from slot 1 while preserving slot 3 identity", () => {
66
+ const value = (key) => baseConfiguration[key];
67
+
68
+ const result = TvActionButtons({
69
+ value,
70
+ platformValue,
71
+ configuration: baseConfiguration,
72
+ state: "focused",
73
+ skipButtons: false,
74
+ });
75
+
76
+ expect(result.elements[1].style.paddingTop).toBe(11);
77
+
78
+ expect(result.elements[1].additionalProps.pluginIdentifier).toBe(
79
+ "action_3"
80
+ );
81
+
82
+ expect(result.elements[1].additionalProps.suffixId).toBe(
83
+ "tv_buttons_button_3"
84
+ );
85
+ });
86
+ });
@@ -1,12 +1,6 @@
1
- import { times } from "@applicaster/zapp-react-native-utils/utils";
2
- import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
3
-
4
1
  import { Button } from "./Button";
5
- import {
6
- getButtonsCount,
7
- memoizedGetPluginIdentifier,
8
- mapSelfAlignment,
9
- } from "./utils";
2
+ import { memoizedGetPluginIdentifier } from "./utils";
3
+ import { buildActionButtonsModel } from "../../ActionButtonsCore/model";
10
4
 
11
5
  import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
12
6
  import { PREFIX, BUTTON_PREFIX } from "./const";
@@ -16,11 +10,9 @@ export {
16
10
  insertButtonsBetweenLabelContainers,
17
11
  } from "./utils";
18
12
 
19
- const buttonId = (index: number) => `${BUTTON_PREFIX}_${index}`;
20
-
21
13
  type Props = {
22
- value: Function;
23
- platformValue: Function;
14
+ value: (key: string) => unknown;
15
+ platformValue: (key: string) => unknown;
24
16
  configuration: Record<string, unknown>;
25
17
  state: string;
26
18
  skipButtons: boolean;
@@ -33,62 +25,53 @@ export const TvActionButtons = ({
33
25
  state,
34
26
  skipButtons,
35
27
  }: Props) => {
36
- if (skipButtons || !value(`${PREFIX}_container_buttons_enabled`)) {
28
+ if (skipButtons) {
37
29
  return null;
38
30
  }
39
31
 
40
- const buttonsCount = getButtonsCount(configuration, PREFIX);
32
+ const model = buildActionButtonsModel({
33
+ configuration,
34
+ value,
35
+ containerPrefix: `${PREFIX}_container`,
36
+ buttonPrefix: BUTTON_PREFIX,
37
+ });
41
38
 
42
- if (buttonsCount <= 0) {
39
+ if (!model) {
43
40
  return null;
44
41
  }
45
42
 
46
- const independentStyles = value(`${PREFIX}_container_independent_styles`);
47
-
48
43
  return {
49
44
  type: "ButtonContainerView",
50
45
  style: {
51
46
  flexDirection: "row",
52
-
53
- justifyContent: mapSelfAlignment(value(`${PREFIX}_container_align`)),
54
-
55
- marginTop: toNumberWithDefaultZero(
56
- value(`${PREFIX}_container_margin_top`)
57
- ),
58
- marginRight: toNumberWithDefaultZero(
59
- value(`${PREFIX}_container_margin_right`)
60
- ),
61
- marginBottom: toNumberWithDefaultZero(
62
- value(`${PREFIX}_container_margin_bottom`)
63
- ),
64
- marginLeft: toNumberWithDefaultZero(
65
- value(`${PREFIX}_container_margin_left`)
66
- ),
47
+ justifyContent: model.container.horizontalAlign,
48
+ marginTop: model.container.margins.top,
49
+ marginRight: model.container.margins.right,
50
+ marginBottom: model.container.margins.bottom,
51
+ marginLeft: model.container.margins.left,
67
52
  },
68
53
  additionalProps: {
69
- horizontalGutter: toNumberWithDefaultZero(
70
- value(`${PREFIX}_container_horizontal_gutter`)
71
- ),
54
+ horizontalGutter: model.container.horizontalGutter,
72
55
  state,
73
- buttonsCount,
56
+ buttonsCount: model.buttonsCount,
74
57
  },
75
58
  elements: compact(
76
- times((index) => {
77
- const prefixSpecificButton = buttonId(index + 1);
78
-
79
- return Button({
80
- prefix: independentStyles ? prefixSpecificButton : buttonId(1),
81
- value,
82
- platformValue,
83
- pluginIdentifier: memoizedGetPluginIdentifier(
84
- configuration,
85
- PREFIX,
86
- index
87
- ),
88
- suffixId: prefixSpecificButton,
89
- preferredFocus: index === 0,
90
- });
91
- }, buttonsCount)
59
+ model.buttons.map(
60
+ ({ slot, renderIndex, specificPrefix, stylePrefix }) => {
61
+ return Button({
62
+ prefix: stylePrefix,
63
+ value,
64
+ platformValue,
65
+ pluginIdentifier: memoizedGetPluginIdentifier(
66
+ configuration,
67
+ PREFIX,
68
+ slot
69
+ ) as string,
70
+ suffixId: specificPrefix,
71
+ preferredFocus: renderIndex === 0,
72
+ });
73
+ }
74
+ )
92
75
  ),
93
76
  };
94
77
  };