@applicaster/zapp-react-native-ui-components 15.1.13 → 15.1.15-alpha.4236143002

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 (40) hide show
  1. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +4 -1
  2. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
  3. package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
  4. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
  5. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  6. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
  7. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -87
  8. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +57 -74
  9. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +142 -107
  10. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +653 -0
  11. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +427 -159
  12. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +89 -0
  13. package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +66 -0
  14. package/Components/ModalComponent/AudioPlayer/Components/index.ts +8 -4
  15. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +230 -0
  16. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +332 -0
  17. package/Components/ModalComponent/BottomSheetModalContent.tsx +379 -61
  18. package/Components/ModalComponent/Button/index.tsx +5 -3
  19. package/Components/ModalComponent/Header/index.tsx +20 -7
  20. package/Components/ModalComponent/Header/utils.ts +1 -1
  21. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  22. package/Components/ModalComponent/SortableList.tsx +142 -0
  23. package/Components/ModalComponent/SortableList.web.tsx +102 -0
  24. package/Components/ModalComponent/TextInputContent.tsx +112 -0
  25. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +238 -0
  26. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +156 -0
  27. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  28. package/Components/ModalComponent/index.tsx +11 -0
  29. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
  30. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  31. package/Components/ModalComponent/presets/index.ts +27 -0
  32. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  33. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  34. package/Components/ModalComponent/presets/types.ts +35 -0
  35. package/Components/ModalComponent/showTextInput.ts +43 -0
  36. package/Components/ModalComponent/utils.ts +82 -21
  37. package/Components/VideoLive/LiveImageManager.ts +8 -2
  38. package/Hooks/useEntryActionsExpander.ts +63 -0
  39. package/package.json +6 -5
  40. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -12,7 +12,20 @@
12
12
  */
13
13
 
14
14
  import React from "react";
15
- import { Image, Platform, StyleSheet, Text, View } from "react-native";
15
+ import {
16
+ Image,
17
+ Platform,
18
+ Pressable,
19
+ StyleSheet,
20
+ Text,
21
+ View,
22
+ } from "react-native";
23
+ import {
24
+ MenuItem,
25
+ SelectionBehavior,
26
+ } from "@applicaster/zapp-react-native-utils/modalState/types";
27
+ import { useModalBlocksStyle } from "../../ModalBlocksStyleContext";
28
+ import { mergeItemConfiguration } from "../utils/mergeStyles";
16
29
 
