@applicaster/zapp-react-native-ui-components 16.0.0-rc.4 → 16.0.0-rc.40

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 (100) hide show
  1. package/Components/BackgroundImage/BackgroundImage.android.tv.tsx +28 -0
  2. package/Components/BackgroundImage/BackgroundImage.ios.tv.tsx +24 -0
  3. package/Components/BackgroundImage/BackgroundImage.tsx +42 -0
  4. package/Components/BackgroundImage/index.ts +1 -0
  5. package/Components/CellRendererResolver/index.ts +2 -2
  6. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  8. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  9. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  10. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  11. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  12. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  14. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  15. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  16. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  17. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  18. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  19. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  20. package/Components/GeneralContentScreen/index.ts +2 -0
  21. package/Components/Layout/FullWidthRow.tsx +38 -0
  22. package/Components/Layout/TV/ScreenContainer.tsx +5 -0
  23. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
  24. package/Components/Layout/TV/index.tsx +3 -4
  25. package/Components/Layout/TV/index.web.tsx +2 -3
  26. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  29. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +1 -1
  30. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  31. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  32. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  33. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  34. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  35. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  36. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  37. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  38. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
  39. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  40. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  41. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  43. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  44. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  45. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  46. package/Components/MasterCell/dataAdapter.ts +15 -5
  47. package/Components/MasterCell/hooks/index.ts +2 -0
  48. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  49. package/Components/MasterCell/index.tsx +30 -5
  50. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  51. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  52. package/Components/MasterCell/utils/index.ts +85 -46
  53. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +332 -0
  54. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +402 -0
  55. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +811 -0
  56. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  57. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +878 -0
  58. package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
  59. package/Components/ModalComponent/BottomSheetModalContent.tsx +34 -18
  60. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  61. package/Components/OfflineHandler/__tests__/index.test.tsx +78 -18
  62. package/Components/OfflineHandler/hooks.ts +123 -0
  63. package/Components/OfflineHandler/index.tsx +51 -73
  64. package/Components/OfflineHandler/utils/index.ts +1 -13
  65. package/Components/PlayerContainer/PlayerContainer.tsx +14 -10
  66. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +284 -0
  67. package/Components/PreloaderWrapper/index.tsx +1 -1
  68. package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -2
  69. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +3 -15
  70. package/Components/Screen/TV/hooks/__tests__/useAfterPaint.test.ts +60 -0
  71. package/Components/Screen/TV/hooks/index.ts +2 -0
  72. package/Components/Screen/TV/hooks/useAfterPaint.ts +23 -0
  73. package/Components/Screen/TV/index.web.tsx +16 -7
  74. package/Components/Screen/index.tsx +8 -4
  75. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  76. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  77. package/Components/ScreenRevealManager/Overlay.tsx +34 -0
  78. package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
  79. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +8 -19
  80. package/Components/TopCutoffOverlay/index.tsx +2 -2
  81. package/Components/Transitioner/Transitioner.tsx +28 -17
  82. package/Components/VideoLive/LiveImageManager.ts +56 -45
  83. package/Components/VideoLive/PlayerLiveImageComponent.tsx +4 -2
  84. package/Components/VideoModal/utils.ts +6 -1
  85. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  86. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  87. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  88. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  89. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  90. package/Helpers/index.js +7 -40
  91. package/package.json +5 -5
  92. package/Components/Layout/TV/LayoutBackground.tsx +0 -31
  93. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  94. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  95. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  96. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  97. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  98. package/Components/Screen/utils.ts +0 -16
  99. package/Helpers/Analytics/index.js +0 -95
  100. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Modal Building Block — Input
