@applicaster/zapp-react-native-ui-components 16.0.0-alpha.1453660071 → 16.0.0-alpha.1454930193

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/Cell.tsx +3 -6
  5. package/Components/Cell/TvOSCellComponent.tsx +0 -1
  6. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +15 -0
  7. package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
  8. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +64 -0
  9. package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
  10. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +18 -0
  11. package/Components/LinkHandler/LinkHandler.tsx +2 -7
  12. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
  13. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  14. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
  15. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
  16. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  17. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
  18. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  19. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  20. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  21. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  22. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
  23. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +2 -3
  24. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  25. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  26. package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
  27. package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
  28. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  29. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  30. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  31. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  32. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  33. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
  34. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  35. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  36. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  37. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  38. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  39. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  40. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
  41. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -2
  42. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  43. package/Components/MasterCell/hooks/index.ts +2 -0
  44. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  45. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
  46. package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
  47. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
  48. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  49. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
  50. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +392 -0
  51. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +772 -0
  52. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +728 -0
  53. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +1007 -0
  54. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
  55. package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +113 -0
  56. package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
  57. package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
  58. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +356 -0
  59. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +521 -0
  60. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  61. package/Components/ModalComponent/BottomSheetModalContent.tsx +505 -50
  62. package/Components/ModalComponent/Button/index.tsx +5 -3
  63. package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
  64. package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
  65. package/Components/ModalComponent/Header/index.tsx +20 -7
  66. package/Components/ModalComponent/Header/utils.ts +1 -1
  67. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  68. package/Components/ModalComponent/SortableList.tsx +156 -0
  69. package/Components/ModalComponent/SortableList.web.tsx +34 -0
  70. package/Components/ModalComponent/TextInputContent.tsx +135 -0
  71. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  72. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
  73. package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
  74. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
  75. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  76. package/Components/ModalComponent/index.tsx +18 -9
  77. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -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 +52 -0
  84. package/Components/ModalComponent/utils.ts +82 -21
  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 +111 -0
  88. package/Components/OfflineHandler/index.tsx +51 -73
  89. package/Components/OfflineHandler/utils/index.ts +1 -13
  90. package/Components/PlayerContainer/PlayerContainer.tsx +11 -7
  91. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  92. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +66 -13
  93. package/Components/PreloaderWrapper/index.tsx +1 -1
  94. package/Components/River/ComponentsMap/ComponentsMap.tsx +85 -59
  95. package/Components/River/River.tsx +4 -0
  96. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +140 -75
  97. package/Components/River/__tests__/componentsMap.test.js +8 -3
  98. package/Components/River/__tests__/resultCallback.test.tsx +47 -0
  99. package/Components/Screen/__tests__/Screen.test.tsx +24 -0
  100. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
  101. package/Components/Screen/index.tsx +5 -0
  102. package/Components/Screen/navigationHandler.ts +8 -8
  103. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  104. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  105. package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
  106. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
  107. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
  108. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
  109. package/Components/TopCutoffOverlay/index.tsx +2 -2
  110. package/Components/Transitioner/Transitioner.tsx +30 -20
  111. package/Components/Transitioner/__tests__/transitioner.test.js +83 -2
  112. package/Components/VideoLive/LiveImageManager.ts +9 -3
  113. package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
  114. package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
  115. package/Contexts/ModalNavigationContext/index.tsx +2 -2
  116. package/Hooks/useEntryActionsExpander.ts +63 -0
  117. package/assets/empty-Info.png +0 -0
  118. package/package.json +6 -5
  119. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  120. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  121. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  122. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  123. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  124. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -0,0 +1,772 @@
