@applicaster/zapp-react-native-ui-components 16.0.0-rc.8 → 16.0.0-rc.80

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 (131) 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/CellRendererResolver/index.ts +2 -2
  7. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  8. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  9. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  10. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  11. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  12. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +188 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
  20. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +90 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  22. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  23. package/Components/GeneralContentScreen/index.ts +2 -0
  24. package/Components/Layout/FullWidthRow.tsx +38 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  28. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  29. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  30. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  31. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  32. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  33. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  34. package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
  35. package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
  36. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  37. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  38. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  39. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  40. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  41. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  43. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  44. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  45. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  46. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  47. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  48. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  49. package/Components/MasterCell/dataAdapter.ts +15 -5
  50. package/Components/MasterCell/hooks/index.ts +2 -0
  51. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  52. package/Components/MasterCell/index.tsx +30 -5
  53. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  54. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  55. package/Components/MasterCell/utils/index.ts +85 -46
  56. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +54 -0
  57. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +21 -0
  58. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  59. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +58 -0
  60. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +314 -0
  61. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +395 -0
  62. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +763 -0
  63. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +653 -0
  64. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +874 -0
  65. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +89 -0
  66. package/Components/ModalComponent/AudioPlayer/Components/index.ts +11 -0
  67. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +199 -0
  68. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  69. package/Components/ModalComponent/BottomSheetModalContent.tsx +383 -42
  70. package/Components/ModalComponent/Button/index.tsx +5 -3
  71. package/Components/ModalComponent/Header/index.tsx +20 -7
  72. package/Components/ModalComponent/Header/utils.ts +1 -1
  73. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  74. package/Components/ModalComponent/SortableList.tsx +142 -0
  75. package/Components/ModalComponent/SortableList.web.tsx +34 -0
  76. package/Components/ModalComponent/TextInputContent.tsx +100 -0
  77. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  78. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +238 -0
  79. package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
  80. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +120 -0
  81. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  82. package/Components/ModalComponent/index.tsx +11 -0
  83. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +66 -0
  84. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  85. package/Components/ModalComponent/presets/index.ts +27 -0
  86. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  87. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  88. package/Components/ModalComponent/presets/types.ts +35 -0
  89. package/Components/ModalComponent/showTextInput.ts +43 -0
  90. package/Components/ModalComponent/utils.ts +76 -20
  91. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  92. package/Components/OfflineHandler/__tests__/index.test.tsx +78 -18
  93. package/Components/OfflineHandler/hooks.ts +123 -0
  94. package/Components/OfflineHandler/index.tsx +51 -73
  95. package/Components/OfflineHandler/utils/index.ts +1 -13
  96. package/Components/PlayerContainer/PlayerContainer.tsx +25 -17
  97. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  98. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
  99. package/Components/PreloaderWrapper/index.tsx +1 -1
  100. package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -2
  101. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +3 -15
  102. package/Components/Screen/index.tsx +8 -4
  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 +20 -3
  109. package/Components/TopCutoffOverlay/index.tsx +2 -2
  110. package/Components/Transitioner/Transitioner.tsx +28 -17
  111. package/Components/VideoLive/LiveImageManager.ts +1 -1
  112. package/Components/VideoModal/utils.ts +6 -1
  113. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  114. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  115. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  116. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  117. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  118. package/Helpers/index.js +0 -39
  119. package/Hooks/useEntryActionsExpander.ts +63 -0
  120. package/package.json +6 -5
  121. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  122. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  123. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  124. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  125. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  126. package/Components/Screen/utils.ts +0 -16
  127. package/Helpers/Analytics/index.js +0 -95
  128. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  129. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  130. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
  131. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { useEntryActionState } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
