@applicaster/zapp-react-native-ui-components 16.0.0-rc.7 → 16.0.0-rc.70

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