17
30
  const styles = StyleSheet.create({
18
31
  row: {
@@ -27,25 +40,6 @@ const styles = StyleSheet.create({
27
40
  flexDirection: "row",
28
41
  alignItems: "center",
29
42
  },
30
- demoScreen: {
31
- flex: 1,
32
- backgroundColor: "#1C1C1E",
33
- paddingTop: 64,
34
- paddingHorizontal: 0,
35
- gap: 16,
36
- },
37
- demoHeading: {
38
- color: "#FFFFFF",
39
- fontSize: 20,
40
- fontWeight: "600",
41
- marginBottom: 8,
42
- paddingHorizontal: 16,
43
- },
44
- demoLabel: {
45
- color: "#999999",
46
- fontSize: 13,
47
- paddingHorizontal: 16,
48
- },
49
43
  });
50
44
 
51
45
  // ---------------------------------------------------------------------------
@@ -62,6 +56,10 @@ export type BackgroundConfiguration = {
62
56
  defaultColor: string;
63
57
  focusedColor: string;
64
58
  cornerRadius: number;
59
+ paddingTop?: number;
60
+ paddingRight?: number;
61
+ paddingBottom?: number;
62
+ paddingLeft?: number;
65
63
  };
66
64
 
67
65
  export type ImageConfiguration = {
@@ -88,6 +86,20 @@ export type NowPlayingAssetConfiguration = {
88
86
  asset?: string;
89
87
  };
90
88
 
89
+ export type CheckboxAssetsConfiguration = {
90
+ /** Unchecked multiSelect control (item.checkbox_asset). */
91
+ asset?: string;
92
+ /** Checked multiSelect control (item.checked_checkbox_asset). */
93
+ checkedAsset?: string;
94
+ };
95
+
96
+ export type ActionButtonAssetsConfiguration = {
97
+ /** Leading remove control on Edit Order rows. */
98
+ removeAsset?: string;
99
+ /** Trailing drag handle on Edit Order / queue rows. */
100
+ dragAsset?: string;
101
+ };
102
+
91
103
  export type ItemConfiguration = {
92
104
  background: BackgroundConfiguration;
93
105
  image: ImageConfiguration;
@@ -95,52 +107,10 @@ export type ItemConfiguration = {
95
107
  nowPlayingLabel: LabelConfiguration;
96
108
  textLabel1: LabelConfiguration;
97
109
  textLabel2: LabelConfiguration;
110
+ checkboxAssets?: CheckboxAssetsConfiguration;
111
+ actionButtonAssets?: ActionButtonAssetsConfiguration;
98
112
  };
99
113
 
100
- export interface Action {
101
- type: string;
102
- payload?: any;
103
- options?: any;
104
- }
105
-
106
- export interface Header {
107
- title: string;
108
- subtitle?: string;
109
- icon?: string;
110
- }
111
-
112
- export interface Content {
113
- title: string;
114
- stickyTitle?: boolean;
115
- items: MenuItem[];
116
- itemsUrl?: string;
117
- action?: Action;
118
- }
119
-
120
- export interface Menu {
121
- header?: Header;
122
- content: Content;
123
- closeButton?: boolean;
124
- }
125
-
126
- export interface PresentSubMenuAction extends Action {
127
- menu: Menu;
128
- }
129
-
130
- export interface SecondaryAction {
131
- action: Action | PresentSubMenuAction;
132
- icon?: string;
133
- }
134
-
135
- export interface MenuItem {
136
- title: string;
137
- summary?: string;
138
- icon?: string;
139
- isSelected?: boolean;
140
- action?: Action | PresentSubMenuAction;
141
- secondaryAction?: SecondaryAction;
142
- }
143
-
144
114
  export const DEFAULT_TEXT_LABEL_1_CONFIGURATION: LabelConfiguration = {
145
115
  fontColor: "#FFFFFF",
146
116
  focusedFontColor: "#FFFFFF",
@@ -191,14 +161,19 @@ export type ItemData = {
191
161
  leadingButton?: ItemButtonAction;
192
162
  /** Button 1 (right most) — hardcoded action decides icon. */
193
163
  trailingButton?: ItemButtonAction;
164
+ isSelected?: boolean;
194
165
  };
195
166
 
196
- export type ItemProps = {
167
+ export type AudioPlayerTrackItemProps = {
197
168
  configuration: ItemConfiguration;
198
169
  data: ItemData;
199
170
  focused?: boolean;
200
171
  focusedLeadingButton?: boolean;
201
172
  focusedTrailingButton?: boolean;
173
+ onPress?: () => void;
174
+ onLeadingButtonPress?: () => void;
175
+ onTrailingButtonPress?: () => void;
176
+ renderHandle?: (element: React.ReactElement) => React.ReactElement;
202
177
  };
203
178
 
204
179
  // ---------------------------------------------------------------------------
@@ -234,9 +209,9 @@ const CONTAINER_SPEC_LARGE_IMAGE: ContainerSpec = {
234
209
 
235
210
  const CONTAINER_SPEC_DEFAULT: ContainerSpec = {
236
211
  paddingTop: 8,
237
- paddingRight: 10,
212
+ paddingRight: 20,
238
213
  paddingBottom: 8,
239
- paddingLeft: 10,
214
+ paddingLeft: 20,
240
215
  horizontalGutter: 12,
241
216
  verticalGutter: 2,
242
217
  };
@@ -297,16 +272,6 @@ const DEFAULT_THUMB_ASSET =
297
272
  const DEFAULT_EQUALIZER_ASSET =
298
273
  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAI0lEQVR42mNgGNTgPxTQzwB0DVQ3gKCBowbQwYAhkBIHBAAA9TvPMeOFaLMAAAAASUVORK5CYII=";
299
274
 
300
- const BUTTON_ASSETS: Record<ItemButtonAction, string> = {
301
- playPause:
302
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAdklEQVR42u3ZwRUAERAEUfkn3TaF5TFTrSuCf3CgjZGg6ast7G8YaAlcB8JAj8F1MRR2G63CUNglNAqsRvmB1TAfsBoXcMABV4MFKEci4IADNrkAed2H80R66SHqvUvglh/ktoZcL5H7MHKBx/5xYH+RsP90t5t2UpF6+22a+AAAAABJRU5ErkJggg==",
303
- drag: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAIklEQVR42mNgGAWjYGiC/2SCwWPBaByMxsFoHIyCUTDYAAAqhk7AVXdZJwAAAABJRU5ErkJggg==",
304
- multiSelect:
305
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAWklEQVR42t2VUQ4AIAhCvf+lrQsULJJZbH7GM1cS0Uk5dd0Qqcz4GJSCSs0pSCkAHNoWBfkTgIxpgGK+ujyEKABqRGyn9FO1A+yfrf+qsGxTSx5YEs2WyU9oAKBJKg8Kfq4dAAAAAElFTkSuQmCC",
306
- remove:
307
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAUklEQVR42mNgGPbgPx5AVYOJlSPJcGqrJU8DKXooCVui9FIacXj1UyNl4DWDLhYgc8nA+INpeFgwtCOZ5vmALjmZ5mURXUpTutQHdKnRRgEMAACwjWWprU1B+AAAAABJRU5ErkJggg==",
308
- };
309
-
310
275
  function getTextTransformStyle(
311
276
  textTransform: TextTransform
312
277
  ): "none" | "lowercase" | "uppercase" | "capitalize" {
@@ -324,32 +289,49 @@ function resolveContainerSpec(image: ImageConfiguration): ContainerSpec {
324
289
  : CONTAINER_SPEC_DEFAULT;
325
290
  }
326
291
 
327
- function mapMenuItemToNowPlayingData(item: MenuItem): ItemData {
292
+ export function mapMenuItemToNowPlayingData(
293
+ item: MenuItem,
294
+ isPaused?: boolean
295
+ ): ItemData {
328
296
  return {
329
297
  imageUri: item.icon,
330
298
  textLabel1: item.title,
331
299
  textLabel2: item.summary ?? "",
332
300
  nowPlayingLabel: "Now Playing",
333
301
  trailingButton: "playPause",
302
+ isSelected: isPaused !== undefined ? !isPaused : item.isSelected,
334
303
  };
335
304
  }
336
305
 
337
- function mapMenuItemToQueuedData(item: MenuItem): ItemData {
306
+ export function mapMenuItemToQueuedData(item: MenuItem): ItemData {
338
307
  return {
339
308
  imageUri: item.icon,
340
309
  textLabel1: item.title,
341
310
  textLabel2: item.summary ?? "",
342
311
  leadingButton: "remove",
343
312
  trailingButton: "drag",
313
+ isSelected: item.isSelected,
344
314
  };
345
315
  }
346
316
 
347
- function mapMenuItemToPlaylistData(item: MenuItem): ItemData {
317
+ export function mapMenuItemToPlaylistData(
318
+ item: MenuItem,
319
+ behavior?: SelectionBehavior
320
+ ): ItemData {
321
+ const showSelectionWidget =
322
+ behavior?.selectMode !== "none" && item.trailingButton !== "none";
323
+
324
+ const summary =
325
+ typeof item.summary === "string" ? item.summary.trim() : item.summary;
326
+
348
327
  return {
349
328
  imageUri: item.icon,
350
329
  textLabel1: item.title,
351
- textLabel2: item.summary ?? "",
352
- trailingButton: "multiSelect",
330
+ textLabel2: summary || "",
331
+ trailingButton: showSelectionWidget
332
+ ? item.trailingButton || ("multiSelect" as ItemButtonAction)
333
+ : undefined,
334
+ isSelected: item.isSelected,
353
335
  };
354
336
  }
355
337
 
@@ -362,6 +344,7 @@ type ItemContainerProps = {
362
344
  background: BackgroundConfiguration;
363
345
  focused: boolean;
364
346
  children: React.ReactNode;
347
+ onPress?: () => void;
365
348
  };
366
349
 
367
350
  function ItemContainer({
@@ -369,23 +352,25 @@ function ItemContainer({
369
352
  background,
370
353
  focused,
371
354
  children,
355
+ onPress,
372
356
  }: ItemContainerProps) {
373
357
  const container = resolveContainerSpec(image);
374
358
  const bg = focused ? background.focusedColor : background.defaultColor;
375
359
 
376
360
  return (
377
- <View
378
- style={{
379
- backgroundColor: bg,
361
+ <Pressable
362
+ onPress={onPress}
363
+ style={({ pressed }) => ({
364
+ backgroundColor: pressed ? background.focusedColor : bg,
380
365
  borderRadius: background.cornerRadius,
381
- paddingTop: container.paddingTop,
382
- paddingRight: container.paddingRight,
383
- paddingBottom: container.paddingBottom,
384
- paddingLeft: container.paddingLeft,
385
- }}
366
+ paddingTop: background.paddingTop ?? container.paddingTop,
367
+ paddingRight: background.paddingRight ?? container.paddingRight,
368
+ paddingBottom: background.paddingBottom ?? container.paddingBottom,
369
+ paddingLeft: background.paddingLeft ?? container.paddingLeft,
370
+ })}
386
371
  >
387
372
  {children}
388
- </View>
373
+ </Pressable>
389
374
  );
390
375
  }
391
376
 
@@ -486,27 +471,272 @@ function NowPlayingRow({
486
471
  type ItemButtonProps = {
487
472
  action: ItemButtonAction;
488
473
  focused?: boolean;
474
+ onPress?: () => void;
475
+ isSelected?: boolean;
476
+ checkboxAssets?: CheckboxAssetsConfiguration;
477
+ actionButtonAssets?: ActionButtonAssetsConfiguration;
489
478
  };
490
479
 
491
- function ItemButton({ action, focused = false }: ItemButtonProps) {
480
+ function ItemButton({
481
+ action,
482
+ focused = false,
483
+ onPress,
484
+ isSelected = false,
485
+ checkboxAssets,
486
+ actionButtonAssets,
487
+ }: ItemButtonProps) {
492
488
  const layout = BUTTON_LAYOUT_SPEC[action];
493
489
 
490
+ if (!layout) {
491
+ return null;
492
+ }
493
+
494
+ const renderIcon = () => {
495
+ switch (action) {
496
+ case "playPause": {
497
+ const iconSize = layout.assetWidth;
498
+ const iconRadius = iconSize / 2;
499
+
500
+ return (
501
+ <View
502
+ style={{
503
+ width: iconSize,
504
+ height: iconSize,
505
+ borderRadius: iconRadius,
506
+ backgroundColor: "#FFFFFF",
507
+ justifyContent: "center",
508
+ alignItems: "center",
509
+ }}
510
+ >
511
+ {isSelected ? (
512
+ <View
513
+ style={{
514
+ flexDirection: "row",
515
+ justifyContent: "space-between",
516
+ width: 10,
517
+ height: 16,
518
+ }}
519
+ >
520
+ <View
521
+ style={{
522
+ width: 3.5,
523
+ height: 16,
524
+ backgroundColor: "#1e1e1e",
525
+ borderRadius: 1,
526
+ }}
527
+ />
528
+ <View
529
+ style={{
530
+ width: 3.5,
531
+ height: 16,
532
+ backgroundColor: "#1e1e1e",
533
+ borderRadius: 1,
534
+ }}
535
+ />
536
+ </View>
537
+ ) : (
538
+ <View
539
+ style={{
540
+ width: 0,
541
+ height: 0,
542
+ backgroundColor: "transparent",
543
+ borderStyle: "solid",
544
+ borderLeftWidth: 14,
545
+ borderRightWidth: 0,
546
+ borderTopWidth: 8,
547
+ borderBottomWidth: 8,
548
+ borderLeftColor: "#1e1e1e",
549
+ borderRightColor: "transparent",
550
+ borderTopColor: "transparent",
551
+ borderBottomColor: "transparent",
552
+ marginLeft: 3,
553
+ }}
554
+ />
555
+ )}
556
+ </View>
557
+ );
558
+ }
559
+
560
+ case "drag": {
561
+ if (actionButtonAssets?.dragAsset) {
562
+ return (
563
+ <Image
564
+ source={{ uri: actionButtonAssets.dragAsset }}
565
+ style={{
566
+ width: layout.assetWidth,
567
+ height: layout.assetHeight,
568
+ }}
569
+ resizeMode="contain"
570
+ />
571
+ );
572
+ }
573
+
574
+ return (
575
+ <View
576
+ style={{
577
+ width: 20,
578
+ height: 12,
579
+ justifyContent: "space-between",
580
+ }}
581
+ >
582
+ <View
583
+ style={{
584
+ height: 2,
585
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
586
+ borderRadius: 1,
587
+ }}
588
+ />
589
+ <View
590
+ style={{
591
+ height: 2,
592
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
593
+ borderRadius: 1,
594
+ }}
595
+ />
596
+ <View
597
+ style={{
598
+ height: 2,
599
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
600
+ borderRadius: 1,
601
+ }}
602
+ />
603
+ </View>
604
+ );
605
+ }
606
+
607
+ case "remove": {
608
+ if (actionButtonAssets?.removeAsset) {
609
+ return (
610
+ <Image
611
+ source={{ uri: actionButtonAssets.removeAsset }}
612
+ style={{
613
+ width: layout.assetWidth,
614
+ height: layout.assetHeight,
615
+ }}
616
+ resizeMode="contain"
617
+ />
618
+ );
619
+ }
620
+
621
+ return (
622
+ <View
623
+ style={{
624
+ width: 24,
625
+ height: 24,
626
+ borderRadius: 12,
627
+ borderWidth: 1.5,
628
+ borderColor: "rgba(255, 255, 255, 0.7)",
629
+ justifyContent: "center",
630
+ alignItems: "center",
631
+ }}
632
+ >
633
+ <View
634
+ style={{
635
+ width: 10,
636
+ height: 1.5,
637
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
638
+ borderRadius: 1,
639
+ }}
640
+ />
641
+ </View>
642
+ );
643
+ }
644
+
645
+ case "multiSelect": {
646
+ const studioAsset = isSelected
647
+ ? checkboxAssets?.checkedAsset
648
+ : checkboxAssets?.asset;
649
+
650
+ if (studioAsset) {
651
+ return (
652
+ <Image
653
+ source={{ uri: studioAsset }}
654
+ style={{
655
+ width: layout.assetWidth,
656
+ height: layout.assetHeight,
657
+ }}
658
+ resizeMode="contain"
659
+ />
660
+ );
661
+ }
662
+
663
+ if (!isSelected) {
664
+ return (
665
+ <View
666
+ style={{
667
+ width: layout.assetWidth,
668
+ height: layout.assetHeight,
669
+ borderRadius: layout.assetWidth / 2,
670
+ borderWidth: 1.5,
671
+ borderColor: "rgba(255, 255, 255, 0.7)",
672
+ backgroundColor: "transparent",
673
+ }}
674
+ />
675
+ );
676
+ }
677
+
678
+ return (
679
+ <View
680
+ style={{
681
+ width: 24,
682
+ height: 24,
683
+ borderRadius: 12,
684
+ backgroundColor: "#FFFFFF",
685
+ justifyContent: "center",
686
+ alignItems: "center",
687
+ }}
688
+ >
689
+ <View
690
+ style={{
691
+ width: 6,
692
+ height: 10,
693
+ borderBottomWidth: 2,
694
+ borderRightWidth: 2,
695
+ borderColor: "#1e1e1e",
696
+ transform: [{ rotate: "45deg" }],
697
+ marginTop: -2,
698
+ }}
699
+ />
700
+ </View>
701
+ );
702
+ }
703
+
704
+ default:
705
+ return null;
706
+ }
707
+ };
708
+
709
+ const buttonPadding = {
710
+ paddingTop: layout.paddingTop,
711
+ paddingRight: layout.paddingRight,
712
+ paddingBottom: layout.paddingBottom,
713
+ paddingLeft: layout.paddingLeft,
714
+ };
715
+
716
+ // A nested Pressable with no handler swallows the parent row press.
717
+ if (!onPress) {
718
+ return (
719
+ <View pointerEvents="none" style={buttonPadding}>
720
+ {renderIcon()}
721
+ </View>
722
+ );
723
+ }
724
+
494
725
  return (
495
- <View
496
- style={{
497
- paddingTop: layout.paddingTop,
498
- paddingRight: layout.paddingRight,
499
- paddingBottom: layout.paddingBottom,
500
- paddingLeft: layout.paddingLeft,
501
- opacity: focused ? 0.7 : 1,
726
+ <Pressable
727
+ onPress={(e: any) => {
728
+ e?.stopPropagation?.();
729
+ onPress();
502
730
  }}
731
+ style={({ pressed }) => [
732
+ buttonPadding,
733
+ {
734
+ opacity: pressed || focused ? 0.7 : 1,
735
+ },
736
+ ]}
503
737
  >
504
- <Image
505
- source={{ uri: BUTTON_ASSETS[action] }}
506
- style={{ width: layout.assetWidth, height: layout.assetHeight }}
507
- resizeMode="contain"
508
- />
509
- </View>
738
+ {renderIcon()}
739
+ </Pressable>
510
740
  );
511
741
  }
512
742
 
@@ -515,6 +745,12 @@ type ItemButtonsProps = {
515
745
  trailingButton?: ItemButtonAction;
516
746
  focusedLeadingButton?: boolean;
517
747
  focusedTrailingButton?: boolean;
748
+ onLeadingButtonPress?: () => void;
749
+ onTrailingButtonPress?: () => void;
750
+ isSelected?: boolean;
751
+ renderHandle?: (element: React.ReactElement) => React.ReactElement;
752
+ checkboxAssets?: CheckboxAssetsConfiguration;
753
+ actionButtonAssets?: ActionButtonAssetsConfiguration;
518
754
  };
519
755
 
520
756
  function ItemButtons({
@@ -522,6 +758,12 @@ function ItemButtons({
522
758
  trailingButton,
523
759
  focusedLeadingButton = false,
524
760
  focusedTrailingButton = false,
761
+ onLeadingButtonPress,
762
+ onTrailingButtonPress,
763
+ isSelected = false,
764
+ renderHandle,
765
+ checkboxAssets,
766
+ actionButtonAssets,
525
767
  }: ItemButtonsProps) {
526
768
  if (!leadingButton && !trailingButton) {
527
769
  return null;
@@ -531,42 +773,106 @@ function ItemButtons({
531
773
  <View style={styles.buttonsRow}>
532
774
  {leadingButton ? (
533
775
  <>
534
- <ItemButton action={leadingButton} focused={focusedLeadingButton} />
776
+ <ItemButton
777
+ action={leadingButton}
778
+ focused={focusedLeadingButton}
779
+ onPress={onLeadingButtonPress}
780
+ isSelected={isSelected}
781
+ checkboxAssets={checkboxAssets}
782
+ actionButtonAssets={actionButtonAssets}
783
+ />
535
784
  <View style={{ width: BUTTONS_CONTAINER_HORIZONTAL_GUTTER }} />
536
785
  </>
537
786
  ) : null}
538
787
  {trailingButton ? (
539
- <ItemButton action={trailingButton} focused={focusedTrailingButton} />
788
+ trailingButton === "drag" ? (
789
+ renderHandle ? (
790
+ renderHandle(
791
+ <ItemButton
792
+ action={trailingButton}
793
+ focused={focusedTrailingButton}
794
+ onPress={onTrailingButtonPress}
795
+ isSelected={isSelected}
796
+ checkboxAssets={checkboxAssets}
797
+ actionButtonAssets={actionButtonAssets}
798
+ />
799
+ )
800
+ ) : (
801
+ <ItemButton
802
+ action={trailingButton}
803
+ focused={focusedTrailingButton}
804
+ onPress={onTrailingButtonPress}
805
+ isSelected={isSelected}
806
+ checkboxAssets={checkboxAssets}
807
+ actionButtonAssets={actionButtonAssets}
808
+ />
809
+ )
810
+ ) : (
811
+ <ItemButton
812
+ action={trailingButton}
813
+ focused={focusedTrailingButton}
814
+ onPress={onTrailingButtonPress}
815
+ isSelected={isSelected}
816
+ checkboxAssets={checkboxAssets}
817
+ actionButtonAssets={actionButtonAssets}
818
+ />
819
+ )
540
820
  ) : null}
541
821
  </View>
542
822
  );
543
823
  }
544
824
 
545
825
  // ---------------------------------------------------------------------------
546
- // Item
826
+ // AudioPlayerTrackItem
547
827
  // ---------------------------------------------------------------------------
548
828
 
549
- export function Item({
829
+ export function AudioPlayerTrackItem({
550
830
  configuration,
551
831
  data,
552
832
  focused = false,
553
833
  focusedLeadingButton = false,
554
834
  focusedTrailingButton = false,
555
- }: ItemProps) {
835
+ onPress,
836
+ onLeadingButtonPress,
837
+ onTrailingButtonPress,
838
+ renderHandle,
839
+ }: AudioPlayerTrackItemProps) {
840
+ const modalBlocksStyle = useModalBlocksStyle();
841
+ const showNowPlaying = !!data.nowPlayingLabel;
842
+
843
+ const itemBlock =
844
+ showNowPlaying && modalBlocksStyle?.nowPlayingItem
845
+ ? modalBlocksStyle.nowPlayingItem
846
+ : modalBlocksStyle?.item;
847
+
848
+ const effectiveConfig = React.useMemo(() => {
849
+ return mergeItemConfiguration(configuration, itemBlock);
850
+ }, [configuration, itemBlock]);
851
+
556
852
  const {
557
853
  background,
558
854
  image,
559
- nowPlayingAsset,
560
- nowPlayingLabel,
561
855
  textLabel1,
562
856
  textLabel2,
563
- } = configuration;
857
+ nowPlayingLabel,
858
+ nowPlayingAsset,
859
+ checkboxAssets,
860
+ actionButtonAssets,
861
+ } = effectiveConfig;
564
862
 
565
863
  const container = resolveContainerSpec(image);
566
- const showNowPlaying = !!data.nowPlayingLabel;
864
+
865
+ const handleTrailingButtonPress =
866
+ onTrailingButtonPress ??
867
+ (data.trailingButton === "multiSelect" ? onPress : undefined);
567
868
 
568
869
  return (
569
- <ItemContainer image={image} background={background} focused={focused}>
870
+ <ItemContainer
871
+ image={image}
872
+ background={background}
873
+ focused={focused}
874
+ onPress={onPress}
875
+ >
570
876
  <View style={styles.row}>
571
877
  <ItemThumbnail configuration={image} imageUri={data.imageUri} />
572
878
  <View style={{ width: container.horizontalGutter }} />
@@ -594,6 +900,12 @@ export function Item({
594
900
  trailingButton={data.trailingButton}
595
901
  focusedLeadingButton={focusedLeadingButton}
596
902
  focusedTrailingButton={focusedTrailingButton}
903
+ onLeadingButtonPress={onLeadingButtonPress}
904
+ onTrailingButtonPress={handleTrailingButtonPress}
905
+ isSelected={data.isSelected}
906
+ renderHandle={renderHandle}
907
+ checkboxAssets={checkboxAssets}
908
+ actionButtonAssets={actionButtonAssets}
597
909
  />
598
910
  </View>
599
911
  </ItemContainer>
@@ -663,47 +975,3 @@ export const VARIANT_PLAYLIST_ITEM: ItemConfiguration = {
663
975
  textLabel1: DEFAULT_TEXT_LABEL_1_CONFIGURATION,
664
976
  textLabel2: DEFAULT_TEXT_LABEL_2_CONFIGURATION,
665
977
  };
666
-
667
- export default function App() {
668
- // Hardcoded core mock data objects matching user types framework
669
- const sharedMockItem: MenuItem = {
670
- title: "Content Title",
671
- summary: "Artist",
672
- icon: "https://picsum.photos/200",
673
- action: { type: "PLAY_AUDIO" },
674
- secondaryAction: { action: { type: "OPEN_CONTEXT_MENU" } },
675
- };
676
-
677
- const playlistMockItem: MenuItem = {
678
- title: "Playlist Name",
679
- summary: "12 Songs",
680
- icon: "https://picsum.photos/201",
681
- isSelected: true,
682
- action: { type: "NAVIGATE_PLAYLIST" },
683
- };
684
-
685
- return (
686
- <View style={styles.demoScreen}>
687
- <Text style={styles.demoHeading}>Modal Item Building Block</Text>
688
-
689
- <Text style={styles.demoLabel}>Now Playing</Text>
690
- <Item
691
- configuration={VARIANT_NOW_PLAYING}
692
- data={mapMenuItemToNowPlayingData(sharedMockItem)}
693
- />
694
-
695
- <Text style={styles.demoLabel}>Queued Item</Text>
696
- <Item
697
- configuration={VARIANT_QUEUED_ITEM}
698
- data={mapMenuItemToQueuedData(sharedMockItem)}
699
- focused
700
- />
701
-
702
- <Text style={styles.demoLabel}>Playlist Item</Text>
703
- <Item
704
- configuration={VARIANT_PLAYLIST_ITEM}
705
- data={mapMenuItemToPlaylistData(playlistMockItem)}
706
- />
707
- </View>
708
- );
709
- }