@applicaster/zapp-react-native-ui-components 15.0.0-rc.143 → 15.0.0-rc.145

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 (53) hide show
  1. package/Components/FocusableGroup/index.tsx +3 -2
  2. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
  3. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  4. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  5. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  6. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  7. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  8. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  9. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  10. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  11. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  12. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  13. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  14. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  15. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  16. package/Components/MasterCell/DefaultComponents/PressableView.tsx +7 -234
  17. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  18. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  19. package/Components/MasterCell/DefaultComponents/index.ts +7 -3
  20. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  21. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +20 -29
  22. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  23. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +67 -69
  24. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +21 -16
  25. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +207 -9
  26. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +56 -55
  27. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +137 -16
  28. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +49 -31
  29. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +165 -0
  30. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  31. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  32. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  33. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  34. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +24 -21
  35. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  36. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +24 -12
  37. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +62 -0
  38. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  39. package/Components/MasterCell/README.md +4 -0
  40. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  41. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  42. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  43. package/Components/MasterCell/dataAdapter.ts +4 -1
  44. package/Components/MasterCell/elementMapper.tsx +51 -7
  45. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  46. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  47. package/Components/MasterCell/utils/index.ts +85 -15
  48. package/Components/Navigator/StackNavigator.tsx +6 -0
  49. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
  50. package/package.json +5 -5
  51. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +0 -23
  52. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  53. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
