@applicaster/zapp-react-native-ui-components 16.0.0-alpha.6593152532 → 16.0.0-alpha.6918106967

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