1
+ /**
2
+ * Modal Building Block — Header
3
+ *
4
+ * Green (configurable): title, closeButton (enable + asset), actionButtons styling
5
+ * (incl. Clear padding/radius for Variant 3 / section_header Studio keys)
6
+ * White (hardcoded): container gutters, action button slot enablement/actions,
7
+ * close dimensions, default action labels (overridable via HeaderData.clearLabel)
8
+ */
9
+
10
+ import React, { useCallback, useState } from "react";
11
+ import { Platform, Pressable, StyleSheet, Text, View } from "react-native";
12
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
13
+
14
+ const styles = StyleSheet.create({
15
+ row: {
16
+ flexDirection: "row",
17
+ alignItems: "center",
18
+ },
19
+ centerAlignedRow: {
20
+ flexDirection: "row",
21
+ alignItems: "center",
22
+ minHeight: 44,
23
+ position: "relative",
24
+ },
25
+ measureLayer: {
26
+ position: "absolute",
27
+ opacity: 0,
28
+ left: 0,
29
+ top: 0,
30
+ flexDirection: "row",
31
+ },
32
+ sideSlotFixed: {
33
+ justifyContent: "center",
34
+ minHeight: 44,
35
+ },
36
+ centerSpacer: {
37
+ flex: 1,
38
+ },
39
+ absoluteTitleLayer: {
40
+ ...StyleSheet.absoluteFillObject,
41
+ alignItems: "center",
42
+ justifyContent: "center",
43
+ },
44
+ closeButton: {
45
+ alignItems: "center",
46
+ justifyContent: "center",
47
+ },
48
+ });
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Configuration (green / editable fields from spec)
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export type HeaderActionType = "cancel" | "done" | "clear";
55
+
56
+ export type TextTransform =
57
+ | "default"
58
+ | "lowercase"
59
+ | "uppercase"
60
+ | "capitalize";
61
+
62
+ export type TitleAlignment = "left" | "center";
63
+
64
+ /** Selects hardcoded container + action layout (white fields). */
65
+ export type HeaderVariant = "singleLine" | "headerWithActions" | "section";
66
+
67
+ export type TitleConfiguration = {
68
+ fontColor: string;
69
+ iosFontFamily: string;
70
+ androidFontFamily: string;
71
+ fontSize: number;
72
+ lineHeight: number;
73
+ iosLetterSpacing: number;
74
+ androidLetterSpacing: number;
75
+ textTransform: TextTransform;
76
+ alignment: TitleAlignment;
77
+ };
78
+
79
+ export type CloseButtonConfiguration = {
80
+ enabled: boolean;
81
+ asset?: string;
82
+ };
83
+
84
+ export type ActionButtonsStyleConfiguration = {
85
+ defaultBackgroundColor: string;
86
+ focusedBackgroundColor: string;
87
+ fontColor: string;
88
+ focusedFontColor: string;
89
+ iosFontFamily: string;
90
+ androidFontFamily: string;
91
+ fontSize: number;
92
+ lineHeight: number;
93
+ iosLetterSpacing: number;
94
+ androidLetterSpacing: number;
95
+ textTransform: TextTransform;
96
+ /** Green Variant 3 fields — defaults match ACTION_BUTTON_LAYOUT_SPEC. */
97
+ paddingTop?: number;
98
+ paddingRight?: number;
99
+ paddingBottom?: number;
100
+ paddingLeft?: number;
101
+ cornerRadius?: number;
102
+ };
103
+
104
+ export type HeaderConfiguration = {
105
+ paddingTop?: number;
106
+ variant: HeaderVariant;
107
+ title: TitleConfiguration;
108
+ closeButton: CloseButtonConfiguration;
109
+ actionButtons: ActionButtonsStyleConfiguration;
110
+ };
111
+
112
+ export const DEFAULT_TITLE_CONFIGURATION: TitleConfiguration = {
113
+ fontColor: "#FFFFFF",
114
+ iosFontFamily: "SFProText-Bold",
115
+ androidFontFamily: "Roboto-Bold",
116
+ fontSize: 22,
117
+ lineHeight: 28,
118
+ iosLetterSpacing: -0.6,
119
+ androidLetterSpacing: 0,
120
+ textTransform: "default",
121
+ alignment: "left",
122
+ };
123
+
124
+ export const DEFAULT_CLOSE_BUTTON_CONFIGURATION: CloseButtonConfiguration = {
125
+ enabled: true,
126
+ };
127
+
128
+ export const DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION: ActionButtonsStyleConfiguration =
129
+ {
130
+ defaultBackgroundColor: "rgba(62, 62, 62, 1)",
131
+ focusedBackgroundColor: "rgba(78, 78, 78, 1)",
132
+ fontColor: "#FFFFFF",
133
+ focusedFontColor: "#FFFFFF",
134
+ iosFontFamily: "SFProText-Semibold",
135
+ androidFontFamily: "Roboto-Medium",
136
+ fontSize: 14,
137
+ lineHeight: 20,
138
+ iosLetterSpacing: 0,
139
+ androidLetterSpacing: 0,
140
+ textTransform: "default",
141
+ paddingTop: 6,
142
+ paddingRight: 10,
143
+ paddingBottom: 6,
144
+ paddingLeft: 10,
145
+ cornerRadius: 16,
146
+ };
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Data (runtime / localization — not styling config)
150
+ // ---------------------------------------------------------------------------
151
+
152
+ export type HeaderData = {
153
+ title: string;
154
+ /** Localization override for Clear; falls back to ACTION_LABELS.clear. */
155
+ clearLabel?: string;
156
+ };
157
+
158
+ export type AudioPlayerHeaderProps = {
159
+ configuration: HeaderConfiguration;
160
+ data: HeaderData;
161
+ focusedCloseButton?: boolean;
162
+ focusedLeadingAction?: boolean;
163
+ focusedTrailingAction?: boolean;
164
+ onClose?: () => void;
165
+ onBack?: () => void;
166
+ onCancel?: () => void;
167
+ onDone?: () => void;
168
+ onClear?: () => void;
169
+ };
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Hardcoded spec values (white fields)
173
+ // ---------------------------------------------------------------------------
174
+
175
+ type ContainerSpec = {
176
+ horizontalGutter: number;
177
+ paddingTop: number;
178
+ paddingRight: number;
179
+ paddingBottom: number;
180
+ paddingLeft: number;
181
+ };
182
+
183
+ type ActionSlotSpec = {
184
+ enabled: boolean;
185
+ actionType?: HeaderActionType;
186
+ };
187
+
188
+ type ActionButtonsBehaviorSpec = {
189
+ sectionEnabled: boolean;
190
+ leading: ActionSlotSpec;
191
+ trailing: ActionSlotSpec;
192
+ };
193
+
194
+ const CONTAINER_SPEC: Record<HeaderVariant, ContainerSpec> = {
195
+ singleLine: {
196
+ horizontalGutter: 16,
197
+ paddingTop: 24,
198
+ paddingRight: 20,
199
+ paddingBottom: 12,
200
+ paddingLeft: 20,
201
+ },
202
+ headerWithActions: {
203
+ horizontalGutter: 12,
204
+ paddingTop: 0,
205
+ paddingRight: 20,
206
+ paddingBottom: 8,
207
+ paddingLeft: 20,
208
+ },
209
+ section: {
210
+ horizontalGutter: 12,
211
+ paddingTop: 0,
212
+ paddingRight: 20,
213
+ paddingBottom: 0,
214
+ paddingLeft: 20,
215
+ },
216
+ };
217
+
218
+ const ACTION_BUTTONS_BEHAVIOR: Record<
219
+ HeaderVariant,
220
+ ActionButtonsBehaviorSpec
221
+ > = {
222
+ singleLine: {
223
+ sectionEnabled: false,
224
+ leading: { enabled: false },
225
+ trailing: { enabled: false },
226
+ },
227
+ headerWithActions: {
228
+ sectionEnabled: true,
229
+ leading: { enabled: true, actionType: "cancel" },
230
+ trailing: { enabled: true, actionType: "done" },
231
+ },
232
+ section: {
233
+ sectionEnabled: true,
234
+ leading: { enabled: false },
235
+ trailing: { enabled: true, actionType: "clear" },
236
+ },
237
+ };
238
+
239
+ const CLOSE_BUTTON_SPEC = {
240
+ width: 44,
241
+ height: 44,
242
+ paddingTop: 0,
243
+ paddingRight: 0,
244
+ paddingBottom: 0,
245
+ paddingLeft: 0,
246
+ };
247
+
248
+ const ACTION_BUTTON_LAYOUT_SPEC = {
249
+ paddingTop: 6,
250
+ paddingRight: 10,
251
+ paddingBottom: 6,
252
+ paddingLeft: 10,
253
+ cornerRadius: 16,
254
+ };
255
+
256
+ const ACTION_LABELS: Record<HeaderActionType, string> = {
257
+ cancel: "Cancel",
258
+ done: "Done",
259
+ clear: "Clear",
260
+ };
261
+
262
+ function getTextTransformStyle(
263
+ textTransform: TextTransform
264
+ ): "none" | "lowercase" | "uppercase" | "capitalize" {
265
+ return textTransform === "default" ? "none" : textTransform;
266
+ }
267
+
268
+ function shouldShowActionSlot(slot: ActionSlotSpec, sectionEnabled: boolean) {
269
+ return sectionEnabled && slot.enabled && !!slot.actionType;
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Inner components
274
+ // ---------------------------------------------------------------------------
275
+
276
+ type HeaderContainerProps = {
277
+ variant: HeaderVariant;
278
+ configuration: HeaderConfiguration;
279
+ children: React.ReactNode;
280
+ };
281
+
282
+ const TRANSPARENT = "transparent";
283
+
284
+ function HeaderContainer({
285
+ variant,
286
+ configuration,
287
+ children,
288
+ }: HeaderContainerProps) {
289
+ const container = CONTAINER_SPEC[variant];
290
+
291
+ const paddingTop =
292
+ configuration.paddingTop !== undefined
293
+ ? configuration.paddingTop
294
+ : container.paddingTop;
295
+
296
+ const backgroundColor = (configuration as any).backgroundColor || TRANSPARENT;
297
+
298
+ return (
299
+ <View
300
+ style={{
301
+ paddingTop: paddingTop,
302
+ paddingRight: container.paddingRight,
303
+ paddingBottom: container.paddingBottom,
304
+ paddingLeft: container.paddingLeft,
305
+ backgroundColor: backgroundColor,
306
+ }}
307
+ >
308
+ {children}
309
+ </View>
310
+ );
311
+ }
312
+
313
+ type HeaderTitleProps = {
314
+ title: string;
315
+ configuration: TitleConfiguration;
316
+ centered: boolean;
317
+ };
318
+
319
+ function HeaderTitle({ title, configuration, centered }: HeaderTitleProps) {
320
+ const { fontColor, fontSize } = configuration;
321
+
322
+ const letterSpacing = isApplePlatform()
323
+ ? configuration.iosLetterSpacing
324
+ : configuration.androidLetterSpacing;
325
+
326
+ return (
327
+ <Text
328
+ style={{
329
+ flex: centered ? undefined : 1,
330
+ color: fontColor,
331
+ fontSize: fontSize,
332
+ lineHeight: configuration.lineHeight,
333
+ letterSpacing,
334
+ fontFamily: Platform.select({
335
+ ios: configuration.iosFontFamily,
336
+ android: configuration.androidFontFamily,
337
+ default: configuration.androidFontFamily,
338
+ }),
339
+ textTransform: getTextTransformStyle(configuration.textTransform),
340
+ textAlign: centered ? "center" : configuration.alignment,
341
+ }}
342
+ numberOfLines={1}
343
+ >
344
+ {title}
345
+ </Text>
346
+ );
347
+ }
348
+
349
+ type HeaderCloseButtonProps = {
350
+ configuration: CloseButtonConfiguration;
351
+ focused?: boolean;
352
+ onPress?: () => void;
353
+ };
354
+
355
+ function HeaderCloseButton({
356
+ configuration,
357
+ focused = false,
358
+ onPress,
359
+ }: HeaderCloseButtonProps) {
360
+ if (!configuration.enabled) {
361
+ return null;
362
+ }
363
+
364
+ const buttonSize = 32;
365
+
366
+ return (
367
+ <Pressable
368
+ onPress={onPress}
369
+ style={({ pressed }) => [
370
+ styles.closeButton,
371
+ {
372
+ width: buttonSize,
373
+ height: buttonSize,
374
+ borderRadius: buttonSize / 2,
375
+ borderWidth: 1.5,
376
+ borderColor: "rgba(255, 255, 255, 0.4)",
377
+ opacity: pressed || focused ? 0.7 : 1,
378
+ justifyContent: "center",
379
+ alignItems: "center",
380
+ },
381
+ ]}
382
+ >
383
+ <View
384
+ style={{
385
+ width: 14,
386
+ height: 14,
387
+ justifyContent: "center",
388
+ alignItems: "center",
389
+ }}
390
+ >
391
+ <View
392
+ style={{
393
+ position: "absolute",
394
+ width: 14,
395
+ height: 1.5,
396
+ backgroundColor: "#FFFFFF",
397
+ transform: [{ rotate: "45deg" }],
398
+ borderRadius: 1,
399
+ }}
400
+ />
401
+ <View
402
+ style={{
403
+ position: "absolute",
404
+ width: 14,
405
+ height: 1.5,
406
+ backgroundColor: "#FFFFFF",
407
+ transform: [{ rotate: "-45deg" }],
408
+ borderRadius: 1,
409
+ }}
410
+ />
411
+ </View>
412
+ </Pressable>
413
+ );
414
+ }
415
+
416
+ type HeaderBackButtonProps = {
417
+ focused?: boolean;
418
+ onPress?: () => void;
419
+ };
420
+
421
+ function HeaderBackButton({ focused = false, onPress }: HeaderBackButtonProps) {
422
+ return (
423
+ <Pressable
424
+ onPress={onPress}
425
+ style={({ pressed }) => [
426
+ styles.closeButton,
427
+ {
428
+ width: CLOSE_BUTTON_SPEC.width,
429
+ height: CLOSE_BUTTON_SPEC.height,
430
+ marginTop: CLOSE_BUTTON_SPEC.paddingTop,
431
+ marginRight: CLOSE_BUTTON_SPEC.paddingRight,
432
+ marginBottom: CLOSE_BUTTON_SPEC.paddingBottom,
433
+ marginLeft: CLOSE_BUTTON_SPEC.paddingLeft,
434
+ borderRadius: CLOSE_BUTTON_SPEC.width / 2,
435
+ borderWidth: 1,
436
+ borderColor: "#FFFFFF",
437
+ opacity: pressed || focused ? 0.7 : 1,
438
+ },
439
+ ]}
440
+ >
441
+ <Text style={{ color: "#FFFFFF", fontSize: 20, fontWeight: "bold" }}>
442
+ ‹
443
+ </Text>
444
+ </Pressable>
445
+ );
446
+ }
447
+
448
+ type HeaderActionButtonProps = {
449
+ actionType: HeaderActionType;
450
+ configuration: ActionButtonsStyleConfiguration;
451
+ focused?: boolean;
452
+ label?: string;
453
+ onPress?: () => void;
454
+ };
455
+
456
+ function HeaderActionButton({
457
+ actionType,
458
+ configuration,
459
+ focused = false,
460
+ label,
461
+ onPress,
462
+ }: HeaderActionButtonProps) {
463
+ const paddingTop =
464
+ configuration.paddingTop ?? ACTION_BUTTON_LAYOUT_SPEC.paddingTop;
465
+
466
+ const paddingRight =
467
+ configuration.paddingRight ?? ACTION_BUTTON_LAYOUT_SPEC.paddingRight;
468
+
469
+ const paddingBottom =
470
+ configuration.paddingBottom ?? ACTION_BUTTON_LAYOUT_SPEC.paddingBottom;
471
+
472
+ const paddingLeft =
473
+ configuration.paddingLeft ?? ACTION_BUTTON_LAYOUT_SPEC.paddingLeft;
474
+
475
+ const cornerRadius =
476
+ configuration.cornerRadius ?? ACTION_BUTTON_LAYOUT_SPEC.cornerRadius;
477
+
478
+ return (
479
+ <Pressable
480
+ onPress={onPress}
481
+ style={({ pressed }) => ({
482
+ backgroundColor:
483
+ pressed || focused
484
+ ? configuration.focusedBackgroundColor
485
+ : configuration.defaultBackgroundColor,
486
+ borderRadius: cornerRadius,
487
+ paddingTop,
488
+ paddingRight,
489
+ paddingBottom,
490
+ paddingLeft,
491
+ })}
492
+ >
493
+ <Text
494
+ style={{
495
+ color: focused
496
+ ? configuration.focusedFontColor
497
+ : configuration.fontColor,
498
+ fontSize: configuration.fontSize,
499
+ lineHeight: configuration.lineHeight,
500
+ letterSpacing:
501
+ Platform.OS === "ios"
502
+ ? configuration.iosLetterSpacing
503
+ : configuration.androidLetterSpacing,
504
+ fontFamily: Platform.select({
505
+ ios: configuration.iosFontFamily,
506
+ android: configuration.androidFontFamily,
507
+ default: configuration.androidFontFamily,
508
+ }),
509
+ textTransform: getTextTransformStyle(configuration.textTransform),
510
+ }}
511
+ numberOfLines={1}
512
+ >
513
+ {label || ACTION_LABELS[actionType]}
514
+ </Text>
515
+ </Pressable>
516
+ );
517
+ }
518
+
519
+ type CenterAlignedHeaderLayoutProps = {
520
+ horizontalGutter: number;
521
+ title: TitleConfiguration;
522
+ titleText: string;
523
+ leadingContent: React.ReactNode;
524
+ trailingContent: React.ReactNode;
525
+ };
526
+
527
+ function CenterAlignedHeaderLayout({
528
+ horizontalGutter,
529
+ title,
530
+ titleText,
531
+ leadingContent,
532
+ trailingContent,
533
+ }: CenterAlignedHeaderLayoutProps) {
534
+ const [sideWidths, setSideWidths] = useState({ leading: 0, trailing: 0 });
535
+ const sideSlotWidth = Math.max(sideWidths.leading, sideWidths.trailing);
536
+
537
+ const updateSideWidth = useCallback(
538
+ (side: "leading" | "trailing", width: number) => {
539
+ setSideWidths((previous) => {
540
+ if (previous[side] === width) {
541
+ return previous;
542
+ }
543
+
544
+ return { ...previous, [side]: width };
545
+ });
546
+ },
547
+ []
548
+ );
549
+
550
+ return (
551
+ <View style={styles.centerAlignedRow}>
552
+ <View style={styles.measureLayer} pointerEvents="none">
553
+ {leadingContent ? (
554
+ <View
555
+ onLayout={(event) =>
556
+ updateSideWidth("leading", event.nativeEvent.layout.width)
557
+ }
558
+ >
559
+ {leadingContent}
560
+ </View>
561
+ ) : null}
562
+ {trailingContent ? (
563
+ <View
564
+ onLayout={(event) =>
565
+ updateSideWidth("trailing", event.nativeEvent.layout.width)
566
+ }
567
+ >
568
+ {trailingContent}
569
+ </View>
570
+ ) : null}
571
+ </View>
572
+
573
+ <View
574
+ style={[
575
+ styles.sideSlotFixed,
576
+ { width: sideSlotWidth, alignItems: "flex-start" },
577
+ ]}
578
+ >
579
+ {leadingContent}
580
+ </View>
581
+ <View style={{ width: horizontalGutter }} />
582
+ <View style={styles.centerSpacer} />
583
+ <View style={{ width: horizontalGutter }} />
584
+ <View
585
+ style={[
586
+ styles.sideSlotFixed,
587
+ { width: sideSlotWidth, alignItems: "flex-end" },
588
+ ]}
589
+ >
590
+ {trailingContent}
591
+ </View>
592
+
593
+ <View
594
+ pointerEvents="none"
595
+ style={[
596
+ styles.absoluteTitleLayer,
597
+ { paddingHorizontal: sideSlotWidth + horizontalGutter },
598
+ ]}
599
+ >
600
+ <HeaderTitle title={titleText} configuration={title} centered />
601
+ </View>
602
+ </View>
603
+ );
604
+ }
605
+
606
+ // ---------------------------------------------------------------------------
607
+ // AudioPlayerHeader
608
+ // ---------------------------------------------------------------------------
609
+
610
+ import { useModalBlocksStyle } from "../../ModalBlocksStyleContext";
611
+ import { mergeHeaderConfiguration } from "../utils/mergeStyles";
612
+
613
+ export function AudioPlayerHeader({
614
+ configuration,
615
+ data,
616
+ focusedCloseButton = false,
617
+ focusedLeadingAction = false,
618
+ focusedTrailingAction = false,
619
+ onClose,
620
+ onBack,
621
+ onCancel,
622
+ onDone,
623
+ onClear,
624
+ }: AudioPlayerHeaderProps) {
625
+ const modalBlocksStyle = useModalBlocksStyle();
626
+
627
+ // Next Up section header uses sectionHeader — never Theme modal `header`,
628
+ // so the Queue title styles do not collide with the section title.
629
+ const styleBlock =
630
+ configuration.variant === "section"
631
+ ? modalBlocksStyle?.sectionHeader
632
+ : modalBlocksStyle?.header;
633
+
634
+ const effectiveConfig = React.useMemo(() => {
635
+ return mergeHeaderConfiguration(configuration, styleBlock);
636
+ }, [configuration, styleBlock]);
637
+
638
+ const { variant, title, closeButton, actionButtons } = effectiveConfig;
639
+ const { title: titleText, clearLabel } = data;
640
+
641
+ const container = CONTAINER_SPEC[variant];
642
+ const behavior = ACTION_BUTTONS_BEHAVIOR[variant];
643
+ const isCenterAligned = title.alignment === "center";
644
+
645
+ const showLeadingAction = shouldShowActionSlot(
646
+ behavior.leading,
647
+ behavior.sectionEnabled
648
+ );
649
+
650
+ const showTrailingAction =
651
+ shouldShowActionSlot(behavior.trailing, behavior.sectionEnabled) &&
652
+ (behavior.trailing.actionType !== "clear" || !!onClear);
653
+
654
+ const actionLabelFor = (actionType?: HeaderActionType) => {
655
+ if (actionType === "clear" && clearLabel) {
656
+ return clearLabel;
657
+ }
658
+
659
+ return actionType ? ACTION_LABELS[actionType] : undefined;
660
+ };
661
+
662
+ const leadingContent = showLeadingAction ? (
663
+ <HeaderActionButton
664
+ actionType={behavior.leading.actionType as HeaderActionType}
665
+ configuration={actionButtons}
666
+ focused={focusedLeadingAction}
667
+ label={actionLabelFor(behavior.leading.actionType)}
668
+ onPress={onCancel}
669
+ />
670
+ ) : onBack ? (
671
+ <HeaderBackButton focused={focusedCloseButton} onPress={onBack} />
672
+ ) : null;
673
+
674
+ const trailingContent = showTrailingAction ? (
675
+ <HeaderActionButton
676
+ actionType={behavior.trailing.actionType as HeaderActionType}
677
+ configuration={actionButtons}
678
+ focused={focusedTrailingAction}
679
+ label={actionLabelFor(behavior.trailing.actionType)}
680
+ onPress={behavior.trailing.actionType === "done" ? onDone : onClear}
681
+ />
682
+ ) : closeButton.enabled && !isCenterAligned ? (
683
+ <HeaderCloseButton
684
+ configuration={closeButton}
685
+ focused={focusedCloseButton}
686
+ onPress={onClose}
687
+ />
688
+ ) : null;
689
+
690
+ if (isCenterAligned) {
691
+ return (
692
+ <HeaderContainer variant={variant} configuration={configuration}>
693
+ <CenterAlignedHeaderLayout
694
+ horizontalGutter={container.horizontalGutter}
695
+ title={title}
696
+ titleText={titleText}
697
+ leadingContent={leadingContent}
698
+ trailingContent={trailingContent}
699
+ />
700
+ </HeaderContainer>
701
+ );
702
+ }
703
+
704
+ return (
705
+ <HeaderContainer variant={variant} configuration={configuration}>
706
+ <View style={styles.row}>
707
+ {leadingContent ? (
708
+ <>
709
+ {leadingContent}
710
+ <View style={{ width: container.horizontalGutter }} />
711
+ </>
712
+ ) : null}
713
+ <HeaderTitle title={titleText} configuration={title} centered={false} />
714
+ {trailingContent ? (
715
+ <>
716
+ <View style={{ width: container.horizontalGutter }} />
717
+ {trailingContent}
718
+ </>
719
+ ) : null}
720
+ </View>
721
+ </HeaderContainer>
722
+ );
723
+ }
724
+
725
+ // ---------------------------------------------------------------------------
726
+ // Variant configurations (green defaults per PDF column)
727
+ // ---------------------------------------------------------------------------
728
+
729
+ /** Default / Variant 1 — Single Line Header */
730
+ export const VARIANT_SINGLE_LINE_HEADER: HeaderConfiguration = {
731
+ variant: "singleLine",
732
+ title: DEFAULT_TITLE_CONFIGURATION,
733
+ closeButton: DEFAULT_CLOSE_BUTTON_CONFIGURATION,
734
+ actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
735
+ };
736
+
737
+ /** Variant 2 — Header With Actions */
738
+ export const VARIANT_HEADER_WITH_ACTIONS: HeaderConfiguration = {
739
+ variant: "headerWithActions",
740
+ title: {
741
+ fontColor: "#FFFFFF",
742
+ iosFontFamily: "SFProText-Bold",
743
+ androidFontFamily: "Roboto-Bold",
744
+ fontSize: 22,
745
+ lineHeight: 28,
746
+ iosLetterSpacing: -0.6,
747
+ androidLetterSpacing: 0,
748
+ textTransform: "default",
749
+ alignment: "center",
750
+ },
751
+ closeButton: { enabled: false },
752
+ actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
753
+ paddingTop: 16,
754
+ };
755
+
756
+ /** Variant 3 — Section Header */
757
+ export const VARIANT_SECTION_HEADER: HeaderConfiguration = {
758
+ variant: "section",
759
+ title: {
760
+ fontColor: "#FFFFFF",
761
+ iosFontFamily: "SFProText-Bold",
762
+ androidFontFamily: "Roboto-Bold",
763
+ fontSize: 13,
764
+ lineHeight: 32,
765
+ iosLetterSpacing: -0.1,
766
+ androidLetterSpacing: 0,
767
+ textTransform: "default",
768
+ alignment: "left",
769
+ },
770
+ closeButton: { enabled: false },
771
+ actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
772
+ };