@applicaster/zapp-react-native-ui-components 15.1.4 → 16.0.0-alpha.1042942411

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 (202) hide show
  1. package/Components/BackgroundImage/index.ts +1 -8
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/FocusableWrapper.tsx +3 -0
  4. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  5. package/Components/CellRendererResolver/index.ts +1 -1
  6. package/Components/Focusable/Focusable.tsx +4 -2
  7. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  8. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  9. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  11. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  12. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  24. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  25. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  26. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  27. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  28. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  29. package/Components/Layout/TV/index.tsx +3 -4
  30. package/Components/Layout/TV/index.web.tsx +3 -4
  31. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  32. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +27 -48
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +213 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  39. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  40. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  41. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  42. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +198 -0
  43. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  44. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  45. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  46. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +217 -46
  47. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +50 -119
  48. package/Components/MasterCell/DefaultComponents/Button.tsx +50 -58
  49. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  50. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.android.tv.tsx +58 -0
  51. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  52. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  53. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  54. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  55. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/__snapshots__/index.test.tsx.snap +18 -0
  56. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/index.test.tsx +90 -0
  57. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +13 -14
  58. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  59. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  60. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  61. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  62. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  63. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  64. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  65. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  66. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  67. package/Components/MasterCell/DefaultComponents/PressableView.tsx +54 -0
  68. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +46 -50
  69. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +47 -39
  70. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  71. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +19 -6
  72. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +15 -0
  73. package/Components/MasterCell/DefaultComponents/Text/index.tsx +45 -15
  74. package/Components/MasterCell/DefaultComponents/UIKitAdapters/Badge.tsx +49 -0
  75. package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +122 -0
  76. package/Components/MasterCell/DefaultComponents/__tests__/Button.test.tsx +186 -0
  77. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  78. package/Components/MasterCell/DefaultComponents/__tests__/DynamicBadge.test.tsx +156 -0
  79. package/Components/MasterCell/DefaultComponents/__tests__/__snapshots__/ActionButton.test.tsx.snap +60 -0
  80. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +196 -67
  81. package/Components/MasterCell/DefaultComponents/index.ts +13 -3
  82. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +189 -0
  83. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  84. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +33 -0
  85. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +159 -0
  86. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +18 -0
  87. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  88. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +39 -0
  89. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +402 -0
  90. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  91. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +354 -0
  92. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  93. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +129 -0
  94. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +123 -0
  95. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  96. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  97. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  98. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  99. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  100. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  101. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  102. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  103. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  104. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  105. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  106. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  107. package/Components/MasterCell/README.md +4 -0
  108. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  109. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  110. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  111. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  112. package/Components/MasterCell/dataAdapter.ts +4 -1
  113. package/Components/MasterCell/elementMapper.tsx +54 -7
  114. package/Components/MasterCell/hooks/index.ts +2 -0
  115. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  116. package/Components/MasterCell/index.tsx +20 -4
  117. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  118. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  119. package/Components/MasterCell/utils/index.ts +82 -16
  120. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -69
  121. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +25 -47
  122. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +122 -39
  123. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  124. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +257 -88
  125. package/Components/ModalComponent/AudioPlayer/Components/index.ts +6 -4
  126. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  127. package/Components/ModalComponent/BottomSheetModalContent.tsx +17 -10
  128. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  129. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  130. package/Components/OfflineHandler/__tests__/index.test.tsx +101 -32
  131. package/Components/OfflineHandler/hooks.ts +123 -0
  132. package/Components/OfflineHandler/index.tsx +51 -73
  133. package/Components/OfflineHandler/utils/index.ts +1 -13
  134. package/Components/PlayerContainer/PlayerContainer.tsx +6 -5
  135. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +2 -4
  136. package/Components/PreloaderWrapper/index.tsx +1 -1
  137. package/Components/River/ComponentsMap/ComponentsMap.tsx +8 -21
  138. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  139. package/Components/River/RefreshControl.tsx +19 -105
  140. package/Components/River/River.tsx +9 -82
  141. package/Components/River/TV/River.tsx +31 -14
  142. package/Components/River/TV/index.tsx +8 -4
  143. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  144. package/Components/River/TV/utils/index.ts +4 -0
  145. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  146. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +4 -0
  147. package/Components/River/__tests__/componentsMap.test.js +38 -0
  148. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  149. package/Components/River/hooks/index.ts +1 -0
  150. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  151. package/Components/Screen/TV/index.web.tsx +4 -2
  152. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  153. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  154. package/Components/Screen/hooks.ts +2 -3
  155. package/Components/Screen/index.tsx +2 -3
  156. package/Components/Screen/orientationHandler.ts +3 -3
  157. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  158. package/Components/ScreenResolver/index.tsx +9 -5
  159. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  160. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  161. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  162. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
  163. package/Components/Tabs/TabContent.tsx +7 -4
  164. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  165. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  166. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  167. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  168. package/Components/TopCutoffOverlay/index.tsx +55 -0
  169. package/Components/Transitioner/index.js +3 -3
  170. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  171. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  172. package/Components/VideoModal/utils.ts +12 -10
  173. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  174. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  175. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  176. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  177. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  178. package/Contexts/ScreenContext/index.tsx +25 -18
  179. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  180. package/Decorators/Analytics/index.tsx +6 -5
  181. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  182. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  183. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  184. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  185. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  186. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  187. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  188. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  189. package/Helpers/DataSourceHelper/index.ts +19 -0
  190. package/package.json +5 -5
  191. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  192. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  193. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  194. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  195. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  196. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -67
  197. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  198. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  199. package/Helpers/DataSourceHelper/index.js +0 -19
  200. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  201. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  202. /package/Components/BackgroundImage/{BackgroundImage.tv.web.tsx → BackgroundImage.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
+ };