@applicaster/zapp-react-native-ui-components 16.0.0-rc.6 → 16.0.0-rc.60

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 (129) hide show
  1. package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
  2. package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
  3. package/Components/AudioPlayer/tv/index.tsx +5 -1
  4. package/Components/BackgroundImage/BackgroundImage.android.tv.tsx +28 -0
  5. package/Components/BackgroundImage/BackgroundImage.ios.tv.tsx +24 -0
  6. package/Components/BackgroundImage/BackgroundImage.tsx +42 -0
  7. package/Components/BackgroundImage/index.ts +1 -0
  8. package/Components/CellRendererResolver/index.ts +2 -2
  9. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  10. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  11. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  12. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  13. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  16. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  17. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  18. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  19. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  20. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  22. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  23. package/Components/GeneralContentScreen/index.ts +2 -0
  24. package/Components/Layout/FullWidthRow.tsx +38 -0
  25. package/Components/Layout/TV/ScreenContainer.tsx +5 -0
  26. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
  27. package/Components/Layout/TV/index.tsx +3 -4
  28. package/Components/Layout/TV/index.web.tsx +2 -3
  29. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  32. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +1 -1
  33. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  34. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  35. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  36. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  37. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  38. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  39. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  40. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  41. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  43. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  44. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
  45. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  46. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  47. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  48. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  49. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  50. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  51. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  52. package/Components/MasterCell/dataAdapter.ts +15 -5
  53. package/Components/MasterCell/hooks/index.ts +2 -0
  54. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  55. package/Components/MasterCell/index.tsx +30 -5
  56. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  57. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  58. package/Components/MasterCell/utils/index.ts +85 -46
  59. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +314 -0
  60. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +395 -0
  61. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +763 -0
  62. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +653 -0
  63. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +874 -0
  64. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +89 -0
  65. package/Components/ModalComponent/AudioPlayer/Components/index.ts +11 -0
  66. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +199 -0
  67. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  68. package/Components/ModalComponent/BottomSheetModalContent.tsx +386 -42
  69. package/Components/ModalComponent/Button/index.tsx +5 -3
  70. package/Components/ModalComponent/Header/index.tsx +20 -7
  71. package/Components/ModalComponent/Header/utils.ts +1 -1
  72. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  73. package/Components/ModalComponent/SortableList.tsx +142 -0
  74. package/Components/ModalComponent/TextInputContent.tsx +100 -0
  75. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  76. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +238 -0
  77. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +120 -0
  78. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  79. package/Components/ModalComponent/index.tsx +11 -0
  80. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +66 -0
  81. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  82. package/Components/ModalComponent/presets/index.ts +27 -0
  83. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  84. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  85. package/Components/ModalComponent/presets/types.ts +35 -0
  86. package/Components/ModalComponent/showTextInput.ts +43 -0
  87. package/Components/ModalComponent/utils.ts +76 -20
  88. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  89. package/Components/OfflineHandler/__tests__/index.test.tsx +78 -18
  90. package/Components/OfflineHandler/hooks.ts +123 -0
  91. package/Components/OfflineHandler/index.tsx +51 -73
  92. package/Components/OfflineHandler/utils/index.ts +1 -13
  93. package/Components/PlayerContainer/PlayerContainer.tsx +25 -17
  94. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  95. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
  96. package/Components/PreloaderWrapper/index.tsx +1 -1
  97. package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -2
  98. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +3 -15
  99. package/Components/Screen/TV/hooks/__tests__/useAfterPaint.test.ts +60 -0
  100. package/Components/Screen/TV/hooks/index.ts +2 -0
  101. package/Components/Screen/TV/hooks/useAfterPaint.ts +23 -0
  102. package/Components/Screen/TV/index.web.tsx +16 -7
  103. package/Components/Screen/index.tsx +8 -4
  104. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  105. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  106. package/Components/ScreenRevealManager/Overlay.tsx +34 -0
  107. package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
  108. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +8 -19
  109. package/Components/TopCutoffOverlay/index.tsx +2 -2
  110. package/Components/Transitioner/Transitioner.tsx +28 -17
  111. package/Components/VideoLive/LiveImageManager.ts +41 -43
  112. package/Components/VideoModal/utils.ts +6 -1
  113. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  114. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  115. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  116. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  117. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  118. package/Helpers/index.js +7 -40
  119. package/package.json +6 -5
  120. package/Components/Layout/TV/LayoutBackground.tsx +0 -31
  121. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  122. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  123. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  124. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  125. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  126. package/Components/Screen/utils.ts +0 -16
  127. package/Helpers/Analytics/index.js +0 -95
  128. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
  129. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -1,16 +1,54 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
