@applicaster/zapp-react-native-ui-components 16.0.0-rc.6 → 16.0.0-rc.61

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