3
+ import {
4
+ isActionAvailableFor,
5
+ RegisteredAction,
6
+ } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
7
+
8
+ import { Button } from "../Button";
9
+
10
+ export type ActionButtonProps = {
11
+ entry: ZappEntry;
12
+ item: RegisteredAction;
13
+ configuration: any;
14
+ width: number;
15
+ };
16
+
17
+ type Props = ActionButtonProps & {
18
+ onPress?: (item: any) => void;
19
+ };
20
+
21
+ /**
22
+ * Unified action button for the modal bottom sheet.
23
+ *
24
+ * Every item is a `RegisteredAction` resolved from the registry (entry actions,
25
+ * registry actions and legacy context-provider actions alike), so the button
26
+ * simply reads `item.action` and drives it through `useEntryActionState`
27
+ * (backed by the action's observable / `addListener`).
28
+ */
29
+ export function ActionButton(props: Props) {
30
+ const { item, entry, onPress: onItemPress } = props;
31
+
32
+ const action = item?.action;
33
+
34
+ const { state, invokeAction } = useEntryActionState(action, entry);
35
+
36
+ const onPress = React.useCallback(
37
+ (_item) => {
38
+ invokeAction({
39
+ context: "toast",
40
+ dismiss: () => onItemPress?.(item),
41
+ });
42
+ },
43
+ [invokeAction, item, onItemPress]
44
+ );
45
+
46
+ if (!isActionAvailableFor(action, entry as ZappEntry)) return null;
47
+
48
+ return (
49
+ <Button {...props} item={state} onPress={onPress} label={state?.label} />
50
+ );
51
+ }
52
+
53
+ // Explicitly set display name for the component
54
+ ActionButton.displayName = "ActionButton";
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+
3
+ import { ActionButton, ActionButtonProps } from "./ActionButton";
4
+
5
+ /**
6
+ * Binds an entry to the sheet's row button.
7
+ *
8
+ * `BottomSheetModalContent` renders `buttonComponent` per item and does not
9
+ * know about entries, so the entry is closed over here instead.
10
+ */
11
+ export const boundActionButton = (entry: ZappEntry | ZappFeed) => {
12
+ const ActionButtonWithEntry = (props: ActionButtonProps) => (
13
+ <ActionButton {...props} entry={entry as ZappEntry} />
14
+ );
15
+
16
+ ActionButtonWithEntry.displayName = "ActionButtonWithEntry";
17
+
18
+ return ActionButtonWithEntry;
19
+ };
20
+
21
+ boundActionButton.displayName = "boundActionButton";
@@ -0,0 +1,9 @@
1
+ export { ActionButton } from "./ActionButton";
2
+
3
+ export type { ActionButtonProps } from "./ActionButton";
4
+
5
+ export { boundActionButton } from "./boundActionButton";
6
+
7
+ export { openActionsBottomSheet } from "./openActionsBottomSheet";
8
+
9
+ export type { OpenActionsBottomSheetArgs } from "./openActionsBottomSheet";
@@ -0,0 +1,58 @@
1
+ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
2
+ import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
3
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
4
+ import { RegisteredAction } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
5
+
6
+ import { boundActionButton } from "./boundActionButton";
7
+
8
+ const { log_warning } = createLogger({
9
+ subsystem: "ActionsBottomSheet",
10
+ category: "Presentation",
11
+ });
12
+
13
+ export type OpenActionsBottomSheetArgs = {
14
+ /** Entry the actions act on; bound into every row button. */
15
+ entry: ZappEntry | ZappFeed;
16
+ /** Already resolved and merged actions, in the order they should appear. */
17
+ actions: RegisteredAction[];
18
+ title?: string;
19
+ summary?: string;
20
+ };
21
+
22
+ /**
23
+ * Presents a list of resolved actions in the shared bottom sheet.
24
+ *
25
+ * This is the core entry point for "show these actions in a sheet": the
26
+ * open-modal-bottom-sheet plugin uses it for its configured list, and the
27
+ * player overflow uses it for the actions that did not fit on the overlay.
28
+ * Neither has to know about the other, and neither needs the plugin installed.
29
+ */
30
+ export function openActionsBottomSheet({
31
+ entry,
32
+ actions,
33
+ title,
34
+ summary,
35
+ }: OpenActionsBottomSheetArgs): void {
36
+ if (!actions?.length) {
37
+ // Every action resolved away - unavailable for this entry, or not
38
+ // invokable. An empty sheet looks broken, so say why and stay put.
39
+ log_warning(
40
+ "openActionsBottomSheet: nothing to present - not opening the sheet",
41
+ { entryId: (entry as ZappEntry)?.id }
42
+ );
43
+
44
+ return;
45
+ }
46
+
47
+ openBottomSheetModal({
48
+ modalBottomSheetContentProps: {
49
+ // TODO: should be optional - the button handles its own tap, but
50
+ // BottomSheetModalContent still requires an onPress.
51
+ onPress: noop,
52
+ items: actions,
53
+ buttonComponent: boundActionButton(entry),
54
+ title,
55
+ summary,
56
+ },
57
+ });
58
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Modal Building Block — Action
3
+ *
4
+ * Single-file Expo Snack demo for the Action list item building block spec.
5
+ * Three forms (text only / leading icon + text / leading + text + trailing)
6
+ * are driven by data — no separate variants.
7
+ *
8
+ * Green (configurable): background, title, trailingIcon.chevronAsset
9
+ * White (hardcoded): container padding/gutter, icon size, leading icon assets,
10
+ * leading/trailing visibility logic from data
11
+ */
12
+
13
+ import React from "react";
14
+ import {
15
+ Image,
16
+ Platform,
17
+ Pressable,
18
+ StyleSheet,
19
+ Text,
20
+ View,
21
+ } from "react-native";
22
+
23
+ const styles = StyleSheet.create({
24
+ row: {
25
+ flexDirection: "row",
26
+ alignItems: "center",
27
+ },
28
+ });
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Configuration (green / editable fields from spec)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export type TextTransform =
35
+ | "default"
36
+ | "lowercase"
37
+ | "uppercase"
38
+ | "capitalize";
39
+
40
+ export type BackgroundConfiguration = {
41
+ defaultColor: string;
42
+ focusedColor: string;
43
+ cornerRadius: number;
44
+ };
45
+
46
+ export type TitleConfiguration = {
47
+ fontColor: string;
48
+ focusedFontColor: string;
49
+ iosFontFamily: string;
50
+ androidFontFamily: string;
51
+ fontSize: number;
52
+ lineHeight: number;
53
+ iosLetterSpacing: number;
54
+ androidLetterSpacing: number;
55
+ textTransform: TextTransform;
56
+ };
57
+
58
+ export type TrailingIconConfiguration = {
59
+ chevronAsset?: string;
60
+ };
61
+
62
+ export type ActionConfiguration = {
63
+ background: BackgroundConfiguration;
64
+ title: TitleConfiguration;
65
+ trailingIcon: TrailingIconConfiguration;
66
+ };
67
+
68
+ export const DEFAULT_BACKGROUND_CONFIGURATION: BackgroundConfiguration = {
69
+ defaultColor: "transparent",
70
+ focusedColor: "rgba(62, 62, 62, 1)",
71
+ cornerRadius: 12,
72
+ };
73
+
74
+ export const DEFAULT_TITLE_CONFIGURATION: TitleConfiguration = {
75
+ fontColor: "#FFFFFF",
76
+ focusedFontColor: "#FFFFFF",
77
+ iosFontFamily: "SFProText-Semibold",
78
+ androidFontFamily: "Roboto-Medium",
79
+ fontSize: 15,
80
+ lineHeight: 24,
81
+ iosLetterSpacing: -0.2,
82
+ androidLetterSpacing: 0,
83
+ textTransform: "default",
84
+ };
85
+
86
+ export const DEFAULT_TRAILING_ICON_CONFIGURATION: TrailingIconConfiguration =
87
+ {};
88
+
89
+ export const DEFAULT_ACTION_CONFIGURATION: ActionConfiguration = {
90
+ background: DEFAULT_BACKGROUND_CONFIGURATION,
91
+ title: DEFAULT_TITLE_CONFIGURATION,
92
+ trailingIcon: DEFAULT_TRAILING_ICON_CONFIGURATION,
93
+ };
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Data (runtime — controls which elements are shown)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ export type LeadingActionType =
100
+ | "addToQueue"
101
+ | "addToPlaylist"
102
+ | "favorite"
103
+ | "share"
104
+ | "sleepTimer"
105
+ | "playbackSpeed";
106
+
107
+ export type ActionData = {
108
+ title: string;
109
+ /** When set, leading icon is shown (Enable: On). */
110
+ leadingActionType?: LeadingActionType;
111
+ /** When true, trailing chevron is shown (Enable: On). */
112
+ showTrailingChevron?: boolean;
113
+ };
114
+
115
+ export type AudioPlayerActionProps = {
116
+ configuration: ActionConfiguration;
117
+ data: ActionData;
118
+ focused?: boolean;
119
+ onPress?: () => void;
120
+ };
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Hardcoded spec values (white fields)
124
+ // ---------------------------------------------------------------------------
125
+
126
+ const CONTAINER_SPEC = {
127
+ paddingTop: 13,
128
+ paddingRight: 10,
129
+ paddingBottom: 13,
130
+ paddingLeft: 10,
131
+ horizontalGutter: 12,
132
+ };
133
+
134
+ const ICON_SPEC = {
135
+ width: 24,
136
+ height: 24,
137
+ };
138
+
139
+ const DEFAULT_CHEVRON_ASSET =
140
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAALElEQVR42mNgGAXUBlu2bPmPjIeWBeiGDy0Lhrbrh1/qGXqZaxSMglEw0gEA4c+IpUrpYX4AAAAASUVORK5CYII=";
141
+
142
+ /** Hardcoded: action type → leading icon asset (base64 PNG). */
143
+ const LEADING_ICON_ASSETS: Record<LeadingActionType, string> = {
144
+ addToQueue:
145
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAALElEQVR42mNgGAUjE/xHAzS3gFgweC0Y+nEwGslkBRFFwTj8LBjNyaNgmAMAJTf+EEEZcS0AAAAASUVORK5CYII=",
146
+ addToPlaylist:
147
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKUlEQVR42mNgGAVDBvxHA6MWjFpAvEGkglELKLdgNJmOWkA7C0YBzQAAivu+UKRR1ZIAAAAASUVORK5CYII=",
148
+ favorite:
149
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKklEQVR42mNgGAWjYBTgBP+xAGLkRi0YQhbgM4wqBo9aMHgsGAWjgCAAALyAZqh4yyZZAAAAAElFTkSuQmCC",
150
+ share:
151
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAK0lEQVR42mNgGAVDDvxHAqMWjFowUiz4TwQYgRYQYxBFlo1aMPAWjAK6AgD2g7pUUT7VeAAAAABJRU5ErkJggg==",
152
+ sleepTimer:
153
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAYUlEQVR42tWUSQ4AIAgD+f+n8eJRliIQ6MmEZTSxJZokvipdXgZpAbzOO17AglIXI7XULxmCoEMwRGuWam6AdRsN4IJYTRH4PIDkhxTA92zUPNBcFDDHaOVR0RJ2LXG9QgemFVK8tpo4PwAAAABJRU5ErkJggg==",
154
+ playbackSpeed:
155
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAK0lEQVR42mNgGAVDDvxHAqMWjFowUiz4TwQYgRYQYxBFlo1aMPAWjAK6AgD2g7pUUT7VeAAAAABJRU5ErkJggg==",
156
+ };
157
+
158
+ function getTextTransformStyle(
159
+ textTransform: TextTransform
160
+ ): "none" | "lowercase" | "uppercase" | "capitalize" {
161
+ return textTransform === "default" ? "none" : textTransform;
162
+ }
163
+
164
+ function shouldShowLeadingIcon(leadingActionType?: LeadingActionType) {
165
+ return !!leadingActionType;
166
+ }
167
+
168
+ function shouldShowTrailingIcon(showTrailingChevron?: boolean) {
169
+ return !!showTrailingChevron;
170
+ }
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Inner components
174
+ // ---------------------------------------------------------------------------
175
+
176
+ type ActionContainerProps = {
177
+ configuration: BackgroundConfiguration;
178
+ focused: boolean;
179
+ children: React.ReactNode;
180
+ onPress?: () => void;
181
+ };
182
+
183
+ function ActionContainer({
184
+ configuration,
185
+ focused,
186
+ children,
187
+ onPress,
188
+ }: ActionContainerProps) {
189
+ return (
190
+ <Pressable
191
+ onPress={onPress}
192
+ style={({ pressed }) => ({
193
+ backgroundColor:
194
+ pressed || focused
195
+ ? configuration.focusedColor
196
+ : configuration.defaultColor,
197
+ borderRadius: configuration.cornerRadius,
198
+ paddingTop: CONTAINER_SPEC.paddingTop,
199
+ paddingRight: CONTAINER_SPEC.paddingRight,
200
+ paddingBottom: CONTAINER_SPEC.paddingBottom,
201
+ paddingLeft: CONTAINER_SPEC.paddingLeft,
202
+ })}
203
+ >
204
+ {children}
205
+ </Pressable>
206
+ );
207
+ }
208
+
209
+ type ActionTitleProps = {
210
+ title: string;
211
+ configuration: TitleConfiguration;
212
+ focused: boolean;
213
+ };
214
+
215
+ function ActionTitle({ title, configuration, focused }: ActionTitleProps) {
216
+ const letterSpacing =
217
+ Platform.OS === "ios"
218
+ ? configuration.iosLetterSpacing
219
+ : configuration.androidLetterSpacing;
220
+
221
+ return (
222
+ <Text
223
+ style={{
224
+ flex: 1,
225
+ color: focused
226
+ ? configuration.focusedFontColor
227
+ : configuration.fontColor,
228
+ fontSize: configuration.fontSize,
229
+ lineHeight: configuration.lineHeight,
230
+ letterSpacing,
231
+ fontFamily: Platform.select({
232
+ ios: configuration.iosFontFamily,
233
+ android: configuration.androidFontFamily,
234
+ default: configuration.androidFontFamily,
235
+ }),
236
+ textTransform: getTextTransformStyle(configuration.textTransform),
237
+ }}
238
+ numberOfLines={1}
239
+ >
240
+ {title}
241
+ </Text>
242
+ );
243
+ }
244
+
245
+ type ActionLeadingIconProps = {
246
+ actionType: LeadingActionType;
247
+ };
248
+
249
+ function ActionLeadingIcon({ actionType }: ActionLeadingIconProps) {
250
+ return (
251
+ <Image
252
+ source={{ uri: LEADING_ICON_ASSETS[actionType] }}
253
+ style={{ width: ICON_SPEC.width, height: ICON_SPEC.height }}
254
+ resizeMode="contain"
255
+ />
256
+ );
257
+ }
258
+
259
+ type ActionTrailingIconProps = {
260
+ configuration: TrailingIconConfiguration;
261
+ };
262
+
263
+ function ActionTrailingIcon({ configuration }: ActionTrailingIconProps) {
264
+ const asset = configuration.chevronAsset ?? DEFAULT_CHEVRON_ASSET;
265
+
266
+ return (
267
+ <Image
268
+ source={{ uri: asset }}
269
+ style={{ width: ICON_SPEC.width, height: ICON_SPEC.height }}
270
+ resizeMode="contain"
271
+ />
272
+ );
273
+ }
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // AudioPlayerAction
277
+ // ---------------------------------------------------------------------------
278
+
279
+ export function AudioPlayerAction({
280
+ configuration,
281
+ data,
282
+ focused = false,
283
+ onPress,
284
+ }: AudioPlayerActionProps) {
285
+ const { background, title, trailingIcon } = configuration;
286
+ const { title: label, leadingActionType, showTrailingChevron } = data;
287
+
288
+ const showLeading = shouldShowLeadingIcon(leadingActionType);
289
+ const showTrailing = shouldShowTrailingIcon(showTrailingChevron);
290
+
291
+ return (
292
+ <ActionContainer
293
+ configuration={background}
294
+ focused={focused}
295
+ onPress={onPress}
296
+ >
297
+ <View style={styles.row}>
298
+ {showLeading ? (
299
+ <>
300
+ <ActionLeadingIcon actionType={leadingActionType} />
301
+ <View style={{ width: CONTAINER_SPEC.horizontalGutter }} />
302
+ </>
303
+ ) : null}
304
+ <ActionTitle title={label} configuration={title} focused={focused} />
305
+ {showTrailing ? (
306
+ <>
307
+ <View style={{ width: CONTAINER_SPEC.horizontalGutter }} />
308
+ <ActionTrailingIcon configuration={trailingIcon} />
309
+ </>
310
+ ) : null}
311
+ </View>
312
+ </ActionContainer>
313
+ );
314
+ }