- import { View, ScrollView } from "react-native";
2
+ import {
3
+ ActivityIndicator,
4
+ ScrollView,
5
+ StyleSheet,
6
+ Text,
7
+ View,
8
+ } from "react-native";
3
9
  import { Button } from "./Button";
4
10
  import { ItemIconProps } from "./Button/ItemIcon";
5
11
  import { ItemProps } from "./Button/Item";
6
12
  import { ItemLabelProps } from "./Button/ItemLabel";
7
13
  import { getItemIconProps, getItemLabelProps, getItemProps } from "./utils";
8
14
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
9
-
15
+ import {
16
+ ContentViewModel,
17
+ OPERATIONS,
18
+ hasOperation,
19
+ useBottomSheetContent,
20
+ } from "@applicaster/zapp-react-native-utils/modalState";
21
+ import { getCell } from "./presets";
10
22
  import type { PluginConfiguration } from "./";
11
-
12
23
  import { ModalHeader as DefaultModalHeader } from "./Header";
13
24
  import { useSafeAreaInsets } from "react-native-safe-area-context";
25
+ import { SortableList } from "./SortableList";
26
+ import {
27
+ AudioPlayerButton,
28
+ NowPlayingHeaderSection,
29
+ VARIANT_WITH_ICON,
30
+ } from "./AudioPlayer/Components";
31
+ import { BottomSheetDragArea } from "./BottomSheetDragArea";
32
+ import { ModalBlocksStyleContext } from "./ModalBlocksStyleContext";
33
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
34
+ import {
35
+ ModalBlocksConfig,
36
+ parseModalBlocksConfiguration,
37
+ } from "@applicaster/zapp-react-native-utils/configurationUtils/modalBlocksParser";
38
+ import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
39
+
40
+ const styles = StyleSheet.create({
41
+ centered: {
42
+ justifyContent: "center",
43
+ alignItems: "center",
44
+ padding: 16,
45
+ },
46
+ errorText: {
47
+ color: "#ff453a",
48
+ fontSize: 14,
49
+ textAlign: "center",
50
+ },
51
+ });
14
52
 
