@applicaster/zapp-react-native-ui-components 16.0.0-rc.33 → 16.0.0-rc.34

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 (22) hide show
  1. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  2. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  3. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  4. package/Components/MasterCell/DefaultComponents/PressableView.tsx +3 -3
  5. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +40 -2
  6. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  7. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  8. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  9. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -0
  10. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +1 -1
  11. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  12. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  13. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  14. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  15. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  16. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  17. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +368 -0
  18. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +424 -0
  19. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +728 -0
  20. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +709 -0
  21. package/Components/ModalComponent/AudioPlayer/Components/index.ts +7 -0
  22. package/package.json +5 -5
@@ -0,0 +1,709 @@
1
+ /**
2
+ * Modal Building Block — Item (Queue / Playlist)
3
+ *
4
+ * Single-file Expo Snack demo. Three item forms driven by configuration + data.
5
+ *
6
+ * Green (configurable): background, image, nowPlayingAsset, nowPlayingLabel,
7
+ * textLabel1, textLabel2
8
+ * White (hardcoded): container padding/gutters, nowPlaying container layout,
9
+ * button sizes/padding/assets
10
+ *
11
+ * Variants (VARIANT_*) are preset configuration bundles — not passed as a field.
12
+ */
13
+
14
+ import React from "react";
15
+ import { Image, Platform, StyleSheet, Text, View } from "react-native";
16
+
17
+ const styles = StyleSheet.create({
18
+ row: {
19
+ flexDirection: "row",
20
+ alignItems: "center",
21
+ },
22
+ contentColumn: {
23
+ flex: 1,
24
+ justifyContent: "center",
25
+ },
26
+ buttonsRow: {
27
+ flexDirection: "row",
28
+ alignItems: "center",
29
+ },
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
+ });
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Configuration (green fields)
53
+ // ---------------------------------------------------------------------------
54
+
55
+ export type TextTransform =
56
+ | "default"
57
+ | "lowercase"
58
+ | "uppercase"
59
+ | "capitalize";
60
+
61
+ export type BackgroundConfiguration = {
62
+ defaultColor: string;
63
+ focusedColor: string;
64
+ cornerRadius: number;
65
+ };
66
+
67
+ export type ImageConfiguration = {
68
+ width: number;
69
+ height: number;
70
+ borderWidth: number;
71
+ borderColor: string;
72
+ cornerRadius: number;
73
+ };
74
+
75
+ export type LabelConfiguration = {
76
+ fontColor: string;
77
+ focusedFontColor?: string;
78
+ iosFontFamily: string;
79
+ androidFontFamily: string;
80
+ fontSize: number;
81
+ lineHeight: number;
82
+ iosLetterSpacing: number;
83
+ androidLetterSpacing: number;
84
+ textTransform: TextTransform;
85
+ };
86
+
87
+ export type NowPlayingAssetConfiguration = {
88
+ asset?: string;
89
+ };
90
+
91
+ export type ItemConfiguration = {
92
+ background: BackgroundConfiguration;
93
+ image: ImageConfiguration;
94
+ nowPlayingAsset: NowPlayingAssetConfiguration;
95
+ nowPlayingLabel: LabelConfiguration;
96
+ textLabel1: LabelConfiguration;
97
+ textLabel2: LabelConfiguration;
98
+ };
99
+
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
+ export const DEFAULT_TEXT_LABEL_1_CONFIGURATION: LabelConfiguration = {
145
+ fontColor: "#FFFFFF",
146
+ focusedFontColor: "#FFFFFF",
147
+ iosFontFamily: "SFProText-Semibold",
148
+ androidFontFamily: "Roboto-Medium",
149
+ fontSize: 15,
150
+ lineHeight: 18,
151
+ iosLetterSpacing: -0.2,
152
+ androidLetterSpacing: 0,
153
+ textTransform: "default",
154
+ };
155
+
156
+ export const DEFAULT_TEXT_LABEL_2_CONFIGURATION: LabelConfiguration = {
157
+ fontColor: "#999999",
158
+ iosFontFamily: "SFProText-Medium",
159
+ androidFontFamily: "Roboto-Medium",
160
+ fontSize: 13,
161
+ lineHeight: 16,
162
+ iosLetterSpacing: -0.1,
163
+ androidLetterSpacing: 0,
164
+ textTransform: "default",
165
+ };
166
+
167
+ export const DEFAULT_NOW_PLAYING_LABEL_CONFIGURATION: LabelConfiguration = {
168
+ fontColor: "#FFFFFF",
169
+ iosFontFamily: "SFProText-Semibold",
170
+ androidFontFamily: "Roboto-Medium",
171
+ fontSize: 13,
172
+ lineHeight: 16,
173
+ iosLetterSpacing: 0,
174
+ androidLetterSpacing: 0,
175
+ textTransform: "default",
176
+ };
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Data
180
+ // ---------------------------------------------------------------------------
181
+
182
+ export type ItemButtonAction = "playPause" | "drag" | "multiSelect" | "remove";
183
+
184
+ export type ItemData = {
185
+ imageUri?: string;
186
+ textLabel1: string;
187
+ textLabel2: string;
188
+ /** When set, shows the Now Playing row (equalizer + label). */
189
+ nowPlayingLabel?: string;
190
+ /** Button 2 (left most) — hardcoded action decides icon. */
191
+ leadingButton?: ItemButtonAction;
192
+ /** Button 1 (right most) — hardcoded action decides icon. */
193
+ trailingButton?: ItemButtonAction;
194
+ };
195
+
196
+ export type ItemProps = {
197
+ configuration: ItemConfiguration;
198
+ data: ItemData;
199
+ focused?: boolean;
200
+ focusedLeadingButton?: boolean;
201
+ focusedTrailingButton?: boolean;
202
+ };
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Hardcoded (white fields)
206
+ // ---------------------------------------------------------------------------
207
+
208
+ type ContainerSpec = {
209
+ paddingTop: number;
210
+ paddingRight: number;
211
+ paddingBottom: number;
212
+ paddingLeft: number;
213
+ horizontalGutter: number;
214
+ verticalGutter: number;
215
+ };
216
+
217
+ type ButtonLayoutSpec = {
218
+ assetWidth: number;
219
+ assetHeight: number;
220
+ paddingTop: number;
221
+ paddingRight: number;
222
+ paddingBottom: number;
223
+ paddingLeft: number;
224
+ };
225
+
226
+ const CONTAINER_SPEC_LARGE_IMAGE: ContainerSpec = {
227
+ paddingTop: 8,
228
+ paddingRight: 20,
229
+ paddingBottom: 8,
230
+ paddingLeft: 20,
231
+ horizontalGutter: 12,
232
+ verticalGutter: 2,
233
+ };
234
+
235
+ const CONTAINER_SPEC_DEFAULT: ContainerSpec = {
236
+ paddingTop: 8,
237
+ paddingRight: 10,
238
+ paddingBottom: 8,
239
+ paddingLeft: 10,
240
+ horizontalGutter: 12,
241
+ verticalGutter: 2,
242
+ };
243
+
244
+ const BUTTONS_CONTAINER_HORIZONTAL_GUTTER = 4;
245
+
246
+ const NOW_PLAYING_CONTAINER_SPEC = {
247
+ horizontalGutter: 4,
248
+ marginTop: 0,
249
+ marginRight: 0,
250
+ marginBottom: 6,
251
+ marginLeft: 0,
252
+ };
253
+
254
+ const NOW_PLAYING_ASSET_SPEC = {
255
+ width: 16,
256
+ height: 16,
257
+ };
258
+
259
+ const BUTTON_LAYOUT_SPEC: Record<ItemButtonAction, ButtonLayoutSpec> = {
260
+ playPause: {
261
+ assetWidth: 44,
262
+ assetHeight: 44,
263
+ paddingTop: 0,
264
+ paddingRight: 0,
265
+ paddingBottom: 0,
266
+ paddingLeft: 0,
267
+ },
268
+ drag: {
269
+ assetWidth: 24,
270
+ assetHeight: 24,
271
+ paddingTop: 10,
272
+ paddingRight: 10,
273
+ paddingBottom: 10,
274
+ paddingLeft: 10,
275
+ },
276
+ multiSelect: {
277
+ assetWidth: 24,
278
+ assetHeight: 24,
279
+ paddingTop: 10,
280
+ paddingRight: 10,
281
+ paddingBottom: 10,
282
+ paddingLeft: 10,
283
+ },
284
+ remove: {
285
+ assetWidth: 24,
286
+ assetHeight: 24,
287
+ paddingTop: 10,
288
+ paddingRight: 10,
289
+ paddingBottom: 10,
290
+ paddingLeft: 10,
291
+ },
292
+ };
293
+
294
+ const DEFAULT_THUMB_ASSET =
295
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAQklEQVR42u3PMQ0AAAgDsEmcfzNggZekRwU0beezCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhcLQVXaABdK6k6AAAAAElFTkSuQmCC";
296
+
297
+ const DEFAULT_EQUALIZER_ASSET =
298
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAI0lEQVR42mNgGNTgPxTQzwB0DVQ3gKCBowbQwYAhkBIHBAAA9TvPMeOFaLMAAAAASUVORK5CYII=";
299
+
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
+ function getTextTransformStyle(
311
+ textTransform: TextTransform
312
+ ): "none" | "lowercase" | "uppercase" | "capitalize" {
313
+ return textTransform === "default" ? "none" : textTransform;
314
+ }
315
+
316
+ function getFontFamily(ios: string, android: string) {
317
+ return Platform.select({ ios, android, default: android });
318
+ }
319
+
320
+ /** Hardcoded: large image (72) uses wider horizontal container padding. */
321
+ function resolveContainerSpec(image: ImageConfiguration): ContainerSpec {
322
+ return image.width >= 72
323
+ ? CONTAINER_SPEC_LARGE_IMAGE
324
+ : CONTAINER_SPEC_DEFAULT;
325
+ }
326
+
327
+ function mapMenuItemToNowPlayingData(item: MenuItem): ItemData {
328
+ return {
329
+ imageUri: item.icon,
330
+ textLabel1: item.title,
331
+ textLabel2: item.summary ?? "",
332
+ nowPlayingLabel: "Now Playing",
333
+ trailingButton: "playPause",
334
+ };
335
+ }
336
+
337
+ function mapMenuItemToQueuedData(item: MenuItem): ItemData {
338
+ return {
339
+ imageUri: item.icon,
340
+ textLabel1: item.title,
341
+ textLabel2: item.summary ?? "",
342
+ leadingButton: "remove",
343
+ trailingButton: "drag",
344
+ };
345
+ }
346
+
347
+ function mapMenuItemToPlaylistData(item: MenuItem): ItemData {
348
+ return {
349
+ imageUri: item.icon,
350
+ textLabel1: item.title,
351
+ textLabel2: item.summary ?? "",
352
+ trailingButton: "multiSelect",
353
+ };
354
+ }
355
+
356
+ // ---------------------------------------------------------------------------
357
+ // Inner components
358
+ // ---------------------------------------------------------------------------
359
+
360
+ type ItemContainerProps = {
361
+ image: ImageConfiguration;
362
+ background: BackgroundConfiguration;
363
+ focused: boolean;
364
+ children: React.ReactNode;
365
+ };
366
+
367
+ function ItemContainer({
368
+ image,
369
+ background,
370
+ focused,
371
+ children,
372
+ }: ItemContainerProps) {
373
+ const container = resolveContainerSpec(image);
374
+ const bg = focused ? background.focusedColor : background.defaultColor;
375
+
376
+ return (
377
+ <View
378
+ style={{
379
+ backgroundColor: bg,
380
+ borderRadius: background.cornerRadius,
381
+ paddingTop: container.paddingTop,
382
+ paddingRight: container.paddingRight,
383
+ paddingBottom: container.paddingBottom,
384
+ paddingLeft: container.paddingLeft,
385
+ }}
386
+ >
387
+ {children}
388
+ </View>
389
+ );
390
+ }
391
+
392
+ type ItemThumbnailProps = {
393
+ configuration: ImageConfiguration;
394
+ imageUri?: string;
395
+ };
396
+
397
+ function ItemThumbnail({ configuration, imageUri }: ItemThumbnailProps) {
398
+ return (
399
+ <Image
400
+ source={{ uri: imageUri ?? DEFAULT_THUMB_ASSET }}
401
+ style={{
402
+ width: configuration.width,
403
+ height: configuration.height,
404
+ borderWidth: configuration.borderWidth,
405
+ borderColor: configuration.borderColor,
406
+ borderRadius: configuration.cornerRadius,
407
+ }}
408
+ resizeMode="cover"
409
+ />
410
+ );
411
+ }
412
+
413
+ type ItemLabelProps = {
414
+ text: string;
415
+ configuration: LabelConfiguration;
416
+ focused?: boolean;
417
+ };
418
+
419
+ function ItemLabel({ text, configuration, focused = false }: ItemLabelProps) {
420
+ const color =
421
+ focused && configuration.focusedFontColor
422
+ ? configuration.focusedFontColor
423
+ : configuration.fontColor;
424
+
425
+ return (
426
+ <Text
427
+ style={{
428
+ color,
429
+ fontSize: configuration.fontSize,
430
+ lineHeight: configuration.lineHeight,
431
+ letterSpacing:
432
+ Platform.OS === "ios"
433
+ ? configuration.iosLetterSpacing
434
+ : configuration.androidLetterSpacing,
435
+ fontFamily: getFontFamily(
436
+ configuration.iosFontFamily,
437
+ configuration.androidFontFamily
438
+ ),
439
+ textTransform: getTextTransformStyle(configuration.textTransform),
440
+ }}
441
+ numberOfLines={1}
442
+ >
443
+ {text}
444
+ </Text>
445
+ );
446
+ }
447
+
448
+ type NowPlayingRowProps = {
449
+ assetConfiguration: NowPlayingAssetConfiguration;
450
+ labelConfiguration: LabelConfiguration;
451
+ label: string;
452
+ };
453
+
454
+ function NowPlayingRow({
455
+ assetConfiguration,
456
+ labelConfiguration,
457
+ label,
458
+ }: NowPlayingRowProps) {
459
+ const asset = assetConfiguration.asset ?? DEFAULT_EQUALIZER_ASSET;
460
+
461
+ return (
462
+ <View
463
+ style={{
464
+ flexDirection: "row",
465
+ alignItems: "center",
466
+ marginTop: NOW_PLAYING_CONTAINER_SPEC.marginTop,
467
+ marginRight: NOW_PLAYING_CONTAINER_SPEC.marginRight,
468
+ marginBottom: NOW_PLAYING_CONTAINER_SPEC.marginBottom,
469
+ marginLeft: NOW_PLAYING_CONTAINER_SPEC.marginLeft,
470
+ }}
471
+ >
472
+ <Image
473
+ source={{ uri: asset }}
474
+ style={{
475
+ width: NOW_PLAYING_ASSET_SPEC.width,
476
+ height: NOW_PLAYING_ASSET_SPEC.height,
477
+ }}
478
+ resizeMode="contain"
479
+ />
480
+ <View style={{ width: NOW_PLAYING_CONTAINER_SPEC.horizontalGutter }} />
481
+ <ItemLabel text={label} configuration={labelConfiguration} />
482
+ </View>
483
+ );
484
+ }
485
+
486
+ type ItemButtonProps = {
487
+ action: ItemButtonAction;
488
+ focused?: boolean;
489
+ };
490
+
491
+ function ItemButton({ action, focused = false }: ItemButtonProps) {
492
+ const layout = BUTTON_LAYOUT_SPEC[action];
493
+
494
+ 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,
502
+ }}
503
+ >
504
+ <Image
505
+ source={{ uri: BUTTON_ASSETS[action] }}
506
+ style={{ width: layout.assetWidth, height: layout.assetHeight }}
507
+ resizeMode="contain"
508
+ />
509
+ </View>
510
+ );
511
+ }
512
+
513
+ type ItemButtonsProps = {
514
+ leadingButton?: ItemButtonAction;
515
+ trailingButton?: ItemButtonAction;
516
+ focusedLeadingButton?: boolean;
517
+ focusedTrailingButton?: boolean;
518
+ };
519
+
520
+ function ItemButtons({
521
+ leadingButton,
522
+ trailingButton,
523
+ focusedLeadingButton = false,
524
+ focusedTrailingButton = false,
525
+ }: ItemButtonsProps) {
526
+ if (!leadingButton && !trailingButton) {
527
+ return null;
528
+ }
529
+
530
+ return (
531
+ <View style={styles.buttonsRow}>
532
+ {leadingButton ? (
533
+ <>
534
+ <ItemButton action={leadingButton} focused={focusedLeadingButton} />
535
+ <View style={{ width: BUTTONS_CONTAINER_HORIZONTAL_GUTTER }} />
536
+ </>
537
+ ) : null}
538
+ {trailingButton ? (
539
+ <ItemButton action={trailingButton} focused={focusedTrailingButton} />
540
+ ) : null}
541
+ </View>
542
+ );
543
+ }
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // Item
547
+ // ---------------------------------------------------------------------------
548
+
549
+ export function Item({
550
+ configuration,
551
+ data,
552
+ focused = false,
553
+ focusedLeadingButton = false,
554
+ focusedTrailingButton = false,
555
+ }: ItemProps) {
556
+ const {
557
+ background,
558
+ image,
559
+ nowPlayingAsset,
560
+ nowPlayingLabel,
561
+ textLabel1,
562
+ textLabel2,
563
+ } = configuration;
564
+
565
+ const container = resolveContainerSpec(image);
566
+ const showNowPlaying = !!data.nowPlayingLabel;
567
+
568
+ return (
569
+ <ItemContainer image={image} background={background} focused={focused}>
570
+ <View style={styles.row}>
571
+ <ItemThumbnail configuration={image} imageUri={data.imageUri} />
572
+ <View style={{ width: container.horizontalGutter }} />
573
+ <View style={styles.contentColumn}>
574
+ {showNowPlaying ? (
575
+ <>
576
+ <NowPlayingRow
577
+ assetConfiguration={nowPlayingAsset}
578
+ labelConfiguration={nowPlayingLabel}
579
+ label={data.nowPlayingLabel}
580
+ />
581
+ <View style={{ height: container.verticalGutter }} />
582
+ </>
583
+ ) : null}
584
+ <ItemLabel
585
+ text={data.textLabel1}
586
+ configuration={textLabel1}
587
+ focused={focused}
588
+ />
589
+ <View style={{ height: container.verticalGutter }} />
590
+ <ItemLabel text={data.textLabel2} configuration={textLabel2} />
591
+ </View>
592
+ <ItemButtons
593
+ leadingButton={data.leadingButton}
594
+ trailingButton={data.trailingButton}
595
+ focusedLeadingButton={focusedLeadingButton}
596
+ focusedTrailingButton={focusedTrailingButton}
597
+ />
598
+ </View>
599
+ </ItemContainer>
600
+ );
601
+ }
602
+
603
+ // ---------------------------------------------------------------------------
604
+ // Variant presets — collections of configuration keys (not a variant field)
605
+ // ---------------------------------------------------------------------------
606
+
607
+ /** Variant 1 — Now Playing */
608
+ export const VARIANT_NOW_PLAYING: ItemConfiguration = {
609
+ background: {
610
+ defaultColor: "transparent",
611
+ focusedColor: "transparent",
612
+ cornerRadius: 0,
613
+ },
614
+ image: {
615
+ width: 72,
616
+ height: 72,
617
+ borderWidth: 1,
618
+ borderColor: "rgba(255, 255, 255, 0.1)",
619
+ cornerRadius: 8,
620
+ },
621
+ nowPlayingAsset: {},
622
+ nowPlayingLabel: DEFAULT_NOW_PLAYING_LABEL_CONFIGURATION,
623
+ textLabel1: DEFAULT_TEXT_LABEL_1_CONFIGURATION,
624
+ textLabel2: DEFAULT_TEXT_LABEL_2_CONFIGURATION,
625
+ };
626
+
627
+ /** Default / Variant 2 — Queued Item */
628
+ export const VARIANT_QUEUED_ITEM: ItemConfiguration = {
629
+ background: {
630
+ defaultColor: "transparent",
631
+ focusedColor: "rgba(62, 62, 62, 1)",
632
+ cornerRadius: 12,
633
+ },
634
+ image: {
635
+ width: 48,
636
+ height: 48,
637
+ borderWidth: 1,
638
+ borderColor: "rgba(255, 255, 255, 0.1)",
639
+ cornerRadius: 6,
640
+ },
641
+ nowPlayingAsset: {},
642
+ nowPlayingLabel: DEFAULT_NOW_PLAYING_LABEL_CONFIGURATION,
643
+ textLabel1: DEFAULT_TEXT_LABEL_1_CONFIGURATION,
644
+ textLabel2: DEFAULT_TEXT_LABEL_2_CONFIGURATION,
645
+ };
646
+
647
+ /** Variant 3 — Playlist Item */
648
+ export const VARIANT_PLAYLIST_ITEM: ItemConfiguration = {
649
+ background: {
650
+ defaultColor: "transparent",
651
+ focusedColor: "rgba(62, 62, 62, 1)",
652
+ cornerRadius: 12,
653
+ },
654
+ image: {
655
+ width: 48,
656
+ height: 48,
657
+ borderWidth: 1,
658
+ borderColor: "rgba(255, 255, 255, 0.1)",
659
+ cornerRadius: 6,
660
+ },
661
+ nowPlayingAsset: {},
662
+ nowPlayingLabel: DEFAULT_NOW_PLAYING_LABEL_CONFIGURATION,
663
+ textLabel1: DEFAULT_TEXT_LABEL_1_CONFIGURATION,
664
+ textLabel2: DEFAULT_TEXT_LABEL_2_CONFIGURATION,
665
+ };
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
+ }
@@ -0,0 +1,7 @@
1
+ export { Item as AudioPlayerItem } from "./Item";
2
+
3
+ export { Item as AudioPlayerHeader } from "./Header";
4
+
5
+ export { Item as AudioPlayerButton } from "./Button";
6
+
7
+ export { Item as AudioPlayerAction } from "./Action";