@applicaster/zapp-react-native-ui-components 16.0.0-alpha.1334507601 → 16.0.0-alpha.1444784746

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 (26) hide show
  1. package/Components/LinkHandler/LinkHandler.tsx +2 -7
  2. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
  3. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +2 -3
  4. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +64 -20
  5. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +139 -39
  6. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +147 -24
  7. package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
  8. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +108 -0
  9. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +148 -1
  10. package/Components/ModalComponent/BottomSheetModalContent.tsx +95 -9
  11. package/Components/ModalComponent/SortableList.tsx +2 -3
  12. package/Components/ModalComponent/TextInputContent.tsx +27 -4
  13. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +74 -1
  14. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +14 -1
  15. package/Components/ModalComponent/showTextInput.ts +11 -2
  16. package/Components/OfflineHandler/__tests__/index.test.tsx +109 -0
  17. package/Components/OfflineHandler/index.tsx +42 -4
  18. package/Components/PlayerContainer/PlayerContainer.tsx +8 -1
  19. package/Components/Transitioner/AnimationManager.js +1 -1
  20. package/Components/Transitioner/Scene.tsx +4 -3
  21. package/Components/Transitioner/Transitioner.tsx +71 -33
  22. package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
  23. package/Components/VideoLive/LiveImageManager.ts +8 -2
  24. package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
  25. package/assets/empty-Info.png +0 -0
  26. package/package.json +5 -5
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { View } from "react-native";
1
+ import React, { useEffect, useState } from "react";
2
+ import { StyleSheet, Text, View } from "react-native";
3
3
  import {
4
4
  AudioPlayerTrackItem,
5
5
  VARIANT_NOW_PLAYING,
@@ -7,32 +7,162 @@ import {
7
7
  } from "./Item";
8
8
  import { AudioPlayerHeader, VARIANT_SECTION_HEADER } from "./Header";
9
9
  import type { MenuItem } from "@applicaster/zapp-react-native-utils/modalState/types";
10
- import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
11
- import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
10
+ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
11
+ import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/const";
12
+ import type { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
13
+
14
+ const DEFAULT_EMPTY_MESSAGE = "Queued items will be added here";
15
+
16
+ const styles = StyleSheet.create({
17
+ divider: {
18
+ height: StyleSheet.hairlineWidth,
19
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
20
+ marginHorizontal: 20,
21
+ marginVertical: 8,
22
+ },
23
+ emptyMessage: {
24
+ paddingTop: 32,
25
+ paddingBottom: 32,
26
+ paddingHorizontal: 40,
27
+ textAlign: "center",
28
+ color: "#999999",
29
+ fontSize: 13,
30
+ lineHeight: 16,
31
+ },
32
+ });
33
+
34
+ const isPrimaryOrCastingPlayer = (player?: Player | null): boolean => {
35
+ if (!player) {
36
+ return false;
37
+ }
38
+
39
+ return (
40
+ player.playerRole === PlayerRole.Primary ||
41
+ player.playerRole === PlayerRole.Chromecast
42
+ );
43
+ };
44
+
45
+ const getTargetPlayer = (): Player | null => {
46
+ if (playerManager.isCasting()) {
47
+ return playerManager.getCastingReceiver() || null;
48
+ }
49
+
50
+ const active = playerManager.getActivePlayer();
51
+
52
+ return isPrimaryOrCastingPlayer(active) ? active : null;
53
+ };
12
54
 
13
55
  type NowPlayingHeaderSectionProps = {
14
56
  onClear?: () => void;
15
57
  nextUpTitle?: string;
16
58
  nowPlayingTitle?: string;
59
+ isEmpty?: boolean;
60
+ emptyMessage?: string;
61
+ clearLabel?: string;
17
62
  };
18
63
 
19
64
  export function NowPlayingHeaderSection({
20
65
  onClear,
21
66
  nextUpTitle = "Next Up",
22
67
  nowPlayingTitle = "Now Playing",
68
+ isEmpty = false,
69
+ emptyMessage = DEFAULT_EMPTY_MESSAGE,
70
+ clearLabel,
23
71
  }: NowPlayingHeaderSectionProps) {
24
- const player = usePlayer();
25
- const playerState = usePlayerState("queue-now-playing-section");
72
+ const [activePlayer, setActivePlayer] = useState<Player | null>(
73
+ getTargetPlayer
74
+ );
75
+
76
+ const [currentEntry, setCurrentEntry] = useState<ZappEntry | null>(
77
+ () => getTargetPlayer()?.entry || null
78
+ );
79
+
80
+ const [isPaused, setIsPaused] = useState<boolean>(() =>
81
+ Boolean(getTargetPlayer()?.isPaused())
82
+ );
83
+
84
+ useEffect(() => {
85
+ const handleActivePlayerUpdate = () => {
86
+ const player = getTargetPlayer();
87
+
88
+ if (player) {
89
+ setActivePlayer(player);
90
+
91
+ if (player.entry) {
92
+ setCurrentEntry(player.entry);
93
+ }
94
+
95
+ setIsPaused(Boolean(player.isPaused()));
96
+ }
97
+ };
98
+
99
+ handleActivePlayerUpdate();
100
+
101
+ const removeLifecycle = playerManager.addLifecycleListener({
102
+ onRegistered: (registeredPlayer) => {
103
+ if (isPrimaryOrCastingPlayer(registeredPlayer)) {
104
+ handleActivePlayerUpdate();
105
+ }
106
+ },
107
+ onCastingChanged: handleActivePlayerUpdate,
108
+ });
109
+
110
+ return () => {
111
+ removeLifecycle?.();
112
+ };
113
+ }, []);
114
+
115
+ useEffect(() => {
116
+ if (!activePlayer) {
117
+ return;
118
+ }
119
+
120
+ const syncPlayerState = () => {
121
+ if (activePlayer.entry) {
122
+ setCurrentEntry(activePlayer.entry);
123
+ }
124
+
125
+ setIsPaused(Boolean(activePlayer.isPaused()));
126
+ };
127
+
128
+ syncPlayerState();
129
+
130
+ const removeListener = activePlayer.addListener({
131
+ id: "queue-now-playing-header",
132
+ listener: {
133
+ onPlayerPause: () => setIsPaused(true),
134
+ onPlayerResume: () => setIsPaused(false),
135
+ onVideoLoad: syncPlayerState,
136
+ onLoad: syncPlayerState,
137
+ },
138
+ });
139
+
140
+ return () => {
141
+ removeListener?.();
142
+ };
143
+ }, [activePlayer]);
144
+
145
+ const handlePlayPause = () => {
146
+ if (!activePlayer) {
147
+ return;
148
+ }
149
+
150
+ if (isPaused) {
151
+ activePlayer.play();
152
+ setIsPaused(false);
153
+ } else {
154
+ activePlayer.pause();
155
+ setIsPaused(true);
156
+ }
157
+ };
26
158
 
27
159
  const renderNowPlayingTrack = () => {
28
- if (!player?.entry) {
160
+ if (!currentEntry) {
29
161
  return null;
30
162
  }
31
163
 
32
- const mediaGroup = player.entry.media_group;
33
-
34
- const imageGroup = Array.isArray(mediaGroup)
35
- ? mediaGroup.find((g: any) => g.type === "image")
164
+ const imageGroup = Array.isArray(currentEntry.media_group)
165
+ ? currentEntry.media_group.find((g: any) => g.type === "image")
36
166
  : null;
37
167
 
38
168
  const firstImage = Array.isArray(imageGroup?.media_item)
@@ -40,26 +170,18 @@ export function NowPlayingHeaderSection({
40
170
  : imageGroup?.media_item?.src;
41
171
 
42
172
  const nowPlayingItem: MenuItem = {
43
- id: String(player.entry.id ?? "now_playing"),
44
- title: (player.entry.title as string) || String(player.entry.id),
45
- summary: (player.entry.summary as string) ?? "",
173
+ id: String(currentEntry.id ?? "now_playing"),
174
+ title: (currentEntry.title as string) || String(currentEntry.id),
175
+ summary: (currentEntry.summary as string) ?? "",
46
176
  icon: firstImage,
47
177
  };
48
178
 
49
179
  const data = mapMenuItemToNowPlayingData(
50
180
  nowPlayingItem,
51
- playerState?.isPaused,
181
+ isPaused,
52
182
  nowPlayingTitle
53
183
  );
54
184
 
55
- const handlePlayPause = () => {
56
- if (playerState?.isPaused) {
57
- player?.play();
58
- } else {
59
- player?.pause();
60
- }
61
- };
62
-
63
185
  return (
64
186
  <AudioPlayerTrackItem
65
187
  configuration={VARIANT_NOW_PLAYING}
@@ -74,9 +196,10 @@ export function NowPlayingHeaderSection({
74
196
  {renderNowPlayingTrack()}
75
197
  <AudioPlayerHeader
76
198
  configuration={VARIANT_SECTION_HEADER}
77
- data={{ title: nextUpTitle }}
199
+ data={{ title: nextUpTitle, clearLabel }}
78
200
  onClear={onClear}
79
201
  />
202
+ {isEmpty ? <Text style={styles.emptyMessage}>{emptyMessage}</Text> : null}
80
203
  </View>
81
204
  );
82
205
  }
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react-native";
3
+ import { NowPlayingHeaderSection } from "../NowPlayingHeaderSection";
4
+
5
+ jest.mock(
6
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer",
7
+ () => ({
8
+ usePlayer: () => null,
9
+ })
10
+ );
11
+
12
+ jest.mock(
13
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState",
14
+ () => ({
15
+ usePlayerState: () => ({ isPaused: true }),
16
+ })
17
+ );
18
+
19
+ const EMPTY_COPY = "Queued items will be added here";
20
+
21
+ describe("NowPlayingHeaderSection empty queue", () => {
22
+ it("renders the empty message under Next Up when the list is empty", () => {
23
+ render(<NowPlayingHeaderSection isEmpty emptyMessage={EMPTY_COPY} />);
24
+
25
+ expect(screen.getByText("Next Up")).toBeTruthy();
26
+ expect(screen.getByText(EMPTY_COPY)).toBeTruthy();
27
+ });
28
+
29
+ it("does not render the empty message when the list has items", () => {
30
+ render(
31
+ <NowPlayingHeaderSection isEmpty={false} emptyMessage={EMPTY_COPY} />
32
+ );
33
+
34
+ expect(screen.queryByText(EMPTY_COPY)).toBeNull();
35
+ });
36
+ });
@@ -1,7 +1,12 @@
1
1
  import {
2
2
  mergeItemConfiguration,
3
3
  mergeButtonConfiguration,
4
+ mergeHeaderConfiguration,
4
5
  } from "../mergeStyles";
6
+ import {
7
+ VARIANT_SECTION_HEADER,
8
+ VARIANT_SINGLE_LINE_HEADER,
9
+ } from "../../Components/Header";
5
10
 
6
11
  const base = {
7
12
  background: {
@@ -246,3 +251,106 @@ describe("mergeButtonConfiguration background", () => {
246
251
  expect(merged.title.textTransform).toBe("uppercase");
247
252
  });
248
253
  });
254
+
255
+ describe("mergeHeaderConfiguration section header (Variant 3)", () => {
256
+ it("merges section title + Clear action button styles", () => {
257
+ const merged = mergeHeaderConfiguration(VARIANT_SECTION_HEADER, {
258
+ title: {
259
+ default: {
260
+ fontColor: "#ABCDEF",
261
+ fontSize: 15,
262
+ lineHeight: 36,
263
+ fontFamily: "CustomSection",
264
+ letterSpacing: -0.2,
265
+ textTransform: "uppercase",
266
+ alignment: "left",
267
+ },
268
+ },
269
+ actionButtons: {
270
+ default: {
271
+ backgroundColor: "rgba(10, 10, 10, 1)",
272
+ fontColor: "#EEEEEE",
273
+ fontSize: 12,
274
+ borderRadius: 20,
275
+ paddingTop: 8,
276
+ paddingRight: 12,
277
+ paddingBottom: 8,
278
+ paddingLeft: 12,
279
+ },
280
+ focused: {
281
+ backgroundColor: "rgba(20, 20, 20, 1)",
282
+ fontColor: "#FFDD00",
283
+ },
284
+ },
285
+ });
286
+
287
+ expect(merged.title.fontColor).toBe("#ABCDEF");
288
+ expect(merged.title.fontSize).toBe(15);
289
+ expect(merged.title.lineHeight).toBe(36);
290
+ expect(merged.title.iosFontFamily).toBe("CustomSection");
291
+ expect(merged.title.androidFontFamily).toBe("CustomSection");
292
+ expect(merged.title.iosLetterSpacing).toBe(-0.2);
293
+ expect(merged.title.textTransform).toBe("uppercase");
294
+
295
+ expect(merged.actionButtons.defaultBackgroundColor).toBe(
296
+ "rgba(10, 10, 10, 1)"
297
+ );
298
+
299
+ expect(merged.actionButtons.focusedBackgroundColor).toBe(
300
+ "rgba(20, 20, 20, 1)"
301
+ );
302
+
303
+ expect(merged.actionButtons.fontColor).toBe("#EEEEEE");
304
+ expect(merged.actionButtons.focusedFontColor).toBe("#FFDD00");
305
+ expect(merged.actionButtons.fontSize).toBe(12);
306
+ expect(merged.actionButtons.cornerRadius).toBe(20);
307
+ expect(merged.actionButtons.paddingTop).toBe(8);
308
+ expect(merged.actionButtons.paddingLeft).toBe(12);
309
+ });
310
+
311
+ it("uses title.default when only platform font and letter spacing are set", () => {
312
+ const merged = mergeHeaderConfiguration(VARIANT_SECTION_HEADER, {
313
+ title: {
314
+ default: {
315
+ iosFontFamily: "SFProText-Semibold",
316
+ androidFontFamily: "Roboto-Medium",
317
+ iosLetterSpacing: -0.4,
318
+ androidLetterSpacing: 0,
319
+ },
320
+ },
321
+ });
322
+
323
+ expect(merged.title.iosFontFamily).toBe("SFProText-Semibold");
324
+ expect(merged.title.androidFontFamily).toBe("Roboto-Medium");
325
+ expect(merged.title.iosLetterSpacing).toBe(-0.4);
326
+ expect(merged.title.androidLetterSpacing).toBe(0);
327
+ expect(merged.title.fontSize).toBe(VARIANT_SECTION_HEADER.title.fontSize);
328
+ });
329
+
330
+ it("does not change section defaults when block is empty", () => {
331
+ const merged = mergeHeaderConfiguration(VARIANT_SECTION_HEADER, {
332
+ title: { default: {} },
333
+ actionButtons: { default: {} },
334
+ });
335
+
336
+ expect(merged.title.fontSize).toBe(VARIANT_SECTION_HEADER.title.fontSize);
337
+
338
+ expect(merged.actionButtons.defaultBackgroundColor).toBe(
339
+ VARIANT_SECTION_HEADER.actionButtons.defaultBackgroundColor
340
+ );
341
+ });
342
+
343
+ it("leaves single-line header title size untouched when only actionButtons set", () => {
344
+ const merged = mergeHeaderConfiguration(VARIANT_SINGLE_LINE_HEADER, {
345
+ actionButtons: {
346
+ default: { backgroundColor: "#123456" },
347
+ },
348
+ });
349
+
350
+ expect(merged.title.fontSize).toBe(
351
+ VARIANT_SINGLE_LINE_HEADER.title.fontSize
352
+ );
353
+
354
+ expect(merged.actionButtons.defaultBackgroundColor).toBe("#123456");
355
+ });
356
+ });
@@ -9,6 +9,7 @@ import {
9
9
  } from "../Components/Item";
10
10
  import {
11
11
  TitleConfiguration as HeaderTitleConfiguration,
12
+ ActionButtonsStyleConfiguration as HeaderActionButtonsStyleConfiguration,
12
13
  HeaderConfiguration,
13
14
  } from "../Components/Header";
14
15
  import {
@@ -210,6 +211,122 @@ function mergeHeaderTitle(
210
211
  ...base,
211
212
  ...(override.fontColor ? { fontColor: override.fontColor } : {}),
212
213
  ...(override.fontSize !== undefined ? { fontSize: override.fontSize } : {}),
214
+ ...(override.lineHeight !== undefined
215
+ ? { lineHeight: override.lineHeight }
216
+ : {}),
217
+ ...(typeof override.fontFamily === "string"
218
+ ? {
219
+ iosFontFamily: override.fontFamily,
220
+ androidFontFamily: override.fontFamily,
221
+ }
222
+ : {}),
223
+ ...(override.iosFontFamily
224
+ ? { iosFontFamily: override.iosFontFamily }
225
+ : {}),
226
+ ...(override.androidFontFamily
227
+ ? { androidFontFamily: override.androidFontFamily }
228
+ : {}),
229
+ ...(override.letterSpacing !== undefined
230
+ ? {
231
+ iosLetterSpacing: override.letterSpacing,
232
+ androidLetterSpacing: override.letterSpacing,
233
+ }
234
+ : {}),
235
+ ...(override.iosLetterSpacing !== undefined
236
+ ? { iosLetterSpacing: override.iosLetterSpacing }
237
+ : {}),
238
+ ...(override.androidLetterSpacing !== undefined
239
+ ? { androidLetterSpacing: override.androidLetterSpacing }
240
+ : {}),
241
+ ...(override.textTransform
242
+ ? { textTransform: override.textTransform }
243
+ : {}),
244
+ ...(override.alignment ? { alignment: override.alignment } : {}),
245
+ };
246
+ }
247
+
248
+ function mergeHeaderActionButtons(
249
+ base: HeaderActionButtonsStyleConfiguration,
250
+ defaultOverride?: Record<string, any>,
251
+ focusedOverride?: Record<string, any>
252
+ ): HeaderActionButtonsStyleConfiguration {
253
+ if (!defaultOverride && !focusedOverride) return base;
254
+
255
+ const focusedBackgroundColor =
256
+ focusedOverride?.backgroundColor ??
257
+ defaultOverride?.focusBackgroundColor ??
258
+ defaultOverride?.focusedBackgroundColor;
259
+
260
+ const focusedFontColor =
261
+ focusedOverride?.fontColor ??
262
+ defaultOverride?.focusFontColor ??
263
+ defaultOverride?.focusedFontColor;
264
+
265
+ return {
266
+ ...base,
267
+ ...(defaultOverride?.backgroundColor
268
+ ? { defaultBackgroundColor: defaultOverride.backgroundColor }
269
+ : {}),
270
+ ...(focusedBackgroundColor != null && focusedBackgroundColor !== ""
271
+ ? { focusedBackgroundColor }
272
+ : {}),
273
+ ...(defaultOverride?.fontColor
274
+ ? { fontColor: defaultOverride.fontColor }
275
+ : {}),
276
+ ...(focusedFontColor != null && focusedFontColor !== ""
277
+ ? { focusedFontColor }
278
+ : {}),
279
+ ...(defaultOverride?.fontSize !== undefined
280
+ ? { fontSize: defaultOverride.fontSize }
281
+ : {}),
282
+ ...(defaultOverride?.lineHeight !== undefined
283
+ ? { lineHeight: defaultOverride.lineHeight }
284
+ : {}),
285
+ ...(typeof defaultOverride?.fontFamily === "string"
286
+ ? {
287
+ iosFontFamily: defaultOverride.fontFamily,
288
+ androidFontFamily: defaultOverride.fontFamily,
289
+ }
290
+ : {}),
291
+ ...(defaultOverride?.iosFontFamily
292
+ ? { iosFontFamily: defaultOverride.iosFontFamily }
293
+ : {}),
294
+ ...(defaultOverride?.androidFontFamily
295
+ ? { androidFontFamily: defaultOverride.androidFontFamily }
296
+ : {}),
297
+ ...(defaultOverride?.letterSpacing !== undefined
298
+ ? {
299
+ iosLetterSpacing: defaultOverride.letterSpacing,
300
+ androidLetterSpacing: defaultOverride.letterSpacing,
301
+ }
302
+ : {}),
303
+ ...(defaultOverride?.iosLetterSpacing !== undefined
304
+ ? { iosLetterSpacing: defaultOverride.iosLetterSpacing }
305
+ : {}),
306
+ ...(defaultOverride?.androidLetterSpacing !== undefined
307
+ ? { androidLetterSpacing: defaultOverride.androidLetterSpacing }
308
+ : {}),
309
+ ...(defaultOverride?.textTransform
310
+ ? { textTransform: defaultOverride.textTransform }
311
+ : {}),
312
+ ...(defaultOverride?.borderRadius !== undefined
313
+ ? { cornerRadius: defaultOverride.borderRadius }
314
+ : {}),
315
+ ...(defaultOverride?.cornerRadius !== undefined
316
+ ? { cornerRadius: defaultOverride.cornerRadius }
317
+ : {}),
318
+ ...(defaultOverride?.paddingTop !== undefined
319
+ ? { paddingTop: defaultOverride.paddingTop }
320
+ : {}),
321
+ ...(defaultOverride?.paddingRight !== undefined
322
+ ? { paddingRight: defaultOverride.paddingRight }
323
+ : {}),
324
+ ...(defaultOverride?.paddingBottom !== undefined
325
+ ? { paddingBottom: defaultOverride.paddingBottom }
326
+ : {}),
327
+ ...(defaultOverride?.paddingLeft !== undefined
328
+ ? { paddingLeft: defaultOverride.paddingLeft }
329
+ : {}),
213
330
  };
214
331
  }
215
332
 
@@ -221,9 +338,39 @@ export function mergeHeaderConfiguration(
221
338
 
222
339
  const bgOverride = headerBlock.container?.default?.backgroundColor;
223
340
 
341
+ const actionButtonsDefault =
342
+ headerBlock.actionButtons?.default || headerBlock.actionButtons;
343
+
344
+ const actionButtonsFocused =
345
+ headerBlock.actionButtons?.focused || headerBlock.actionButtons?.focus;
346
+
347
+ // Parser shape is title.default.*; hydrate() returns a flat title object.
348
+ const titleDefault = headerBlock.title?.default;
349
+
350
+ const titleOverride =
351
+ titleDefault &&
352
+ (titleDefault.fontColor !== undefined ||
353
+ titleDefault.fontSize !== undefined ||
354
+ titleDefault.lineHeight !== undefined ||
355
+ titleDefault.fontFamily ||
356
+ titleDefault.iosFontFamily ||
357
+ titleDefault.androidFontFamily ||
358
+ titleDefault.letterSpacing !== undefined ||
359
+ titleDefault.iosLetterSpacing !== undefined ||
360
+ titleDefault.androidLetterSpacing !== undefined ||
361
+ titleDefault.textTransform ||
362
+ titleDefault.alignment)
363
+ ? titleDefault
364
+ : headerBlock.title;
365
+
224
366
  return {
225
367
  ...base,
226
- title: mergeHeaderTitle(base.title, headerBlock.title?.default),
368
+ title: mergeHeaderTitle(base.title, titleOverride),
369
+ actionButtons: mergeHeaderActionButtons(
370
+ base.actionButtons,
371
+ actionButtonsDefault,
372
+ actionButtonsFocused
373
+ ),
227
374
  ...(bgOverride ? { backgroundColor: bgOverride } : {}),
228
375
  };
229
376
  }