15
53
  type ModalComponentProps = {
16
54
  items: any[];
@@ -22,7 +60,8 @@ type ModalComponentProps = {
22
60
  title?: string;
23
61
  maxHeight?: number;
24
62
  dismiss: () => void;
25
- headerComponent?: React.ComponentType;
63
+ headerComponent?: React.ComponentType<any>;
64
+ headerComponentProps?: Record<string, any>;
26
65
  buttonComponent?: React.ComponentType;
27
66
  iconProps?: ItemIconProps | ((theme: PluginConfiguration) => ItemIconProps);
28
67
  itemProps?:
@@ -31,13 +70,18 @@ type ModalComponentProps = {
31
70
  labelProps?:
32
71
  | ItemLabelProps
33
72
  | ((theme: PluginConfiguration, width: number) => ItemLabelProps);
34
- getSelectedItemIcon: (
73
+ getSelectedItemIcon?: (
35
74
  theme: PluginConfiguration
36
75
  ) => ItemIconProps["asset"] | null;
37
- getDefaultItemIcon: (
76
+ getDefaultItemIcon?: (
38
77
  theme: PluginConfiguration
39
78
  ) => ItemIconProps["asset"] | null;
40
79
  iconPlacement?: "left" | "right";
80
+ viewModel?: ContentViewModel;
81
+ /** Optional body that replaces the items list (header stays DefaultModalHeader). */
82
+ contentComponent?: React.ComponentType<any>;
83
+ contentComponentProps?: Record<string, any>;
84
+ styleOverrides?: ModalBlocksConfig | Record<string, any>;
41
85
  };
42
86
 
43
87
  export function BottomSheetModalContent(props: ModalComponentProps) {
@@ -49,13 +93,18 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
49
93
  dismiss,
50
94
  summary,
51
95
  title,
52
- headerComponent: ModalHeader = DefaultModalHeader,
96
+ headerComponent,
97
+ headerComponentProps,
98
+ viewModel = null,
53
99
  buttonComponent: ButtonComponent = Button,
54
100
  getSelectedItemIcon = ({
55
101
  modal_bottom_sheet_item_selected_icon,
56
102
  }: BaseThemePropertiesMobile) => modal_bottom_sheet_item_selected_icon,
57
103
  getDefaultItemIcon = () => null,
58
104
  iconPlacement,
105
+ contentComponent: ContentComponent,
106
+ contentComponentProps = {},
107
+ styleOverrides: propStyleOverrides,
59
108
  } = props;
60
109
 
61
110
  const route = useRef(currentRoute);
@@ -63,6 +112,68 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
63
112
  const paddingTop = Number(theme.modal_bottom_sheet_padding_top);
64
113
  const paddingBottom = Number(theme.modal_bottom_sheet_padding_bottom);
65
114
 
115
+ const {
116
+ items: fetchedItems,
117
+ showCentralLoading,
118
+ error,
119
+ handleItemPress,
120
+ role,
121
+ behavior,
122
+ editableCollection,
123
+ themePluginId,
124
+ } = useBottomSheetContent({
125
+ onPress,
126
+ viewModel,
127
+ });
128
+
129
+ const plugins = usePlugins();
130
+
131
+ const plugin = useMemo(
132
+ () => plugins?.find((p) => p.identifier === themePluginId),
133
+ [plugins, themePluginId]
134
+ );
135
+
136
+ const styleOverrides = useMemo(() => {
137
+ if (propStyleOverrides) {
138
+ return propStyleOverrides;
139
+ }
140
+
141
+ const configuration =
142
+ plugin?.configuration || (plugin as any)?.configuration_json;
143
+
144
+ if (!configuration || !themePluginId) return null;
145
+
146
+ return parseModalBlocksConfiguration(configuration, themePluginId);
147
+ }, [propStyleOverrides, plugin, themePluginId]);
148
+
149
+ const pluginLocalizations = useLocalizedStrings({
150
+ localizations:
151
+ plugin?.configuration?.localizations ||
152
+ (plugin as any)?.configuration_json?.localizations,
153
+ });
154
+
155
+ const normalizedPluginId = themePluginId?.replace(/-/g, "_");
156
+
157
+ const localizedHeaderTitle = normalizedPluginId
158
+ ? pluginLocalizations?.[`${normalizedPluginId}_header_title`]
159
+ : undefined;
160
+
161
+ const createPlaylistTitle =
162
+ pluginLocalizations?.create_new_playlist_title || "Create New Playlist";
163
+
164
+ const resolvedTitle = props.title || localizedHeaderTitle || title;
165
+
166
+ const itemsToRender = viewModel ? fetchedItems : items;
167
+ const operations = editableCollection?.operations || [];
168
+ const isSortable = hasOperation(operations, OPERATIONS.REORDER);
169
+
170
+ const cell = getCell(
171
+ viewModel ? role : undefined,
172
+ viewModel ? behavior : undefined
173
+ );
174
+
175
+ const Header = headerComponent ?? DefaultModalHeader;
176
+
66
177
  useEffect(() => {
67
178
  if (currentRoute !== route.current) {
68
179
  props.dismiss();
@@ -74,14 +185,24 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
74
185
  }, [theme, props.iconProps]);
75
186
 
76
187
  const itemBaseProps = useMemo<ItemProps>(() => {
77
- return getItemProps(theme, props.width, props.itemProps);
78
- }, [theme, props.width, props.itemProps]);
188
+ return getItemProps(
189
+ theme,
190
+ props.width,
191
+ props.itemProps,
192
+ styleOverrides?.item
193
+ );
194
+ }, [theme, props.width, props.itemProps, styleOverrides?.item]);
79
195
 
80
196
  const labelBaseProps = useMemo<ItemLabelProps>(() => {
81
- return getItemLabelProps(theme, props.width, props.labelProps);
82
- }, [theme, props.width, props.labelProps]);
197
+ return getItemLabelProps(
198
+ theme,
199
+ props.width,
200
+ props.labelProps,
201
+ styleOverrides?.item
202
+ );
203
+ }, [theme, props.width, props.labelProps, styleOverrides?.item]);
83
204
 
84
- const handlePress = useCallback(
205
+ const handleStaticPress = useCallback(
85
206
  (item: any) => {
86
207
  onPress(item);
87
208
  dismiss();
@@ -89,45 +210,268 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
89
210
  [onPress, dismiss]
90
211
  );
91
212
 
213
+ const onItemPress = viewModel ? handleItemPress : handleStaticPress;
214
+
215
+ const handleDragEnd = useCallback(
216
+ (params: any) => {
217
+ const from = params?.fromIndex ?? params?.from;
218
+ const to = params?.toIndex ?? params?.to;
219
+
220
+ const reorderedItemIds = Array.isArray(params?.data)
221
+ ? params.data
222
+ .filter((item: any) => !item?.isHeader)
223
+ .map((item: any) => item?.id)
224
+ .filter(Boolean)
225
+ : undefined;
226
+
227
+ if (
228
+ (reorderedItemIds?.length ||
229
+ (typeof from === "number" &&
230
+ typeof to === "number" &&
231
+ from !== to)) &&
232
+ editableCollection?.move
233
+ ) {
234
+ editableCollection.move(reorderedItemIds || []);
235
+ }
236
+ },
237
+ [editableCollection]
238
+ );
239
+
240
+ const renderContext = useMemo(
241
+ () => ({
242
+ theme,
243
+ ButtonComponent,
244
+ current_selection,
245
+ iconBaseProps,
246
+ itemBaseProps,
247
+ labelBaseProps,
248
+ getSelectedItemIcon,
249
+ getDefaultItemIcon,
250
+ iconPlacement,
251
+ editableCollection,
252
+ behavior,
253
+ }),
254
+ [
255
+ theme,
256
+ ButtonComponent,
257
+ current_selection,
258
+ iconBaseProps,
259
+ itemBaseProps,
260
+ labelBaseProps,
261
+ getSelectedItemIcon,
262
+ getDefaultItemIcon,
263
+ iconPlacement,
264
+ editableCollection,
265
+ behavior,
266
+ ]
267
+ );
268
+
92
269
  const bottomInset = useSafeAreaInsets().bottom;
93
270
 
94
- return (
95
- <View
96
- style={{
97
- maxHeight: props.maxHeight,
98
- paddingTop: paddingTop,
99
- }}
100
- >
101
- <ModalHeader
102
- dismiss={dismiss}
103
- configuration={theme}
104
- summary={summary}
105
- title={title}
106
- />
271
+ const hasNowPlaying = hasOperation(operations, OPERATIONS.NOW_PLAYING);
272
+ const hasClearAll = hasOperation(operations, OPERATIONS.CLEAR_ALL);
273
+ const hasAdd = hasOperation(operations, OPERATIONS.ADD);
274
+
275
+ const handleDeleteCollection = useCallback(() => {
276
+ editableCollection?.deleteCollection?.();
277
+ }, [editableCollection]);
278
+
279
+ const handleCreatePlaylist = useCallback(() => {
280
+ editableCollection?.add?.({
281
+ id: "create_new_playlist",
282
+ title: createPlaylistTitle,
283
+ });
284
+ }, [editableCollection, createPlaylistTitle]);
285
+
286
+ const headers = useMemo(() => {
287
+ const list: Array<{ index: number; component: () => React.ReactElement }> =
288
+ [];
289
+
290
+ if (hasNowPlaying) {
291
+ list.push({
292
+ index: list.length,
293
+ component: () => (
294
+ <NowPlayingHeaderSection
295
+ onClear={hasClearAll ? handleDeleteCollection : undefined}
296
+ />
297
+ ),
298
+ });
299
+ }
300
+
301
+ if (hasAdd) {
302
+ list.push({
303
+ index: list.length,
304
+ component: () => (
305
+ <View
306
+ style={{ paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12 }}
307
+ >
308
+ <AudioPlayerButton
309
+ configuration={VARIANT_WITH_ICON}
310
+ data={{ title: createPlaylistTitle }}
311
+ onPress={handleCreatePlaylist}
312
+ />
313
+ </View>
314
+ ),
315
+ });
316
+ }
317
+
318
+ return list.length > 0 ? list : undefined;
319
+ }, [
320
+ hasNowPlaying,
321
+ hasClearAll,
322
+ handleDeleteCollection,
323
+ hasAdd,
324
+ handleCreatePlaylist,
325
+ ]);
326
+
327
+ const renderBody = () => {
328
+ if (ContentComponent) {
329
+ const shouldWrapInScrollView =
330
+ !contentComponentProps?.disableScrollViewWrap &&
331
+ !(ContentComponent as any)?.disableScrollViewWrap;
332
+
333
+ if (shouldWrapInScrollView) {
334
+ return (
335
+ <ScrollView
336
+ contentContainerStyle={{
337
+ paddingBottom: paddingBottom + bottomInset,
338
+ }}
339
+ >
340
+ <ContentComponent
341
+ {...contentComponentProps}
342
+ dismiss={dismiss}
343
+ width={props.width}
344
+ maxHeight={props.maxHeight}
345
+ />
346
+ </ScrollView>
347
+ );
348
+ }
349
+
350
+ return (
351
+ <View
352
+ style={{
353
+ flexShrink: 1,
354
+ paddingBottom: paddingBottom + bottomInset,
355
+ }}
356
+ >
357
+ <ContentComponent
358
+ {...contentComponentProps}
359
+ dismiss={dismiss}
360
+ width={props.width}
361
+ maxHeight={props.maxHeight}
362
+ />
363
+ </View>
364
+ );
365
+ }
366
+
367
+ if (showCentralLoading) {
368
+ const loaderColor =
369
+ (theme as any)?.modal_bottom_sheet_activity_indicator_color ||
370
+ (theme as any)?.cell_title_color ||
371
+ "#ffffff";
372
+
373
+ return (
374
+ <View
375
+ style={[
376
+ styles.centered,
377
+ { backgroundColor: theme.modal_bottom_sheet_background_color },
378
+ ]}
379
+ >
380
+ <ActivityIndicator color={loaderColor} />
381
+ </View>
382
+ );
383
+ }
384
+
385
+ if (error) {
386
+ return (
387
+ <View style={styles.centered}>
388
+ <Text style={styles.errorText}>
389
+ {typeof error === "string" ? error : error.message}
390
+ </Text>
391
+ </View>
392
+ );
393
+ }
394
+
395
+ if (isSortable) {
396
+ return (
397
+ <SortableList
398
+ sortableData={itemsToRender}
399
+ headers={headers}
400
+ scrollViewStyle={{
401
+ flexShrink: 1,
402
+ }}
403
+ contentContainerStyle={{
404
+ paddingBottom: paddingBottom + bottomInset,
405
+ }}
406
+ onDragEnd={handleDragEnd}
407
+ renderItem={({ item, index, renderHandle }) =>
408
+ cell.renderItem({
409
+ item,
410
+ index,
411
+ onPress: onItemPress,
412
+ context: renderContext,
413
+ renderHandle,
414
+ })
415
+ }
416
+ />
417
+ );
418
+ }
419
+
420
+ return (
107
421
  <ScrollView
422
+ overScrollMode="never"
423
+ bounces={false}
108
424
  contentContainerStyle={{
109
425
  paddingBottom: paddingBottom + bottomInset,
110
426
  }}
111
427
  >
112
- {items.map((item, index) =>
113
- item ? (
114
- <ButtonComponent
115
- key={index}
116
- configuration={theme}
117
- selectedItem={current_selection}
118
- item={item}
119
- onPress={handlePress}
120
- label={theme[item?.label] ?? item?.label}
121
- iconBaseProps={iconBaseProps}
122
- itemBaseProps={itemBaseProps}
123
- labelBaseProps={labelBaseProps}
124
- selectedItemIcon={getSelectedItemIcon(theme)}
125
- defaultItemIcon={getDefaultItemIcon(theme)}
126
- iconPlacement={iconPlacement}
428
+ {hasNowPlaying ? (
429
+ <NowPlayingHeaderSection
430
+ onClear={hasClearAll ? handleDeleteCollection : undefined}
431
+ />
432
+ ) : null}
433
+ {hasAdd ? (
434
+ <View
435
+ style={{ paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12 }}
436
+ >
437
+ <AudioPlayerButton
438
+ configuration={VARIANT_WITH_ICON}
439
+ data={{ title: createPlaylistTitle }}
440
+ onPress={handleCreatePlaylist}
127
441
  />
128
- ) : null
442
+ </View>
443
+ ) : null}
444
+ {(itemsToRender || []).map((item, index) =>
445
+ cell.renderItem({
446
+ item,
447
+ index,
448
+ onPress: onItemPress,
449
+ context: renderContext,
450
+ })
129
451
  )}
130
452
  </ScrollView>
131
- </View>
453
+ );
454
+ };
455
+
456
+ return (
457
+ <ModalBlocksStyleContext.Provider value={styleOverrides as any}>
458
+ <View
459
+ style={{
460
+ maxHeight: props.maxHeight,
461
+ paddingTop: paddingTop,
462
+ }}
463
+ >
464
+ <BottomSheetDragArea>
465
+ <Header
466
+ {...(headerComponentProps || {})}
467
+ dismiss={dismiss}
468
+ configuration={theme}
469
+ summary={summary}
470
+ title={resolvedTitle}
471
+ />
472
+ </BottomSheetDragArea>
473
+ {renderBody()}
474
+ </View>
475
+ </ModalBlocksStyleContext.Provider>
132
476
  );
133
477
  }
@@ -43,11 +43,13 @@ export function Button({
43
43
  }: ButtonProps) {
44
44
  const [focused, setFocused] = useState(false);
45
45
 
46
- const selected = selectedItem && item.value === selectedItem?.value;
46
+ const selected = selectedItem && item?.value === selectedItem?.value;
47
47
 
48
48
  const itemIconPropsAssets = useMemo<ItemIconProps["asset"]>(
49
- () => configuration[item.asset] ?? item.asset ?? defaultItemIcon,
50
- [item.asset, defaultItemIcon]
49
+ () =>
50
+ (item ? (configuration[item.asset] ?? item.asset) : undefined) ??
51
+ defaultItemIcon,
52
+ [item?.asset, defaultItemIcon]
51
53
  );
52
54
 
53
55
  const selectedItemIconPropsAssets = useMemo<ItemIconProps["asset"]>(
@@ -4,6 +4,7 @@ import { Title, TitleProps } from "./Title";
4
4
  import { CloseButton, CloseButtonProps } from "./CloseButton";
5
5
  import { getCloseButtonProps, getTitleProps } from "./utils";
6
6
  import { PluginConfiguration } from "../index";
7
+ import { useModalBlocksStyle } from "../ModalBlocksStyleContext";
7
8
 
8
9
  type Props = {
9
10
  dismiss: () => void;
@@ -28,6 +29,14 @@ const styles = StyleSheet.create({
28
29
 
29
30
  export function ModalHeader(props: Props) {
30
31
  const { configuration, dismiss, onLayout, summary, title } = props;
32
+ const modalBlocksStyle = useModalBlocksStyle();
33
+ const headerBlock = modalBlocksStyle?.header;
34
+
35
+ const containerStyle = useMemo(() => {
36
+ const bg = headerBlock?.container?.default?.backgroundColor;
37
+
38
+ return [styles.container, bg ? { backgroundColor: bg } : null];
39
+ }, [headerBlock?.container?.default?.backgroundColor]);
31
40
 
32
41
  const closeButtonProps = useMemo<CloseButtonProps>(
33
42
  () => ({
@@ -37,13 +46,17 @@ export function ModalHeader(props: Props) {
37
46
  [configuration, dismiss]
38
47
  );
39
48
 
40
- const titleProps = useMemo<TitleProps>(
41
- () => ({
42
- ...getTitleProps(configuration, "title"),
49
+ const titleProps = useMemo<TitleProps>(() => {
50
+ const baseProps = getTitleProps(configuration, "title");
51
+ const titleOverride = headerBlock?.title?.default;
52
+
53
+ return {
54
+ ...baseProps,
43
55
  text: title,
44
- }),
45
- [configuration, title]
46
- );
56
+ ...(titleOverride?.fontColor ? { color: titleOverride.fontColor } : {}),
57
+ ...(titleOverride?.fontSize ? { fontSize: titleOverride.fontSize } : {}),
58
+ };
59
+ }, [configuration, title, headerBlock?.title?.default]);
47
60
 
48
61
  const summaryProps = useMemo<TitleProps>(
49
62
  () => ({
@@ -59,7 +72,7 @@ export function ModalHeader(props: Props) {
59
72
  configuration.modal_bottom_sheet_close_margin_right;
60
73
 
61
74
  return (
62
- <View onLayout={onLayout} style={styles.container}>
75
+ <View onLayout={onLayout} style={containerStyle}>
63
76
  <View style={styles.flex}>
64
77
  {title ? <Title {...titleProps} /> : null}
65
78
  {summary ? <Title {...summaryProps} /> : null}
@@ -18,7 +18,7 @@ export function getCloseButtonProps(config: PluginConfiguration) {
18
18
  return {
19
19
  asset: modal_bottom_sheet_close_asset,
20
20
  assetFocused: modal_bottom_sheet_close_focus_asset,
21
- enabled: modal_bottom_sheet_close_switch,
21
+ enabled: modal_bottom_sheet_close_switch !== false,
22
22
  width: modal_bottom_sheet_close_width,
23
23
  height: modal_bottom_sheet_close_height,
24
24
  marginTop: modal_bottom_sheet_close_margin_top,
@@ -0,0 +1,10 @@
1
+ import React, { useContext } from "react";
2
+ import type { ModalBlocksConfig } from "@applicaster/zapp-react-native-utils/configurationUtils/modalBlocksParser";
3
+
4
+ export const ModalBlocksStyleContext = React.createContext<
5
+ ModalBlocksConfig | Record<string, any> | undefined
6
+ >(undefined);
7
+
8
+ export const useModalBlocksStyle = () => {
9
+ return useContext(ModalBlocksStyleContext);
10
+ };