@@ -0,0 +1,405 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render, fireEvent } from "@testing-library/react-native";
4
+ import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
+
6
+ import { ActionButtonController } from "../ActionButtonController";
7
+
8
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
9
+ useActions: jest.fn(),
10
+ }));
11
+
12
+ const mockUseActions = useActions as jest.Mock;
13
+
14
+ const entry = { id: "entry-1" } as ZappEntry;
15
+
16
+ describe("ActionButtonController", () => {
17
+ beforeEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+
21
+ it("returns null when action is unavailable", () => {
22
+ const actionContext = {
23
+ isActionAvailable: jest.fn(() => false),
24
+ initialEntryState: jest.fn(),
25
+ };
26
+
27
+ mockUseActions.mockReturnValue(actionContext);
28
+
29
+ const child = jest.fn(() => <Text testID="content">content</Text>);
30
+
31
+ const { queryByTestId } = render(
32
+ <ActionButtonController
33
+ action={{ identifier: "navigation_action" }}
34
+ entry={entry}
35
+ >
36
+ {child}
37
+ </ActionButtonController>
38
+ );
39
+
40
+ expect(queryByTestId("content")).toBeNull();
41
+ expect(child).not.toHaveBeenCalled();
42
+ expect(actionContext.isActionAvailable).toHaveBeenCalledWith(entry);
43
+ });
44
+
45
+ it("passes action context, action state and press handler to children", () => {
46
+ const actionContext = {
47
+ isActionAvailable: jest.fn(() => true),
48
+ initialEntryState: jest.fn(() => ({ active: true })),
49
+ invokeAction: jest.fn(),
50
+ addListener: jest.fn(() => jest.fn()),
51
+ };
52
+
53
+ mockUseActions.mockReturnValue(actionContext);
54
+
55
+ const child = jest.fn(({ actionState, onPress, isActive }) => (
56
+ <Text testID="content" onPress={onPress}>
57
+ {String(actionState?.active)}-{String(isActive)}
58
+ </Text>
59
+ ));
60
+
61
+ const { getByTestId } = render(
62
+ <ActionButtonController
63
+ action={{ identifier: "navigation_action" }}
64
+ entry={entry}
65
+ >
66
+ {child}
67
+ </ActionButtonController>
68
+ );
69
+
70
+ expect(actionContext.initialEntryState).toHaveBeenCalledWith(entry);
71
+
72
+ expect(child).toHaveBeenCalledWith(
73
+ expect.objectContaining({
74
+ actionContext,
75
+ actionState: { active: true },
76
+ entry,
77
+ isActive: true,
78
+ onPress: expect.any(Function),
79
+ })
80
+ );
81
+
82
+ fireEvent.press(getByTestId("content"));
83
+
84
+ expect(actionContext.invokeAction).toHaveBeenCalledWith(
85
+ entry,
86
+ expect.objectContaining({
87
+ updateState: expect.any(Function),
88
+ })
89
+ );
90
+ });
91
+
92
+ it("updates state from addListener", () => {
93
+ let listener;
94
+
95
+ const actionContext = {
96
+ isActionAvailable: jest.fn(() => true),
97
+ initialEntryState: jest.fn(() => ({ active: false })),
98
+ addListener: jest.fn((_entryId, callback) => {
99
+ listener = callback;
100
+
101
+ return jest.fn();
102
+ }),
103
+ invokeAction: jest.fn(),
104
+ };
105
+
106
+ mockUseActions.mockReturnValue(actionContext);
107
+
108
+ const { getByText } = render(
109
+ <ActionButtonController
110
+ action={{ identifier: "navigation_action" }}
111
+ entry={entry}
112
+ >
113
+ {({ actionState }) => <Text>{String(actionState?.active)}</Text>}
114
+ </ActionButtonController>
115
+ );
116
+
117
+ expect(getByText("false")).toBeTruthy();
118
+
119
+ listener({ active: true });
120
+
121
+ expect(getByText("true")).toBeTruthy();
122
+ });
123
+
124
+ it("subscribes via addListener and unsubscribes on unmount", () => {
125
+ const unsubscribe = jest.fn();
126
+
127
+ const actionContext = {
128
+ isActionAvailable: jest.fn(() => true),
129
+ initialEntryState: jest.fn(() => ({ active: false })),
130
+ addListener: jest.fn(() => unsubscribe),
131
+ };
132
+
133
+ mockUseActions.mockReturnValue(actionContext);
134
+
135
+ const { unmount } = render(
136
+ <ActionButtonController
137
+ action={{ identifier: "navigation_action" }}
138
+ entry={entry}
139
+ >
140
+ {() => <Text testID="content">content</Text>}
141
+ </ActionButtonController>
142
+ );
143
+
144
+ expect(actionContext.addListener).toHaveBeenCalledWith(
145
+ String(entry.id),
146
+ expect.any(Function)
147
+ );
148
+
149
+ unmount();
150
+
151
+ expect(unsubscribe).toHaveBeenCalledTimes(1);
152
+ });
153
+
154
+ it("subscribes via addListeners and unsubscribes on unmount when addListener is missing", () => {
155
+ const unsubscribe = jest.fn();
156
+
157
+ const actionContext = {
158
+ isActionAvailable: jest.fn(() => true),
159
+ addListeners: jest.fn(() => unsubscribe),
160
+ initialEntryState: undefined,
161
+ };
162
+
163
+ mockUseActions.mockReturnValue(actionContext);
164
+
165
+ const { unmount } = render(
166
+ <ActionButtonController
167
+ action={{ identifier: "navigation_action" }}
168
+ entry={entry}
169
+ >
170
+ {() => <Text testID="content">content</Text>}
171
+ </ActionButtonController>
172
+ );
173
+
174
+ expect(actionContext.addListeners).toHaveBeenCalledWith(
175
+ expect.any(Function)
176
+ );
177
+
178
+ unmount();
179
+
180
+ expect(unsubscribe).toHaveBeenCalledTimes(1);
181
+ });
182
+
183
+ it("subscribes when action context becomes available after initial render", () => {
184
+ const actionContext = {
185
+ isActionAvailable: jest.fn(() => true),
186
+ initialEntryState: jest.fn(() => ({ active: false })),
187
+ addListener: jest.fn(() => jest.fn()),
188
+ invokeAction: jest.fn(),
189
+ };
190
+
191
+ mockUseActions
192
+ .mockReturnValueOnce(undefined)
193
+ .mockReturnValue(actionContext);
194
+
195
+ const { rerender } = render(
196
+ <ActionButtonController
197
+ action={{ identifier: "navigation_action" }}
198
+ entry={entry}
199
+ >
200
+ {() => <Text testID="content">content</Text>}
201
+ </ActionButtonController>
202
+ );
203
+
204
+ expect(actionContext.addListener).not.toHaveBeenCalled();
205
+
206
+ rerender(
207
+ <ActionButtonController
208
+ action={{ identifier: "navigation_action" }}
209
+ entry={entry}
210
+ >
211
+ {() => <Text testID="content">content</Text>}
212
+ </ActionButtonController>
213
+ );
214
+
215
+ expect(actionContext.addListener).toHaveBeenCalledWith(
216
+ String(entry.id),
217
+ expect.any(Function)
218
+ );
219
+ });
220
+
221
+ it("falls back to legacy toggle behavior without initialEntryState", () => {
222
+ const addFavourite = jest.fn();
223
+ const removeFavourite = jest.fn();
224
+
225
+ const actionContext = {
226
+ state: [entry],
227
+ masterCell: {
228
+ isSelected: jest.fn(() => true),
229
+ },
230
+ addFavourite,
231
+ removeFavourite,
232
+ isActionAvailable: jest.fn(() => true),
233
+ addListeners: jest.fn(() => jest.fn()),
234
+ };
235
+
236
+ mockUseActions.mockReturnValue(actionContext);
237
+
238
+ const { getByTestId } = render(
239
+ <ActionButtonController
240
+ action={{ identifier: "navigation_action" }}
241
+ entry={entry}
242
+ >
243
+ {({ onPress, isActive }) => (
244
+ <Text testID="content" onPress={onPress}>
245
+ {String(isActive)}
246
+ </Text>
247
+ )}
248
+ </ActionButtonController>
249
+ );
250
+
251
+ expect(getByTestId("content").props.children).toBe("true");
252
+
253
+ fireEvent.press(getByTestId("content"));
254
+
255
+ expect(removeFavourite).toHaveBeenCalledWith(entry);
256
+ expect(addFavourite).not.toHaveBeenCalled();
257
+ });
258
+
259
+ it("uses legacy addFavourite when item is not selected", () => {
260
+ const addFavourite = jest.fn();
261
+ const removeFavourite = jest.fn();
262
+
263
+ const actionContext = {
264
+ state: [],
265
+ masterCell: {
266
+ isSelected: jest.fn(() => false),
267
+ },
268
+ addFavourite,
269
+ removeFavourite,
270
+ isActionAvailable: jest.fn(() => true),
271
+ addListeners: jest.fn(() => jest.fn()),
272
+ };
273
+
274
+ mockUseActions.mockReturnValue(actionContext);
275
+
276
+ const { getByTestId } = render(
277
+ <ActionButtonController
278
+ action={{ identifier: "navigation_action" }}
279
+ entry={entry}
280
+ >
281
+ {({ onPress, isActive }) => (
282
+ <Text testID="content" onPress={onPress}>
283
+ {String(isActive)}
284
+ </Text>
285
+ )}
286
+ </ActionButtonController>
287
+ );
288
+
289
+ expect(getByTestId("content").props.children).toBe("false");
290
+
291
+ fireEvent.press(getByTestId("content"));
292
+
293
+ expect(addFavourite).toHaveBeenCalledWith(entry);
294
+ expect(removeFavourite).not.toHaveBeenCalled();
295
+ });
296
+
297
+ it("prefers invokeAction over legacy favourites handlers in legacy mode", () => {
298
+ const invokeAction = jest.fn();
299
+ const addFavourite = jest.fn();
300
+ const removeFavourite = jest.fn();
301
+
302
+ const actionContext = {
303
+ state: [],
304
+ masterCell: {
305
+ isSelected: jest.fn(() => false),
306
+ },
307
+ invokeAction,
308
+ addFavourite,
309
+ removeFavourite,
310
+ isActionAvailable: jest.fn(() => true),
311
+ addListeners: jest.fn(() => jest.fn()),
312
+ };
313
+
314
+ mockUseActions.mockReturnValue(actionContext);
315
+
316
+ const { getByTestId } = render(
317
+ <ActionButtonController
318
+ action={{ identifier: "navigation_action" }}
319
+ entry={entry}
320
+ >
321
+ {({ onPress }) => (
322
+ <Text testID="content" onPress={onPress}>
323
+ press
324
+ </Text>
325
+ )}
326
+ </ActionButtonController>
327
+ );
328
+
329
+ fireEvent.press(getByTestId("content"));
330
+
331
+ expect(invokeAction).toHaveBeenCalledWith(entry);
332
+ expect(addFavourite).not.toHaveBeenCalled();
333
+ expect(removeFavourite).not.toHaveBeenCalled();
334
+ });
335
+
336
+ it("calls resolved legacy toggleAction onPress when invokeAction is missing", async () => {
337
+ const addFavourite = jest.fn(() => Promise.resolve("added"));
338
+ const removeFavourite = jest.fn();
339
+
340
+ const actionContext = {
341
+ state: [],
342
+ masterCell: {
343
+ isSelected: jest.fn(() => false),
344
+ },
345
+ addFavourite,
346
+ removeFavourite,
347
+ isActionAvailable: jest.fn(() => true),
348
+ addListeners: jest.fn(() => jest.fn()),
349
+ };
350
+
351
+ mockUseActions.mockReturnValue(actionContext);
352
+
353
+ let capturedOnPress: (() => Promise<unknown> | unknown) | undefined;
354
+
355
+ render(
356
+ <ActionButtonController
357
+ action={{ identifier: "navigation_action" }}
358
+ entry={entry}
359
+ >
360
+ {({ onPress }) => {
361
+ capturedOnPress = onPress;
362
+
363
+ return <Text testID="content">press</Text>;
364
+ }}
365
+ </ActionButtonController>
366
+ );
367
+
368
+ await capturedOnPress?.();
369
+
370
+ expect(addFavourite).toHaveBeenCalledWith(entry);
371
+ expect(removeFavourite).not.toHaveBeenCalled();
372
+ });
373
+
374
+ it("wires invokeAction updateState callback to update render state", () => {
375
+ const actionContext = {
376
+ isActionAvailable: jest.fn(() => true),
377
+ initialEntryState: jest.fn(() => ({ active: false })),
378
+ invokeAction: jest.fn((_entry, options) => {
379
+ options.updateState({ active: true });
380
+ }),
381
+ addListener: jest.fn(() => jest.fn()),
382
+ };
383
+
384
+ mockUseActions.mockReturnValue(actionContext);
385
+
386
+ const { getByTestId, getByText } = render(
387
+ <ActionButtonController
388
+ action={{ identifier: "navigation_action" }}
389
+ entry={entry}
390
+ >
391
+ {({ onPress, actionState }) => (
392
+ <Text testID="content" onPress={onPress}>
393
+ {String(actionState?.active)}
394
+ </Text>
395
+ )}
396
+ </ActionButtonController>
397
+ );
398
+
399
+ expect(getByText("false")).toBeTruthy();
400
+
401
+ fireEvent.press(getByTestId("content"));
402
+
403
+ expect(getByText("true")).toBeTruthy();
404
+ });
405
+ });
@@ -0,0 +1 @@
1
+ export { ActionButtonController } from "./ActionButtonController";
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+
4
+ import type { HorizontalSeparatorProps } from "../types";
5
+
6
+ export const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
7
+ <View style={{ width }} />
8
+ );
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { ContainerProps } from "./types";
4
+
5
+ export function ButtonContainerView({
6
+ style,
7
+ contentStyle,
8
+ children,
9
+ }: ContainerProps) {
10
+ return (
11
+ <View style={style}>
12
+ <View style={contentStyle}>{children}</View>
13
+ </View>
14
+ );
15
+ }
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import * as R from "ramda";
4
+ import { useInitialFocus } from "@applicaster/zapp-react-native-utils/focusManager";
5
+ import {
6
+ useIsRTL,
7
+ applyRTLStylesIfNeeded,
8
+ } from "@applicaster/zapp-react-native-utils/localizationUtils";
9
+
10
+ import {
11
+ cloneChildrenWithIds,
12
+ insertBetween,
13
+ unwrapDataProviderChild,
14
+ useFilterChildren,
15
+ } from "../../utils";
16
+
17
+ import type { ContainerProps, ContainerChildren } from "./types";
18
+ import { HorizontalSeparator } from "./components/HorizontalSeparator";
19
+
20
+ const generateId = (cellUUID, suffixId) => `${cellUUID}--${suffixId}`;
21
+
22
+ export function ButtonContainerView({
23
+ style,
24
+ children,
25
+ ...otherProps
26
+ }: ContainerProps) {
27
+ const isRTL = useIsRTL();
28
+
29
+ const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
30
+ const filteredChildren = useFilterChildren<ContainerChildren>(children);
31
+
32
+ const buttonIds = filteredChildren.map((child) => {
33
+ const wrappedChild = unwrapDataProviderChild(child) as ContainerChildren;
34
+ const { cellUUID, suffixId } = wrappedChild.props;
35
+
36
+ return generateId(cellUUID, suffixId);
37
+ });
38
+
39
+ useInitialFocus(otherProps.state === "focused", R.head(buttonIds));
40
+
41
+ if (R.isEmpty(filteredChildren)) {
42
+ return null;
43
+ }
44
+
45
+ return (
46
+ <View style={applyRTLStylesIfNeeded(style, isRTL)}>
47
+ {insertBetween(
48
+ (index) => (
49
+ <HorizontalSeparator
50
+ key={`separator_${index}`}
51
+ width={horizontalGutter}
52
+ />
53
+ ),
54
+ cloneChildrenWithIds(buttonIds, filteredChildren)
55
+ )}
56
+ </View>
57
+ );
58
+ }
@@ -1,20 +1,13 @@
1
1
  import React from "react";