3
+ *
4
+ * Green (configurable): background, label, text, icons
5
+ * White (hardcoded): default search / clear icon assets, clear button circle layout
6
+ *
7
+ * Variants (VARIANT_*) are preset configuration bundles — not a config field.
8
+ */
9
+
10
+ import React, { useRef } from "react";
11
+ import {
12
+ Image,
13
+ Platform,
14
+ Pressable,
15
+ StyleSheet,
16
+ Text,
17
+ TextInput,
18
+ View,
19
+ } from "react-native";
20
+
21
+ const styles = StyleSheet.create({
22
+ column: {
23
+ alignItems: "center",
24
+ width: "100%",
25
+ },
26
+ textInput: {
27
+ padding: 0,
28
+ margin: 0,
29
+ },
30
+ textInputRow: {
31
+ flex: 1,
32
+ },
33
+ clearButton: {
34
+ alignItems: "center",
35
+ justifyContent: "center",
36
+ },
37
+ underline: {
38
+ alignSelf: "stretch",
39
+ },
40
+ demoScreen: {
41
+ flex: 1,
42
+ backgroundColor: "#1C1C1E",
43
+ paddingTop: 64,
44
+ paddingHorizontal: 16,
45
+ gap: 24,
46
+ },
47
+ demoHeading: {
48
+ color: "#FFFFFF",
49
+ fontSize: 20,
50
+ fontWeight: "600",
51
+ marginBottom: 8,
52
+ },
53
+ demoLabel: {
54
+ color: "#999999",
55
+ fontSize: 13,
56
+ marginTop: 8,
57
+ },
58
+ });
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Configuration (green / editable fields)
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export type TextTransform =
65
+ | "default"
66
+ | "lowercase"
67
+ | "uppercase"
68
+ | "capitalize";
69
+
70
+ export type TextAlignment = "left" | "center";
71
+
72
+ export type BackgroundConfiguration = {
73
+ defaultColor: string;
74
+ focusedColor: string;
75
+ borderColor: string;
76
+ focusedBorderColor: string;
77
+ borderWidth: number;
78
+ /** Bottom underline height; 0 = none (pill search uses borderWidth instead) */
79
+ underlineHeight: number;
80
+ cornerRadius: number;
81
+ paddingTop: number;
82
+ paddingRight: number;
83
+ paddingBottom: number;
84
+ paddingLeft: number;
85
+ horizontalGutter: number;
86
+ verticalGutter: number;
87
+ /** Fixed height for pill layout; 0 = hug content (name / underline layout) */
88
+ height: number;
89
+ };
90
+
91
+ export type LabelConfiguration = {
92
+ enabled: boolean;
93
+ fontColor: string;
94
+ iosFontFamily: string;
95
+ androidFontFamily: string;
96
+ fontSize: number;
97
+ lineHeight: number;
98
+ iosLetterSpacing: number;
99
+ androidLetterSpacing: number;
100
+ textTransform: TextTransform;
101
+ };
102
+
103
+ export type TextConfiguration = {
104
+ fontColor: string;
105
+ focusedFontColor: string;
106
+ placeholderColor: string;
107
+ cursorColor: string;
108
+ iosFontFamily: string;
109
+ androidFontFamily: string;
110
+ fontSize: number;
111
+ lineHeight: number;
112
+ iosLetterSpacing: number;
113
+ androidLetterSpacing: number;
114
+ textTransform: TextTransform;
115
+ alignment: TextAlignment;
116
+ };
117
+
118
+ export type LeadingIconConfiguration = {
119
+ enabled: boolean;
120
+ asset?: string;
121
+ };
122
+
123
+ export type TrailingIconConfiguration = {
124
+ enabled: boolean;
125
+ asset?: string;
126
+ backgroundColor: string;
127
+ focusedBackgroundColor: string;
128
+ };
129
+
130
+ export type IconsConfiguration = {
131
+ width: number;
132
+ height: number;
133
+ leadingIcon: LeadingIconConfiguration;
134
+ trailingIcon: TrailingIconConfiguration;
135
+ };
136
+
137
+ export type InputConfiguration = {
138
+ background: BackgroundConfiguration;
139
+ label: LabelConfiguration;
140
+ text: TextConfiguration;
141
+ icons: IconsConfiguration;
142
+ };
143
+
144
+ export const DEFAULT_BACKGROUND_CONFIGURATION: BackgroundConfiguration = {
145
+ defaultColor: "#000000",
146
+ focusedColor: "#000000",
147
+ borderColor: "rgba(255, 255, 255, 0.55)",
148
+ focusedBorderColor: "rgba(255, 255, 255, 0.85)",
149
+ borderWidth: 1,
150
+ underlineHeight: 0,
151
+ cornerRadius: 24,
152
+ paddingTop: 10,
153
+ paddingRight: 10,
154
+ paddingBottom: 10,
155
+ paddingLeft: 14,
156
+ horizontalGutter: 8,
157
+ verticalGutter: 0,
158
+ height: 44,
159
+ };
160
+
161
+ export const DEFAULT_LABEL_CONFIGURATION: LabelConfiguration = {
162
+ enabled: false,
163
+ fontColor: "rgba(142, 142, 147, 1)",
164
+ iosFontFamily: "SFProText-Regular",
165
+ androidFontFamily: "Roboto",
166
+ fontSize: 15,
167
+ lineHeight: 20,
168
+ iosLetterSpacing: -0.2,
169
+ androidLetterSpacing: 0,
170
+ textTransform: "default",
171
+ };
172
+
173
+ export const DEFAULT_TEXT_CONFIGURATION: TextConfiguration = {
174
+ fontColor: "#FFFFFF",
175
+ focusedFontColor: "#FFFFFF",
176
+ placeholderColor: "rgba(142, 142, 147, 1)",
177
+ cursorColor: "#FFFFFF",
178
+ iosFontFamily: "SFProText-Regular",
179
+ androidFontFamily: "Roboto",
180
+ fontSize: 15,
181
+ lineHeight: 20,
182
+ iosLetterSpacing: -0.2,
183
+ androidLetterSpacing: 0,
184
+ textTransform: "default",
185
+ alignment: "left",
186
+ };
187
+
188
+ export const DEFAULT_ICONS_CONFIGURATION: IconsConfiguration = {
189
+ width: 20,
190
+ height: 20,
191
+ leadingIcon: {
192
+ enabled: true,
193
+ },
194
+ trailingIcon: {
195
+ enabled: true,
196
+ backgroundColor: "rgba(58, 58, 60, 1)",
197
+ focusedBackgroundColor: "rgba(72, 72, 74, 1)",
198
+ },
199
+ };
200
+
201
+ export const DEFAULT_INPUT_CONFIGURATION: InputConfiguration = {
202
+ background: DEFAULT_BACKGROUND_CONFIGURATION,
203
+ label: DEFAULT_LABEL_CONFIGURATION,
204
+ text: DEFAULT_TEXT_CONFIGURATION,
205
+ icons: DEFAULT_ICONS_CONFIGURATION,
206
+ };
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Data (runtime / localization)
210
+ // ---------------------------------------------------------------------------
211
+
212
+ export type InputData = {
213
+ placeholder: string;
214
+ /** Top instructional label (e.g. "Name your playlist") */
215
+ label?: string;
216
+ };
217
+
218
+ export type ItemProps = {
219
+ configuration: InputConfiguration;
220
+ data: InputData;
221
+ value?: string;
222
+ focused?: boolean;
223
+ focusedClearButton?: boolean;
224
+ onChangeText?: (text: string) => void;
225
+ onClearPress?: () => void;
226
+ onFocus?: () => void;
227
+ onBlur?: () => void;
228
+ };
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Hardcoded (white fields)
232
+ // ---------------------------------------------------------------------------
233
+
234
+ const CLEAR_BUTTON_SPEC = {
235
+ width: 20,
236
+ height: 20,
237
+ };
238
+
239
+ const DEFAULT_SEARCH_ICON_ASSET =
240
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAr0lEQVR42mNgGAXkgnXrNrEAcT4Q7wTiB1C8EyrGQqnhXlAD/0PxayiG8UFyXpQYDjOoG4hVkORUoGIweS9yggXm8kg86iKRfMJCigX5MJcToRbmk3xSLNgJ1aRChFoVqNqdpFgA8vJrEtSDIv7BoLKA5pHE80imbTIlI6M9AmJ9WhUVjyi1BG9hBzIUyZLHZFlChCP0oYbDLNEbspbooVnCQktLaGMBUsKgjeEDBgBwWWj4ea9s3wAAAABJRU5ErkJggg==";
241
+
242
+ const DEFAULT_CLEAR_ICON_ASSET =
243
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAPklEQVR42mNgGAWjYGiD/0BATXVkaabIcEKGUMVwXIZR1XB0Q2liOE1dThcf0DQOaJqKaJoP6JKTR8EoIAgADw5TrVXxxAIAAAAASUVORK5CYII=";
244
+
245
+ function getTextTransformStyle(
246
+ textTransform: TextTransform
247
+ ): "none" | "lowercase" | "uppercase" | "capitalize" {
248
+ return textTransform === "default" ? "none" : textTransform;
249
+ }
250
+
251
+ function shouldShowLeadingIcon(leadingIcon: LeadingIconConfiguration) {
252
+ return leadingIcon.enabled;
253
+ }
254
+
255
+ function shouldShowTrailingIcon(trailingIcon: TrailingIconConfiguration) {
256
+ return trailingIcon.enabled;
257
+ }
258
+
259
+ function shouldShowLabel(label: LabelConfiguration, labelText?: string) {
260
+ return label.enabled && !!labelText;
261
+ }
262
+
263
+ function isUnderlineLayout(configuration: InputConfiguration) {
264
+ return configuration.background.underlineHeight > 0;
265
+ }
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // Inner components
269
+ // ---------------------------------------------------------------------------
270
+
271
+ type InputLabelProps = {
272
+ text: string;
273
+ configuration: LabelConfiguration;
274
+ };
275
+
276
+ function InputLabel({ text, configuration }: InputLabelProps) {
277
+ const letterSpacing =
278
+ Platform.OS === "ios"
279
+ ? configuration.iosLetterSpacing
280
+ : configuration.androidLetterSpacing;
281
+
282
+ return (
283
+ <Text
284
+ style={{
285
+ color: configuration.fontColor,
286
+ fontSize: configuration.fontSize,
287
+ lineHeight: configuration.lineHeight,
288
+ letterSpacing,
289
+ fontFamily: Platform.select({
290
+ ios: configuration.iosFontFamily,
291
+ android: configuration.androidFontFamily,
292
+ default: configuration.androidFontFamily,
293
+ }),
294
+ textTransform: getTextTransformStyle(configuration.textTransform),
295
+ textAlign: "center",
296
+ }}
297
+ numberOfLines={1}
298
+ >
299
+ {text}
300
+ </Text>
301
+ );
302
+ }
303
+
304
+ type InputContainerProps = {
305
+ configuration: BackgroundConfiguration;
306
+ focused: boolean;
307
+ underlineLayout: boolean;
308
+ children: React.ReactNode;
309
+ };
310
+
311
+ function InputContainer({
312
+ configuration,
313
+ focused,
314
+ underlineLayout,
315
+ children,
316
+ }: InputContainerProps) {
317
+ return (
318
+ <View
319
+ style={{
320
+ flexDirection: underlineLayout ? "column" : "row",
321
+ alignItems: "center",
322
+ alignSelf: "stretch",
323
+ height: configuration.height > 0 ? configuration.height : undefined,
324
+ backgroundColor: focused
325
+ ? configuration.focusedColor
326
+ : configuration.defaultColor,
327
+ borderColor: focused
328
+ ? configuration.focusedBorderColor
329
+ : configuration.borderColor,
330
+ borderWidth: configuration.borderWidth,
331
+ borderRadius: configuration.cornerRadius,
332
+ paddingTop: configuration.paddingTop,
333
+ paddingRight: configuration.paddingRight,
334
+ paddingBottom: configuration.paddingBottom,
335
+ paddingLeft: configuration.paddingLeft,
336
+ }}
337
+ >
338
+ {children}
339
+ </View>
340
+ );
341
+ }
342
+
343
+ type InputUnderlineProps = {
344
+ configuration: BackgroundConfiguration;
345
+ focused: boolean;
346
+ };
347
+
348
+ function InputUnderline({ configuration, focused }: InputUnderlineProps) {
349
+ if (configuration.underlineHeight <= 0) {
350
+ return null;
351
+ }
352
+
353
+ return (
354
+ <View
355
+ style={[
356
+ styles.underline,
357
+ {
358
+ height: configuration.underlineHeight,
359
+ backgroundColor: focused
360
+ ? configuration.focusedBorderColor
361
+ : configuration.borderColor,
362
+ },
363
+ ]}
364
+ />
365
+ );
366
+ }
367
+
368
+ type InputLeadingIconProps = {
369
+ icons: IconsConfiguration;
370
+ tintColor: string;
371
+ };
372
+
373
+ function InputLeadingIcon({ icons, tintColor }: InputLeadingIconProps) {
374
+ const asset = icons.leadingIcon.asset ?? DEFAULT_SEARCH_ICON_ASSET;
375
+
376
+ return (
377
+ <Image
378
+ source={{ uri: asset }}
379
+ style={{
380
+ width: icons.width,
381
+ height: icons.height,
382
+ tintColor,
383
+ }}
384
+ resizeMode="contain"
385
+ />
386
+ );
387
+ }
388
+
389
+ type TextInputRef = React.ElementRef<typeof TextInput>;
390
+
391
+ type InputFieldProps = {
392
+ inputRef?: React.RefObject<TextInputRef>;
393
+ value: string;
394
+ placeholder: string;
395
+ configuration: TextConfiguration;
396
+ focused: boolean;
397
+ stretch: boolean;
398
+ onChangeText?: (text: string) => void;
399
+ onFocus?: () => void;
400
+ onBlur?: () => void;
401
+ };
402
+
403
+ function InputField({
404
+ inputRef,
405
+ value,
406
+ placeholder,
407
+ configuration,
408
+ focused,
409
+ stretch,
410
+ onChangeText,
411
+ onFocus,
412
+ onBlur,
413
+ }: InputFieldProps) {
414
+ const letterSpacing =
415
+ Platform.OS === "ios"
416
+ ? configuration.iosLetterSpacing
417
+ : configuration.androidLetterSpacing;
418
+
419
+ return (
420
+ <TextInput
421
+ ref={inputRef}
422
+ value={value}
423
+ placeholder={placeholder}
424
+ placeholderTextColor={configuration.placeholderColor}
425
+ onChangeText={onChangeText}
426
+ onFocus={onFocus}
427
+ onBlur={onBlur}
428
+ showSoftInputOnFocus
429
+ selectionColor={configuration.cursorColor}
430
+ cursorColor={configuration.cursorColor}
431
+ style={[
432
+ styles.textInput,
433
+ stretch ? styles.textInputRow : { alignSelf: "stretch", width: "100%" },
434
+ {
435
+ color: focused
436
+ ? configuration.focusedFontColor
437
+ : configuration.fontColor,
438
+ fontSize: configuration.fontSize,
439
+ lineHeight: configuration.lineHeight,
440
+ letterSpacing,
441
+ fontFamily: Platform.select({
442
+ ios: configuration.iosFontFamily,
443
+ android: configuration.androidFontFamily,
444
+ default: configuration.androidFontFamily,
445
+ }),
446
+ textTransform: getTextTransformStyle(configuration.textTransform),
447
+ textAlign: configuration.alignment,
448
+ },
449
+ ]}
450
+ />
451
+ );
452
+ }
453
+
454
+ type InputClearButtonProps = {
455
+ icons: IconsConfiguration;
456
+ focused?: boolean;
457
+ };
458
+
459
+ function InputClearButton({ icons, focused = false }: InputClearButtonProps) {
460
+ const { trailingIcon } = icons;
461
+ const asset = trailingIcon.asset ?? DEFAULT_CLEAR_ICON_ASSET;
462
+
463
+ return (
464
+ <View
465
+ style={[
466
+ styles.clearButton,
467
+ {
468
+ width: CLEAR_BUTTON_SPEC.width,
469
+ height: CLEAR_BUTTON_SPEC.height,
470
+ borderRadius: CLEAR_BUTTON_SPEC.width / 2,
471
+ backgroundColor: focused
472
+ ? trailingIcon.focusedBackgroundColor
473
+ : trailingIcon.backgroundColor,
474
+ },
475
+ ]}
476
+ >
477
+ <Image
478
+ source={{ uri: asset }}
479
+ style={{
480
+ width: CLEAR_BUTTON_SPEC.width * 0.55,
481
+ height: CLEAR_BUTTON_SPEC.height * 0.55,
482
+ tintColor: "#000000",
483
+ }}
484
+ resizeMode="contain"
485
+ />
486
+ </View>
487
+ );
488
+ }
489
+
490
+ // ---------------------------------------------------------------------------
491
+ // Item
492
+ // ---------------------------------------------------------------------------
493
+
494
+ export function AudioPlayerInput({
495
+ configuration,
496
+ data,
497
+ value = "",
498
+ focused = false,
499
+ focusedClearButton = false,
500
+ onChangeText,
501
+ onClearPress,
502
+ onFocus,
503
+ onBlur,
504
+ }: ItemProps) {
505
+ const { background, label, text, icons } = configuration;
506
+ const inputRef = useRef<TextInputRef>(null);
507
+ const underlineLayout = isUnderlineLayout(configuration);
508
+ const showLabel = shouldShowLabel(label, data.label);
509
+ const showLeading = shouldShowLeadingIcon(icons.leadingIcon);
510
+ const showTrailing = shouldShowTrailingIcon(icons.trailingIcon);
511
+
512
+ const focusInput = () => {
513
+ inputRef.current?.focus();
514
+ };
515
+
516
+ const clearButton = showTrailing ? (
517
+ <InputClearButton icons={icons} focused={focusedClearButton} />
518
+ ) : null;
519
+
520
+ const field = (
521
+ <InputField
522
+ inputRef={inputRef}
523
+ value={value}
524
+ placeholder={data.placeholder}
525
+ configuration={text}
526
+ focused={focused}
527
+ stretch={!underlineLayout}
528
+ onChangeText={onChangeText}
529
+ onFocus={onFocus}
530
+ onBlur={onBlur}
531
+ />
532
+ );
533
+
534
+ const inputRow = (
535
+ <Pressable onPress={focusInput} style={{ alignSelf: "stretch" }}>
536
+ <InputContainer
537
+ configuration={background}
538
+ focused={focused}
539
+ underlineLayout={underlineLayout}
540
+ >
541
+ {showLeading ? (
542
+ <>
543
+ <InputLeadingIcon icons={icons} tintColor={text.placeholderColor} />
544
+ <View style={{ width: background.horizontalGutter }} />
545
+ </>
546
+ ) : null}
547
+ {field}
548
+ {clearButton ? (
549
+ <>
550
+ <View style={{ width: background.horizontalGutter }} />
551
+ {onClearPress ? (
552
+ <Pressable
553
+ onPress={(event) => {
554
+ event.stopPropagation?.();
555
+ onClearPress();
556
+ }}
557
+ >
558
+ {clearButton}
559
+ </Pressable>
560
+ ) : (
561
+ clearButton
562
+ )}
563
+ </>
564
+ ) : null}
565
+ {underlineLayout ? (
566
+ <InputUnderline configuration={background} focused={focused} />
567
+ ) : null}
568
+ </InputContainer>
569
+ </Pressable>
570
+ );
571
+
572
+ if (!showLabel || !data.label) {
573
+ return inputRow;
574
+ }
575
+
576
+ return (
577
+ <View style={styles.column}>
578
+ <InputLabel text={data.label} configuration={label} />
579
+ <View style={{ height: background.verticalGutter }} />
580
+ {inputRow}
581
+ </View>
582
+ );
583
+ }
584
+
585
+ // ---------------------------------------------------------------------------
586
+ // Variant presets — collections of configuration keys (not a variant field)
587
+ // ---------------------------------------------------------------------------
588
+
589
+ /** Search pill — leading icon + clear button */
590
+ export const VARIANT_SEARCH_INPUT: InputConfiguration =
591
+ DEFAULT_INPUT_CONFIGURATION;
592
+
593
+ /** Name playlist — centered title + label + underline (screenshot) */
594
+ export const VARIANT_NAME_PLAYLIST_INPUT: InputConfiguration = {
595
+ background: {
596
+ defaultColor: "transparent",
597
+ focusedColor: "transparent",
598
+ borderColor: "rgba(255, 255, 255, 0.35)",
599
+ focusedBorderColor: "rgba(255, 255, 255, 0.55)",
600
+ borderWidth: 0,
601
+ underlineHeight: 1,
602
+ cornerRadius: 0,
603
+ paddingTop: 0,
604
+ paddingRight: 0,
605
+ paddingBottom: 10,
606
+ paddingLeft: 0,
607
+ horizontalGutter: 0,
608
+ verticalGutter: 16,
609
+ height: 0,
610
+ },
611
+ label: {
612
+ enabled: true,
613
+ fontColor: "rgba(142, 142, 147, 1)",
614
+ iosFontFamily: "SFProText-Regular",
615
+ androidFontFamily: "Roboto",
616
+ fontSize: 15,
617
+ lineHeight: 20,
618
+ iosLetterSpacing: -0.2,
619
+ androidLetterSpacing: 0,
620
+ textTransform: "default",
621
+ },
622
+ text: {
623
+ fontColor: "#FFFFFF",
624
+ focusedFontColor: "#FFFFFF",
625
+ placeholderColor: "rgba(142, 142, 147, 1)",
626
+ cursorColor: "#FFFFFF",
627
+ iosFontFamily: "SFProText-Bold",
628
+ androidFontFamily: "Roboto-Bold",
629
+ fontSize: 32,
630
+ lineHeight: 40,
631
+ iosLetterSpacing: -0.6,
632
+ androidLetterSpacing: 0,
633
+ textTransform: "default",
634
+ alignment: "center",
635
+ },
636
+ icons: {
637
+ width: 20,
638
+ height: 20,
639
+ leadingIcon: { enabled: false },
640
+ trailingIcon: {
641
+ enabled: false,
642
+ backgroundColor: "rgba(58, 58, 60, 1)",
643
+ focusedBackgroundColor: "rgba(72, 72, 74, 1)",
644
+ },
645
+ },
646
+ };
647
+
648
+ /** Text only search pill — no leading icon */
649
+ export const VARIANT_TEXT_ONLY_INPUT: InputConfiguration = {
650
+ background: DEFAULT_BACKGROUND_CONFIGURATION,
651
+ label: DEFAULT_LABEL_CONFIGURATION,
652
+ text: DEFAULT_TEXT_CONFIGURATION,
653
+ icons: {
654
+ ...DEFAULT_ICONS_CONFIGURATION,
655
+ leadingIcon: { enabled: false },
656
+ },
657
+ };
658
+
659
+ /** Search without clear button */
660
+ export const VARIANT_SEARCH_WITHOUT_CLEAR: InputConfiguration = {
661
+ background: DEFAULT_BACKGROUND_CONFIGURATION,
662
+ label: DEFAULT_LABEL_CONFIGURATION,
663
+ text: DEFAULT_TEXT_CONFIGURATION,
664
+ icons: {
665
+ ...DEFAULT_ICONS_CONFIGURATION,
666
+ trailingIcon: {
667
+ ...DEFAULT_ICONS_CONFIGURATION.trailingIcon,
668
+ enabled: false,
669
+ },
670
+ },
671
+ };