2
2
  import { View } from "react-native";
3
3
  import * as R from "ramda";
4
- import { useFilterChildren, insertBetween } from "../../../utils";
5
- import type {
6
- HorizontalSeparatorProps,
7
- ContainerProps,
8
- ContainerChildren,
9
- } from "./types";
4
+ import { insertBetween, useFilterChildren } from "../../utils";
5
+ import type { ContainerProps, ContainerChildren } from "./types";
10
6
  import {
11
7
  useIsRTL,
12
8
  applyRTLStylesIfNeeded,
13
9
  } from "@applicaster/zapp-react-native-utils/localizationUtils";
14
-
15
- const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
16
- <View style={{ width }} />
17
- );
10
+ import { HorizontalSeparator } from "./components/HorizontalSeparator";
18
11
 
19
12
  export function ButtonContainerView({
20
13
  style,
@@ -23,7 +16,6 @@ export function ButtonContainerView({
23
16
  }: ContainerProps) {
24
17
  const isRTL = useIsRTL();
25
18
  const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
26
-
27
19
  const filteredChildren = useFilterChildren<ContainerChildren>(children);
28
20
 
29
21
  if (R.isEmpty(filteredChildren)) {
@@ -0,0 +1 @@
1
+ export { ButtonContainerView } from "./index.tv";
@@ -0,0 +1,40 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ import { ViewStyle } from "react-native";
3
+
4
+ export type ContainerChildProps = {
5
+ item: any;
6
+ pluginIdentifier: string;
7
+ cellUUID: string;
8
+ suffixId: string;
9
+ nextFocusLeft?: string;
10
+ nextFocusRight?: string;
11
+ };
12
+
13
+ export type ContainerProps = Record<string, any> & {
14
+ buttonsToggleEnabled: boolean;
15
+ skipButtons: boolean;
16
+ style: ViewStyle;
17
+ contentStyle?: ViewStyle;
18
+ children: ContainerChildren[];
19
+ };
20
+
21
+ export type ButtonProps = Record<string, any> & {
22
+ style: ViewStyle;
23
+ children?: ReactNode;
24
+ item: any;
25
+ cellUUID: string;
26
+ groupId?: string;
27
+ suffixId: string;
28
+ normalStyles?: ViewStyle;
29
+ focusedStyles?: ViewStyle;
30
+ nextFocusLeft?: string;
31
+ nextFocusRight?: string;
32
+ pluginIdentifier: string;
33
+ disableFocus?: boolean;
34
+ };
35
+
36
+ export type HorizontalSeparatorProps = {
37
+ width: number;
38
+ };
39
+
40
+ export type ContainerChildren = ReactElement<ContainerChildProps>;