zkit-ui 1.0.0

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 (267) hide show
  1. package/README.md +267 -0
  2. package/dist/ComponentLibProvider.d.ts +15 -0
  3. package/dist/ComponentLibProvider.d.ts.map +1 -0
  4. package/dist/ComponentLibProvider.js +18 -0
  5. package/dist/assets/animations/spinner.json +1 -0
  6. package/dist/assets/icons/check.svg +4 -0
  7. package/dist/assets/icons/toast/error.webp +0 -0
  8. package/dist/assets/icons/toast/success.webp +0 -0
  9. package/dist/assets/icons/toast/warning.webp +0 -0
  10. package/dist/config.d.ts +16 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +42 -0
  13. package/dist/i18n/I18nProvider.d.ts +11 -0
  14. package/dist/i18n/I18nProvider.d.ts.map +1 -0
  15. package/dist/i18n/I18nProvider.js +84 -0
  16. package/dist/i18n/locale.d.ts +7 -0
  17. package/dist/i18n/locale.d.ts.map +1 -0
  18. package/dist/i18n/locale.js +87 -0
  19. package/dist/i18n/locales/de.d.ts +51 -0
  20. package/dist/i18n/locales/de.d.ts.map +1 -0
  21. package/dist/i18n/locales/de.js +62 -0
  22. package/dist/i18n/locales/en-US.d.ts +51 -0
  23. package/dist/i18n/locales/en-US.d.ts.map +1 -0
  24. package/dist/i18n/locales/en-US.js +62 -0
  25. package/dist/i18n/locales/index.d.ts +9 -0
  26. package/dist/i18n/locales/index.d.ts.map +1 -0
  27. package/dist/i18n/locales/index.js +23 -0
  28. package/dist/i18n/locales/ja.d.ts +51 -0
  29. package/dist/i18n/locales/ja.d.ts.map +1 -0
  30. package/dist/i18n/locales/ja.js +62 -0
  31. package/dist/i18n/locales/zh-CN.d.ts +51 -0
  32. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  33. package/dist/i18n/locales/zh-CN.js +62 -0
  34. package/dist/i18n/locales/zh-TW.d.ts +51 -0
  35. package/dist/i18n/locales/zh-TW.d.ts.map +1 -0
  36. package/dist/i18n/locales/zh-TW.js +62 -0
  37. package/dist/i18n/types.d.ts +11 -0
  38. package/dist/i18n/types.d.ts.map +1 -0
  39. package/dist/i18n/types.js +2 -0
  40. package/dist/i18n/useI18n.d.ts +2 -0
  41. package/dist/i18n/useI18n.d.ts.map +1 -0
  42. package/dist/i18n/useI18n.js +41 -0
  43. package/dist/index.d.ts +57 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +90 -0
  46. package/dist/services/ActionDialogService/ActionDialog.d.ts +4 -0
  47. package/dist/services/ActionDialogService/ActionDialog.d.ts.map +1 -0
  48. package/dist/services/ActionDialogService/ActionDialog.js +531 -0
  49. package/dist/services/ActionDialogService/index.d.ts +4 -0
  50. package/dist/services/ActionDialogService/index.d.ts.map +1 -0
  51. package/dist/services/ActionDialogService/index.js +8 -0
  52. package/dist/services/ActionDialogService/service.d.ts +7 -0
  53. package/dist/services/ActionDialogService/service.d.ts.map +1 -0
  54. package/dist/services/ActionDialogService/service.js +309 -0
  55. package/dist/services/ActionDialogService/shared.d.ts +17 -0
  56. package/dist/services/ActionDialogService/shared.d.ts.map +1 -0
  57. package/dist/services/ActionDialogService/shared.js +167 -0
  58. package/dist/services/ActionDialogService/types.d.ts +160 -0
  59. package/dist/services/ActionDialogService/types.d.ts.map +1 -0
  60. package/dist/services/ActionDialogService/types.js +2 -0
  61. package/dist/services/CardToastService/index.d.ts +115 -0
  62. package/dist/services/CardToastService/index.d.ts.map +1 -0
  63. package/dist/services/CardToastService/index.js +899 -0
  64. package/dist/services/ImagePreviewService/index.d.ts +179 -0
  65. package/dist/services/ImagePreviewService/index.d.ts.map +1 -0
  66. package/dist/services/ImagePreviewService/index.js +1383 -0
  67. package/dist/services/LoadingService/index.d.ts +146 -0
  68. package/dist/services/LoadingService/index.d.ts.map +1 -0
  69. package/dist/services/LoadingService/index.js +827 -0
  70. package/dist/services/PermissionPurposeDialogService/index.d.ts +99 -0
  71. package/dist/services/PermissionPurposeDialogService/index.d.ts.map +1 -0
  72. package/dist/services/PermissionPurposeDialogService/index.js +360 -0
  73. package/dist/services/PickerService/index.d.ts +128 -0
  74. package/dist/services/PickerService/index.d.ts.map +1 -0
  75. package/dist/services/PickerService/index.js +250 -0
  76. package/dist/theme/ThemeProvider.d.ts +15 -0
  77. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  78. package/dist/theme/ThemeProvider.js +61 -0
  79. package/dist/theme/defaultTheme.d.ts +3 -0
  80. package/dist/theme/defaultTheme.d.ts.map +1 -0
  81. package/dist/theme/defaultTheme.js +28 -0
  82. package/dist/theme/mergeTheme.d.ts +3 -0
  83. package/dist/theme/mergeTheme.d.ts.map +1 -0
  84. package/dist/theme/mergeTheme.js +19 -0
  85. package/dist/theme/types.d.ts +17 -0
  86. package/dist/theme/types.d.ts.map +1 -0
  87. package/dist/theme/types.js +2 -0
  88. package/dist/theme/useTheme.d.ts +2 -0
  89. package/dist/theme/useTheme.d.ts.map +1 -0
  90. package/dist/theme/useTheme.js +46 -0
  91. package/dist/ui/Accordion/index.d.ts +99 -0
  92. package/dist/ui/Accordion/index.d.ts.map +1 -0
  93. package/dist/ui/Accordion/index.js +777 -0
  94. package/dist/ui/AddressCascader/index.d.ts +58 -0
  95. package/dist/ui/AddressCascader/index.d.ts.map +1 -0
  96. package/dist/ui/AddressCascader/index.js +710 -0
  97. package/dist/ui/BetweenTime/index.d.ts +82 -0
  98. package/dist/ui/BetweenTime/index.d.ts.map +1 -0
  99. package/dist/ui/BetweenTime/index.js +702 -0
  100. package/dist/ui/Button/index.d.ts +93 -0
  101. package/dist/ui/Button/index.d.ts.map +1 -0
  102. package/dist/ui/Button/index.js +832 -0
  103. package/dist/ui/Checkbox/index.d.ts +103 -0
  104. package/dist/ui/Checkbox/index.d.ts.map +1 -0
  105. package/dist/ui/Checkbox/index.js +694 -0
  106. package/dist/ui/DatePicker/index.d.ts +5 -0
  107. package/dist/ui/DatePicker/index.d.ts.map +1 -0
  108. package/dist/ui/DatePicker/index.js +157 -0
  109. package/dist/ui/DatePicker/types.d.ts +99 -0
  110. package/dist/ui/DatePicker/types.d.ts.map +1 -0
  111. package/dist/ui/DatePicker/types.js +2 -0
  112. package/dist/ui/DatePicker/utils.d.ts +32 -0
  113. package/dist/ui/DatePicker/utils.d.ts.map +1 -0
  114. package/dist/ui/DatePicker/utils.js +316 -0
  115. package/dist/ui/LinkedScroll/index.d.ts +78 -0
  116. package/dist/ui/LinkedScroll/index.d.ts.map +1 -0
  117. package/dist/ui/LinkedScroll/index.js +470 -0
  118. package/dist/ui/LoadingSpinner/index.d.ts +8 -0
  119. package/dist/ui/LoadingSpinner/index.d.ts.map +1 -0
  120. package/dist/ui/LoadingSpinner/index.js +122 -0
  121. package/dist/ui/Picker/actionBar.d.ts +14 -0
  122. package/dist/ui/Picker/actionBar.d.ts.map +1 -0
  123. package/dist/ui/Picker/actionBar.js +28 -0
  124. package/dist/ui/Picker/index.d.ts +5 -0
  125. package/dist/ui/Picker/index.d.ts.map +1 -0
  126. package/dist/ui/Picker/index.js +412 -0
  127. package/dist/ui/Picker/types.d.ts +80 -0
  128. package/dist/ui/Picker/types.d.ts.map +1 -0
  129. package/dist/ui/Picker/types.js +2 -0
  130. package/dist/ui/Picker/utils.d.ts +27 -0
  131. package/dist/ui/Picker/utils.d.ts.map +1 -0
  132. package/dist/ui/Picker/utils.js +182 -0
  133. package/dist/ui/Radio/index.d.ts +95 -0
  134. package/dist/ui/Radio/index.d.ts.map +1 -0
  135. package/dist/ui/Radio/index.js +633 -0
  136. package/dist/ui/Sheet/index.d.ts +12 -0
  137. package/dist/ui/Sheet/index.d.ts.map +1 -0
  138. package/dist/ui/Sheet/index.js +832 -0
  139. package/dist/ui/Sheet/nativeBottom.d.ts +52 -0
  140. package/dist/ui/Sheet/nativeBottom.d.ts.map +1 -0
  141. package/dist/ui/Sheet/nativeBottom.js +442 -0
  142. package/dist/ui/Sheet/nativeBottom.web.d.ts +18 -0
  143. package/dist/ui/Sheet/nativeBottom.web.d.ts.map +1 -0
  144. package/dist/ui/Sheet/nativeBottom.web.js +46 -0
  145. package/dist/ui/Sheet/slots.d.ts +5 -0
  146. package/dist/ui/Sheet/slots.d.ts.map +1 -0
  147. package/dist/ui/Sheet/slots.js +40 -0
  148. package/dist/ui/Sheet/types.d.ts +131 -0
  149. package/dist/ui/Sheet/types.d.ts.map +1 -0
  150. package/dist/ui/Sheet/types.js +2 -0
  151. package/dist/ui/SliderCaptcha/index.d.ts +57 -0
  152. package/dist/ui/SliderCaptcha/index.d.ts.map +1 -0
  153. package/dist/ui/SliderCaptcha/index.js +586 -0
  154. package/dist/ui/Switch/index.d.ts +72 -0
  155. package/dist/ui/Switch/index.d.ts.map +1 -0
  156. package/dist/ui/Switch/index.js +671 -0
  157. package/dist/ui/Text/index.d.ts +41 -0
  158. package/dist/ui/Text/index.d.ts.map +1 -0
  159. package/dist/ui/Text/index.js +325 -0
  160. package/dist/ui/TextInput/TextInputPrimitive.d.ts +8 -0
  161. package/dist/ui/TextInput/TextInputPrimitive.d.ts.map +1 -0
  162. package/dist/ui/TextInput/TextInputPrimitive.js +54 -0
  163. package/dist/ui/TextInput/index.d.ts +89 -0
  164. package/dist/ui/TextInput/index.d.ts.map +1 -0
  165. package/dist/ui/TextInput/index.js +545 -0
  166. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts +39 -0
  167. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts.map +1 -0
  168. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.js +60 -0
  169. package/dist/ui/WheelColumn/constants.d.ts +2 -0
  170. package/dist/ui/WheelColumn/constants.d.ts.map +1 -0
  171. package/dist/ui/WheelColumn/constants.js +4 -0
  172. package/dist/ui/WheelColumn/index.d.ts +59 -0
  173. package/dist/ui/WheelColumn/index.d.ts.map +1 -0
  174. package/dist/ui/WheelColumn/index.js +547 -0
  175. package/ios/ZKitWheelPicker.h +18 -0
  176. package/ios/ZKitWheelPicker.mm +230 -0
  177. package/ios/ZKitWheelPickerLabel.h +10 -0
  178. package/ios/ZKitWheelPickerLabel.mm +31 -0
  179. package/ios/ZKitWheelPickerManager.h +5 -0
  180. package/ios/ZKitWheelPickerManager.mm +53 -0
  181. package/package.json +75 -0
  182. package/react-native.config.js +10 -0
  183. package/src/ComponentLibProvider.tsx +56 -0
  184. package/src/assets/animations/spinner.json +1 -0
  185. package/src/assets/icons/check.svg +4 -0
  186. package/src/assets/icons/toast/error.webp +0 -0
  187. package/src/assets/icons/toast/success.webp +0 -0
  188. package/src/assets/icons/toast/warning.webp +0 -0
  189. package/src/config.ts +65 -0
  190. package/src/i18n/I18nProvider.tsx +69 -0
  191. package/src/i18n/locale.ts +108 -0
  192. package/src/i18n/locales/de.ts +67 -0
  193. package/src/i18n/locales/en-US.ts +67 -0
  194. package/src/i18n/locales/index.ts +19 -0
  195. package/src/i18n/locales/ja.ts +67 -0
  196. package/src/i18n/locales/zh-CN.ts +67 -0
  197. package/src/i18n/locales/zh-TW.ts +67 -0
  198. package/src/i18n/types.ts +21 -0
  199. package/src/i18n/useI18n.ts +7 -0
  200. package/src/index.ts +395 -0
  201. package/src/services/ActionDialogService/ActionDialog.tsx +756 -0
  202. package/src/services/ActionDialogService/README.md +146 -0
  203. package/src/services/ActionDialogService/index.tsx +39 -0
  204. package/src/services/ActionDialogService/service.tsx +355 -0
  205. package/src/services/ActionDialogService/shared.ts +204 -0
  206. package/src/services/ActionDialogService/types.ts +197 -0
  207. package/src/services/CardToastService/README.md +128 -0
  208. package/src/services/CardToastService/index.tsx +1284 -0
  209. package/src/services/ImagePreviewService/README.md +67 -0
  210. package/src/services/ImagePreviewService/index.tsx +2148 -0
  211. package/src/services/LoadingService/README.md +128 -0
  212. package/src/services/LoadingService/index.tsx +1113 -0
  213. package/src/services/PermissionPurposeDialogService/README.md +80 -0
  214. package/src/services/PermissionPurposeDialogService/index.tsx +449 -0
  215. package/src/services/PickerService/README.md +154 -0
  216. package/src/services/PickerService/index.tsx +410 -0
  217. package/src/theme/ThemeProvider.tsx +40 -0
  218. package/src/theme/defaultTheme.ts +30 -0
  219. package/src/theme/mergeTheme.ts +16 -0
  220. package/src/theme/types.ts +36 -0
  221. package/src/theme/useTheme.ts +10 -0
  222. package/src/types/svg.d.ts +9 -0
  223. package/src/types/vant-area-data.d.ts +19 -0
  224. package/src/ui/Accordion/README.md +67 -0
  225. package/src/ui/Accordion/index.tsx +1221 -0
  226. package/src/ui/AddressCascader/README.md +144 -0
  227. package/src/ui/AddressCascader/index.tsx +1084 -0
  228. package/src/ui/BetweenTime/README.md +490 -0
  229. package/src/ui/BetweenTime/index.tsx +1001 -0
  230. package/src/ui/Button/README.md +186 -0
  231. package/src/ui/Button/index.tsx +1356 -0
  232. package/src/ui/Checkbox/README.md +211 -0
  233. package/src/ui/Checkbox/index.tsx +1231 -0
  234. package/src/ui/DatePicker/README.md +117 -0
  235. package/src/ui/DatePicker/index.tsx +295 -0
  236. package/src/ui/DatePicker/types.ts +115 -0
  237. package/src/ui/DatePicker/utils.ts +405 -0
  238. package/src/ui/LinkedScroll/README.md +55 -0
  239. package/src/ui/LinkedScroll/index.tsx +809 -0
  240. package/src/ui/LoadingSpinner/index.tsx +104 -0
  241. package/src/ui/Picker/README.md +190 -0
  242. package/src/ui/Picker/actionBar.tsx +70 -0
  243. package/src/ui/Picker/index.tsx +694 -0
  244. package/src/ui/Picker/types.ts +94 -0
  245. package/src/ui/Picker/utils.ts +239 -0
  246. package/src/ui/Radio/README.md +207 -0
  247. package/src/ui/Radio/index.tsx +1117 -0
  248. package/src/ui/Sheet/README.md +82 -0
  249. package/src/ui/Sheet/index.tsx +1163 -0
  250. package/src/ui/Sheet/nativeBottom.tsx +683 -0
  251. package/src/ui/Sheet/nativeBottom.web.tsx +40 -0
  252. package/src/ui/Sheet/slots.tsx +75 -0
  253. package/src/ui/Sheet/types.ts +195 -0
  254. package/src/ui/SliderCaptcha/README.md +61 -0
  255. package/src/ui/SliderCaptcha/index.tsx +816 -0
  256. package/src/ui/Switch/README.md +143 -0
  257. package/src/ui/Switch/index.tsx +1092 -0
  258. package/src/ui/Text/README.md +157 -0
  259. package/src/ui/Text/index.tsx +450 -0
  260. package/src/ui/TextInput/README.md +197 -0
  261. package/src/ui/TextInput/TextInputPrimitive.tsx +44 -0
  262. package/src/ui/TextInput/index.tsx +942 -0
  263. package/src/ui/WheelColumn/README.md +104 -0
  264. package/src/ui/WheelColumn/ZKitWheelPickerNativeComponent.tsx +68 -0
  265. package/src/ui/WheelColumn/constants.ts +1 -0
  266. package/src/ui/WheelColumn/index.tsx +826 -0
  267. package/zkit-ui.podspec +19 -0
@@ -0,0 +1,942 @@
1
+ import MaterialIcons from '@expo/vector-icons/MaterialIcons';
2
+ import * as React from 'react';
3
+ import type {
4
+ ColorValue,
5
+ DimensionValue,
6
+ GestureResponderEvent,
7
+ StyleProp,
8
+ TextStyle,
9
+ ViewStyle,
10
+ } from 'react-native';
11
+ import {
12
+ Platform,
13
+ Pressable,
14
+ processColor,
15
+ StyleSheet,
16
+ View,
17
+ } from 'react-native';
18
+ import { wp } from 'zkit-tools';
19
+ import { useI18n } from '../../i18n/useI18n';
20
+ import type { Theme } from '../../theme/types';
21
+ import { useTheme } from '../../theme/useTheme';
22
+ import { Text } from '../Text';
23
+ import {
24
+ TextInputPrimitive,
25
+ type TextInputPrimitiveProps,
26
+ type TextInputPrimitiveRef,
27
+ } from './TextInputPrimitive';
28
+ export {
29
+ TextInputPrimitive,
30
+ type TextInputPrimitiveProps,
31
+ type TextInputPrimitiveRef,
32
+ } from './TextInputPrimitive';
33
+
34
+ export type TextInputVariant = 'outline' | 'filled' | 'plain';
35
+ export type TextInputTone = 'primary' | 'neutral' | 'success' | 'warning' | 'danger' | 'info';
36
+ export type TextInputStatus = 'default' | 'success' | 'warning' | 'error';
37
+ export type TextInputSize = 'sm' | 'md' | 'lg';
38
+
39
+ export type TextInputLayout = {
40
+ width?: DimensionValue;
41
+ minWidth?: DimensionValue;
42
+ maxWidth?: DimensionValue;
43
+ height?: DimensionValue;
44
+ minHeight?: number;
45
+ maxHeight?: number;
46
+ paddingHorizontal?: number;
47
+ paddingVertical?: number;
48
+ gap?: number;
49
+ radius?: number;
50
+ textSize?: number;
51
+ textLineHeight?: number;
52
+ };
53
+
54
+ export type TextInputColors = {
55
+ background?: string;
56
+ border?: string;
57
+ text?: string;
58
+ placeholder?: ColorValue;
59
+ label?: string;
60
+ description?: string;
61
+ error?: string;
62
+ success?: string;
63
+ warning?: string;
64
+ icon?: string;
65
+ disabledBackground?: string;
66
+ disabledBorder?: string;
67
+ disabledText?: string;
68
+ };
69
+
70
+ type NativeTextInputProps = Omit<
71
+ TextInputPrimitiveProps,
72
+ | 'defaultValue'
73
+ | 'editable'
74
+ | 'onChange'
75
+ | 'onChangeText'
76
+ | 'readOnly'
77
+ | 'style'
78
+ | 'value'
79
+ >;
80
+
81
+ type SubmitEditingEvent = Parameters<
82
+ NonNullable<TextInputPrimitiveProps['onSubmitEditing']>
83
+ >[0];
84
+
85
+ export type TextInputRef = TextInputPrimitiveRef;
86
+
87
+ export interface TextInputProps extends NativeTextInputProps {
88
+ value?: string;
89
+ defaultValue?: string;
90
+ onChange?: (value: string) => void;
91
+ onNativeChange?: TextInputPrimitiveProps['onChange'];
92
+ onSubmit?: (value: string, event: SubmitEditingEvent) => void;
93
+
94
+ disabled?: boolean;
95
+ readOnly?: boolean;
96
+ required?: boolean;
97
+ invalid?: boolean;
98
+
99
+ variant?: TextInputVariant;
100
+ tone?: TextInputTone;
101
+ status?: TextInputStatus;
102
+ size?: TextInputSize;
103
+ color?: string;
104
+ colors?: TextInputColors;
105
+ layout?: TextInputLayout;
106
+
107
+ label?: React.ReactNode;
108
+ labelAction?: React.ReactNode;
109
+ description?: React.ReactNode;
110
+ error?: React.ReactNode;
111
+
112
+ prefix?: React.ReactNode;
113
+ suffix?: React.ReactNode;
114
+ clearable?: boolean;
115
+ clearIcon?: React.ReactNode;
116
+ clearAccessibilityLabel?: string;
117
+ onClear?: () => void;
118
+
119
+ minRows?: number;
120
+ maxRows?: number;
121
+ showCount?: boolean;
122
+ renderCount?: (info: { count: number; maxLength?: number }) => React.ReactNode;
123
+
124
+ style?: StyleProp<ViewStyle>;
125
+ fieldStyle?: StyleProp<ViewStyle>;
126
+ inputStyle?: StyleProp<TextStyle>;
127
+ labelStyle?: StyleProp<TextStyle>;
128
+ descriptionStyle?: StyleProp<TextStyle>;
129
+ errorStyle?: StyleProp<TextStyle>;
130
+ countStyle?: StyleProp<TextStyle>;
131
+ prefixStyle?: StyleProp<ViewStyle>;
132
+ suffixStyle?: StyleProp<ViewStyle>;
133
+ clearButtonStyle?: StyleProp<ViewStyle>;
134
+ }
135
+
136
+ type TextInputMetrics = {
137
+ minHeight: number;
138
+ paddingHorizontal: number;
139
+ paddingVertical: number;
140
+ gap: number;
141
+ radius: number;
142
+ textSize: number;
143
+ textLineHeight: number;
144
+ clearButtonSize: number;
145
+ clearIconSize: number;
146
+ borderWidth: number;
147
+ labelGap: number;
148
+ messageGap: number;
149
+ };
150
+
151
+ type TextInputVisualColors = {
152
+ backgroundColor: string;
153
+ borderColor: string;
154
+ textColor: string;
155
+ placeholderColor: ColorValue;
156
+ labelColor: string;
157
+ messageColor: string;
158
+ countColor: string;
159
+ iconColor: string;
160
+ };
161
+
162
+ const SEMANTIC_COLORS: Record<string, string> = {
163
+ danger: '#DC2626',
164
+ error: '#DC2626',
165
+ info: '#2563EB',
166
+ success: '#16A34A',
167
+ warn: '#D97706',
168
+ warning: '#D97706',
169
+ };
170
+
171
+ const SIZE_TOKENS = {
172
+ sm: {
173
+ minHeight: 40,
174
+ paddingHorizontal: 12,
175
+ paddingVertical: 8,
176
+ gap: 8,
177
+ radius: 10,
178
+ textSize: 14,
179
+ textLineHeight: 20,
180
+ clearButtonSize: 26,
181
+ clearIconSize: 16,
182
+ },
183
+ md: {
184
+ minHeight: 48,
185
+ paddingHorizontal: 14,
186
+ paddingVertical: 10,
187
+ gap: 10,
188
+ radius: 12,
189
+ textSize: 15,
190
+ textLineHeight: 21,
191
+ clearButtonSize: 28,
192
+ clearIconSize: 17,
193
+ },
194
+ lg: {
195
+ minHeight: 56,
196
+ paddingHorizontal: 16,
197
+ paddingVertical: 12,
198
+ gap: 10,
199
+ radius: 14,
200
+ textSize: 16,
201
+ textLineHeight: 22,
202
+ clearButtonSize: 30,
203
+ clearIconSize: 18,
204
+ },
205
+ } as const satisfies Record<
206
+ TextInputSize,
207
+ Omit<TextInputMetrics, 'borderWidth' | 'labelGap' | 'messageGap'>
208
+ >;
209
+
210
+ const WEB_INPUT_RESET_STYLE =
211
+ Platform.OS === 'web'
212
+ ? ({
213
+ outlineStyle: 'solid',
214
+ outlineWidth: 0,
215
+ } as TextStyle)
216
+ : null;
217
+ const IS_IOS = Platform.OS === 'ios';
218
+ const IS_WEB = Platform.OS === 'web';
219
+
220
+ type WebTextInputStyle = TextStyle & {
221
+ caretColor?: ColorValue;
222
+ };
223
+
224
+ function isPrimitiveNode(node: React.ReactNode): node is string | number {
225
+ return typeof node === 'string' || typeof node === 'number';
226
+ }
227
+
228
+ function extractReadableText(node: React.ReactNode): string | undefined {
229
+ if (node == null || typeof node === 'boolean') return undefined;
230
+ if (isPrimitiveNode(node)) return String(node);
231
+ if (Array.isArray(node)) {
232
+ const text = node
233
+ .map((item) => extractReadableText(item))
234
+ .filter(Boolean)
235
+ .join(' ')
236
+ .trim();
237
+ return text || undefined;
238
+ }
239
+ return undefined;
240
+ }
241
+
242
+ function isProcessableColor(color: string) {
243
+ return processColor(color) != null;
244
+ }
245
+
246
+ function colorToRgba(color: string, alpha: number) {
247
+ const processed = processColor(color);
248
+ if (typeof processed !== 'number') return undefined;
249
+
250
+ const normalized = processed >>> 0;
251
+ const r = (normalized >> 16) & 255;
252
+ const g = (normalized >> 8) & 255;
253
+ const b = normalized & 255;
254
+ const a = Math.max(0, Math.min(1, alpha));
255
+ return `rgba(${r},${g},${b},${a})`;
256
+ }
257
+
258
+ function resolveColorToken(input: string | undefined, fallback: string, theme: Theme) {
259
+ if (input == null) return fallback;
260
+ const key = input.trim();
261
+ if (!key) return fallback;
262
+
263
+ const resolved =
264
+ key === 'primary'
265
+ ? theme.colors.primary
266
+ : key === 'onPrimary'
267
+ ? theme.colors.onPrimary
268
+ : key === 'secondary' || key === 'neutral'
269
+ ? theme.colors.secondary
270
+ : key === 'onSecondary'
271
+ ? theme.colors.onSecondary
272
+ : key === 'surface'
273
+ ? theme.colors.surface
274
+ : key === 'onSurface'
275
+ ? theme.colors.onSurface
276
+ : key === 'border'
277
+ ? theme.colors.border
278
+ : key === 'muted'
279
+ ? theme.colors.muted
280
+ : key === 'disabled'
281
+ ? theme.colors.disabled
282
+ : SEMANTIC_COLORS[key] ?? key;
283
+
284
+ return isProcessableColor(resolved) ? resolved : fallback;
285
+ }
286
+
287
+ function resolveToneAccent(tone: TextInputTone, theme: Theme) {
288
+ if (tone === 'neutral') return theme.colors.onSurface;
289
+ if (tone === 'success') return SEMANTIC_COLORS.success;
290
+ if (tone === 'warning') return SEMANTIC_COLORS.warning;
291
+ if (tone === 'danger') return SEMANTIC_COLORS.danger;
292
+ if (tone === 'info') return SEMANTIC_COLORS.info;
293
+ return theme.colors.primary;
294
+ }
295
+
296
+ function resolveMetrics(size: TextInputSize, layout: TextInputLayout | undefined): TextInputMetrics {
297
+ const token = SIZE_TOKENS[size] ?? SIZE_TOKENS.md;
298
+ return {
299
+ minHeight: layout?.minHeight ?? wp(token.minHeight),
300
+ paddingHorizontal: layout?.paddingHorizontal ?? wp(token.paddingHorizontal),
301
+ paddingVertical: layout?.paddingVertical ?? wp(token.paddingVertical),
302
+ gap: layout?.gap ?? wp(token.gap),
303
+ radius: layout?.radius ?? wp(token.radius),
304
+ textSize: layout?.textSize ?? wp(token.textSize),
305
+ textLineHeight: layout?.textLineHeight ?? wp(token.textLineHeight),
306
+ clearButtonSize: wp(token.clearButtonSize),
307
+ clearIconSize: wp(token.clearIconSize),
308
+ borderWidth: wp(1),
309
+ labelGap: wp(6),
310
+ messageGap: wp(6),
311
+ };
312
+ }
313
+
314
+ function normalizeRows(value: number | undefined, fallback: number) {
315
+ if (value == null) return fallback;
316
+ if (!Number.isFinite(value)) return fallback;
317
+ return Math.max(1, Math.round(value));
318
+ }
319
+
320
+ function resolveVisualColors({
321
+ colors,
322
+ disabled,
323
+ status,
324
+ theme,
325
+ variant,
326
+ }: {
327
+ colors?: TextInputColors;
328
+ disabled: boolean;
329
+ status: TextInputStatus;
330
+ theme: Theme;
331
+ variant: TextInputVariant;
332
+ }): TextInputVisualColors {
333
+ const subtleSurface = colorToRgba(theme.colors.onSurface, 0.04) ?? theme.colors.secondary;
334
+ const statusColor =
335
+ status === 'error'
336
+ ? colors?.error ?? SEMANTIC_COLORS.danger
337
+ : status === 'warning'
338
+ ? colors?.warning ?? SEMANTIC_COLORS.warning
339
+ : status === 'success'
340
+ ? colors?.success ?? SEMANTIC_COLORS.success
341
+ : undefined;
342
+
343
+ const baseBackground =
344
+ variant === 'plain'
345
+ ? 'transparent'
346
+ : variant === 'filled'
347
+ ? subtleSurface
348
+ : theme.colors.surface;
349
+ const baseBorder = variant === 'plain' ? 'transparent' : theme.colors.border;
350
+
351
+ const borderColor = statusColor ?? colors?.border ?? baseBorder;
352
+
353
+ if (disabled) {
354
+ return {
355
+ backgroundColor: colors?.disabledBackground ?? (variant === 'plain' ? 'transparent' : subtleSurface),
356
+ borderColor: colors?.disabledBorder ?? (variant === 'plain' ? 'transparent' : theme.colors.border),
357
+ textColor: colors?.disabledText ?? theme.colors.disabled,
358
+ placeholderColor: colors?.placeholder ?? theme.colors.disabled,
359
+ labelColor: colors?.label ?? theme.colors.disabled,
360
+ messageColor: colors?.description ?? theme.colors.disabled,
361
+ countColor: colors?.description ?? theme.colors.disabled,
362
+ iconColor: colors?.icon ?? theme.colors.disabled,
363
+ };
364
+ }
365
+
366
+ return {
367
+ backgroundColor: colors?.background ?? baseBackground,
368
+ borderColor,
369
+ textColor: colors?.text ?? theme.colors.onSurface,
370
+ placeholderColor: colors?.placeholder ?? theme.colors.muted,
371
+ labelColor: colors?.label ?? theme.colors.onSurface,
372
+ messageColor: statusColor ?? colors?.description ?? theme.colors.muted,
373
+ countColor: colors?.description ?? theme.colors.muted,
374
+ iconColor: colors?.icon ?? theme.colors.muted,
375
+ };
376
+ }
377
+
378
+ function renderTextNode(
379
+ node: React.ReactNode,
380
+ props: React.ComponentPropsWithoutRef<typeof Text>
381
+ ) {
382
+ if (node == null || typeof node === 'boolean') return null;
383
+ if (isPrimitiveNode(node)) {
384
+ return <Text {...props}>{node}</Text>;
385
+ }
386
+ return node;
387
+ }
388
+
389
+ function renderAffix(
390
+ node: React.ReactNode,
391
+ color: string,
392
+ style: StyleProp<ViewStyle>,
393
+ textSize: number,
394
+ lineHeight: number
395
+ ) {
396
+ if (node == null || typeof node === 'boolean') return null;
397
+
398
+ return (
399
+ <View pointerEvents="box-none" style={[styles.affix, style]}>
400
+ {isPrimitiveNode(node) ? (
401
+ <Text
402
+ numberOfLines={1}
403
+ style={[
404
+ styles.affixText,
405
+ {
406
+ color,
407
+ fontSize: textSize,
408
+ lineHeight,
409
+ },
410
+ ]}
411
+ >
412
+ {node}
413
+ </Text>
414
+ ) : (
415
+ node
416
+ )}
417
+ </View>
418
+ );
419
+ }
420
+
421
+ function assignRef<T>(ref: React.ForwardedRef<T>, value: T | null) {
422
+ if (typeof ref === 'function') {
423
+ ref(value);
424
+ return;
425
+ }
426
+ if (ref) {
427
+ ref.current = value;
428
+ }
429
+ }
430
+
431
+ const TextInputBase = React.forwardRef<TextInputRef, TextInputProps>(function TextInput(
432
+ {
433
+ value,
434
+ defaultValue = '',
435
+ onChange,
436
+ onNativeChange,
437
+ onSubmit,
438
+ disabled = false,
439
+ readOnly = false,
440
+ required = false,
441
+ invalid,
442
+ variant = 'outline',
443
+ tone = 'primary',
444
+ status = 'default',
445
+ size = 'md',
446
+ color,
447
+ colors,
448
+ layout,
449
+ label,
450
+ labelAction,
451
+ description,
452
+ error,
453
+ prefix,
454
+ suffix,
455
+ clearable = false,
456
+ clearIcon,
457
+ clearAccessibilityLabel,
458
+ onClear,
459
+ minRows,
460
+ maxRows,
461
+ showCount = false,
462
+ renderCount,
463
+ style,
464
+ fieldStyle,
465
+ inputStyle,
466
+ labelStyle,
467
+ descriptionStyle,
468
+ errorStyle,
469
+ countStyle,
470
+ prefixStyle,
471
+ suffixStyle,
472
+ clearButtonStyle,
473
+ maxFontSizeMultiplier,
474
+ placeholderTextColor,
475
+ selectionColor,
476
+ cursorColor,
477
+ selectionHandleColor,
478
+ onBlur,
479
+ onFocus,
480
+ onSubmitEditing,
481
+ accessibilityLabel,
482
+ accessibilityHint,
483
+ accessibilityState,
484
+ maxLength,
485
+ multiline = false,
486
+ numberOfLines,
487
+ testID,
488
+ underlineColorAndroid,
489
+ ...nativeProps
490
+ },
491
+ forwardedRef
492
+ ) {
493
+ const theme = useTheme();
494
+ const { t } = useI18n();
495
+ const inputRef = React.useRef<TextInputRef | null>(null);
496
+ const isControlled = value !== undefined;
497
+ const initialValueRef = React.useRef(defaultValue);
498
+ const currentValueRef = React.useRef(isControlled ? value ?? '' : initialValueRef.current);
499
+ const shouldTrackValue = clearable || showCount || renderCount != null;
500
+ const hasSyncedUncontrolledValueRef = React.useRef(false);
501
+ const [, scheduleValueRender] = React.useReducer((revision: number) => revision + 1, 0);
502
+
503
+ if (isControlled) {
504
+ currentValueRef.current = value ?? '';
505
+ }
506
+
507
+ const shouldSyncUncontrolledValue =
508
+ !isControlled && (shouldTrackValue || hasSyncedUncontrolledValueRef.current);
509
+
510
+ React.useEffect(() => {
511
+ if (!isControlled && shouldTrackValue) {
512
+ hasSyncedUncontrolledValueRef.current = true;
513
+ }
514
+ }, [isControlled, shouldTrackValue]);
515
+
516
+ const setInputRef = React.useCallback(
517
+ (node: TextInputRef | null) => {
518
+ inputRef.current = node;
519
+ assignRef(forwardedRef, node);
520
+ },
521
+ [forwardedRef]
522
+ );
523
+
524
+ const interactive = !disabled && !readOnly;
525
+ const resolvedStatus: TextInputStatus = error != null || invalid ? 'error' : status;
526
+ const metrics = React.useMemo(() => resolveMetrics(size, layout), [layout, size]);
527
+ const accentColor = React.useMemo(
528
+ () => resolveColorToken(color, resolveToneAccent(tone, theme), theme),
529
+ [color, theme, tone]
530
+ );
531
+ const visualColors = React.useMemo(
532
+ () =>
533
+ resolveVisualColors({
534
+ colors,
535
+ disabled,
536
+ status: resolvedStatus,
537
+ theme,
538
+ variant,
539
+ }),
540
+ [colors, disabled, resolvedStatus, theme, variant]
541
+ );
542
+
543
+ const displayValue = isControlled ? value ?? '' : currentValueRef.current;
544
+ const nativeValue = isControlled || shouldSyncUncontrolledValue ? displayValue : undefined;
545
+ const nativeDefaultValue =
546
+ !isControlled && !shouldSyncUncontrolledValue ? initialValueRef.current : undefined;
547
+ const hasValue = displayValue.length > 0;
548
+ const count = displayValue.length;
549
+ const resolvedMinRows = multiline
550
+ ? normalizeRows(minRows ?? numberOfLines, 3)
551
+ : 1;
552
+ const resolvedMaxRows =
553
+ multiline && maxRows != null
554
+ ? Math.max(resolvedMinRows, normalizeRows(maxRows, resolvedMinRows))
555
+ : undefined;
556
+ const inputMinHeight = metrics.textLineHeight * resolvedMinRows;
557
+ const inputMaxHeight =
558
+ resolvedMaxRows == null ? undefined : metrics.textLineHeight * resolvedMaxRows;
559
+ const fieldPaddingHorizontal = variant === 'plain' ? 0 : metrics.paddingHorizontal;
560
+ const fieldPaddingVertical = variant === 'plain' ? 0 : metrics.paddingVertical;
561
+ const fieldBorderWidth = variant === 'plain' ? 0 : metrics.borderWidth;
562
+ const fieldMinHeight = Math.max(
563
+ metrics.minHeight,
564
+ inputMinHeight + fieldPaddingVertical * 2 + fieldBorderWidth * 2
565
+ );
566
+ const singleLineInputHeight = Math.max(
567
+ metrics.textLineHeight,
568
+ fieldMinHeight - fieldPaddingVertical * 2 - fieldBorderWidth * 2
569
+ );
570
+ const fieldMaxHeight =
571
+ layout?.maxHeight ??
572
+ (inputMaxHeight == null
573
+ ? undefined
574
+ : inputMaxHeight + fieldPaddingVertical * 2 + fieldBorderWidth * 2);
575
+ const rootSizeStyle = React.useMemo<ViewStyle>(
576
+ () => ({
577
+ ...(layout?.width !== undefined ? { width: layout.width } : undefined),
578
+ ...(layout?.minWidth !== undefined ? { minWidth: layout.minWidth } : undefined),
579
+ ...(layout?.maxWidth !== undefined ? { maxWidth: layout.maxWidth } : undefined),
580
+ }),
581
+ [layout?.maxWidth, layout?.minWidth, layout?.width]
582
+ );
583
+ const fieldSizeStyle = React.useMemo<ViewStyle>(
584
+ () => ({
585
+ ...(layout?.height !== undefined
586
+ ? { height: layout.height }
587
+ : multiline
588
+ ? undefined
589
+ : { height: fieldMinHeight }),
590
+ ...(multiline && fieldMaxHeight !== undefined ? { maxHeight: fieldMaxHeight } : undefined),
591
+ minHeight: fieldMinHeight,
592
+ paddingHorizontal: fieldPaddingHorizontal,
593
+ paddingVertical: fieldPaddingVertical,
594
+ }),
595
+ [
596
+ fieldMaxHeight,
597
+ fieldMinHeight,
598
+ fieldPaddingHorizontal,
599
+ fieldPaddingVertical,
600
+ layout?.height,
601
+ multiline,
602
+ ]
603
+ );
604
+ const fieldVisualStyle = React.useMemo<ViewStyle>(
605
+ () => ({
606
+ alignItems: multiline ? 'flex-start' : 'center',
607
+ backgroundColor: visualColors.backgroundColor,
608
+ borderColor: visualColors.borderColor,
609
+ borderRadius: variant === 'plain' ? 0 : metrics.radius,
610
+ borderWidth: fieldBorderWidth,
611
+ gap: metrics.gap,
612
+ }),
613
+ [
614
+ fieldBorderWidth,
615
+ metrics.gap,
616
+ metrics.radius,
617
+ multiline,
618
+ variant,
619
+ visualColors.backgroundColor,
620
+ visualColors.borderColor,
621
+ ]
622
+ );
623
+ const inputBaseStyle = React.useMemo<TextStyle>(
624
+ () => ({
625
+ backgroundColor: 'transparent',
626
+ borderWidth: 0,
627
+ color: visualColors.textColor,
628
+ fontSize: metrics.textSize,
629
+ includeFontPadding: false,
630
+ lineHeight: IS_IOS && !multiline ? undefined : metrics.textLineHeight,
631
+ maxHeight: multiline ? inputMaxHeight : undefined,
632
+ minHeight: multiline ? inputMinHeight : singleLineInputHeight,
633
+ padding: 0,
634
+ paddingBottom: 0,
635
+ paddingHorizontal: 0,
636
+ paddingTop: 0,
637
+ paddingVertical: 0,
638
+ textAlignVertical: multiline ? 'top' : 'center',
639
+ }),
640
+ [
641
+ inputMaxHeight,
642
+ inputMinHeight,
643
+ metrics.textLineHeight,
644
+ metrics.textSize,
645
+ multiline,
646
+ singleLineInputHeight,
647
+ visualColors.textColor,
648
+ ]
649
+ );
650
+ const finalSelectionColor = selectionColor ?? accentColor;
651
+ const finalCursorColor = cursorColor ?? finalSelectionColor;
652
+ const finalHandleColor = selectionHandleColor ?? finalSelectionColor;
653
+ const webCaretStyle = React.useMemo<WebTextInputStyle | null>(
654
+ () => (IS_WEB ? { caretColor: finalCursorColor } : null),
655
+ [finalCursorColor]
656
+ );
657
+ const computedAccessibilityLabel =
658
+ accessibilityLabel ?? extractReadableText(label) ?? nativeProps.placeholder;
659
+ const computedAccessibilityHint =
660
+ accessibilityHint ??
661
+ extractReadableText(resolvedStatus === 'error' ? error : description);
662
+ const finalClearAccessibilityLabel = clearAccessibilityLabel ?? t('textInput.clear');
663
+
664
+ const commitValue = React.useCallback(
665
+ (nextValue: string) => {
666
+ currentValueRef.current = nextValue;
667
+ if (shouldSyncUncontrolledValue) {
668
+ scheduleValueRender();
669
+ }
670
+ onChange?.(nextValue);
671
+ },
672
+ [onChange, shouldSyncUncontrolledValue]
673
+ );
674
+
675
+ const handleChangeText = React.useCallback(
676
+ (nextValue: string) => {
677
+ commitValue(nextValue);
678
+ },
679
+ [commitValue]
680
+ );
681
+
682
+ const handleNativeChange = React.useCallback(
683
+ (event: Parameters<NonNullable<TextInputPrimitiveProps['onChange']>>[0]) => {
684
+ onNativeChange?.(event);
685
+ },
686
+ [onNativeChange]
687
+ );
688
+
689
+ const handleFocus = React.useCallback(
690
+ (event: Parameters<NonNullable<TextInputPrimitiveProps['onFocus']>>[0]) => {
691
+ onFocus?.(event);
692
+ },
693
+ [onFocus]
694
+ );
695
+
696
+ const handleBlur = React.useCallback(
697
+ (event: Parameters<NonNullable<TextInputPrimitiveProps['onBlur']>>[0]) => {
698
+ onBlur?.(event);
699
+ },
700
+ [onBlur]
701
+ );
702
+
703
+ const handleSubmitEditing = React.useCallback(
704
+ (event: SubmitEditingEvent) => {
705
+ onSubmitEditing?.(event);
706
+ onSubmit?.(currentValueRef.current, event);
707
+ },
708
+ [onSubmit, onSubmitEditing]
709
+ );
710
+
711
+ const handleClear = React.useCallback(
712
+ (event: GestureResponderEvent) => {
713
+ event.preventDefault();
714
+ if (!interactive) return;
715
+ inputRef.current?.clear();
716
+ commitValue('');
717
+ onClear?.();
718
+ inputRef.current?.focus();
719
+ },
720
+ [commitValue, interactive, onClear]
721
+ );
722
+
723
+ const labelNode = renderTextNode(label, {
724
+ variant: 'label',
725
+ color: visualColors.labelColor,
726
+ weight: 'semibold',
727
+ style: [styles.labelText, labelStyle],
728
+ });
729
+ const messageNode =
730
+ resolvedStatus === 'error' && error != null
731
+ ? renderTextNode(error, {
732
+ variant: 'caption',
733
+ color: visualColors.messageColor,
734
+ style: [styles.messageText, errorStyle],
735
+ })
736
+ : renderTextNode(description, {
737
+ variant: 'caption',
738
+ color: visualColors.messageColor,
739
+ style: [styles.messageText, descriptionStyle],
740
+ });
741
+ const countNode =
742
+ showCount || renderCount
743
+ ? renderTextNode(
744
+ renderCount?.({ count, maxLength }) ??
745
+ (maxLength != null ? `${count}/${maxLength}` : String(count)),
746
+ {
747
+ variant: 'caption',
748
+ color: visualColors.countColor,
749
+ style: [styles.countText, countStyle],
750
+ }
751
+ )
752
+ : null;
753
+ const prefixNode = renderAffix(
754
+ prefix,
755
+ visualColors.iconColor,
756
+ prefixStyle,
757
+ metrics.textSize,
758
+ metrics.textLineHeight
759
+ );
760
+ const suffixNode = renderAffix(
761
+ suffix,
762
+ visualColors.iconColor,
763
+ suffixStyle,
764
+ metrics.textSize,
765
+ metrics.textLineHeight
766
+ );
767
+ const showClearButton = clearable && interactive && hasValue;
768
+
769
+ return (
770
+ <View style={[styles.root, rootSizeStyle, style]}>
771
+ {labelNode || labelAction ? (
772
+ <View style={[styles.labelRow, { marginBottom: metrics.labelGap }]}>
773
+ <View style={styles.labelContent}>
774
+ {labelNode}
775
+ {required && labelNode ? (
776
+ <Text
777
+ accessibilityElementsHidden
778
+ tone="danger"
779
+ importantForAccessibility="no"
780
+ style={styles.requiredMark}
781
+ >
782
+ *
783
+ </Text>
784
+ ) : null}
785
+ </View>
786
+ {labelAction ? <View style={styles.labelAction}>{labelAction}</View> : null}
787
+ </View>
788
+ ) : null}
789
+
790
+ <View style={[styles.field, fieldSizeStyle, fieldVisualStyle, fieldStyle]}>
791
+ {prefixNode}
792
+
793
+ <TextInputPrimitive
794
+ ref={setInputRef}
795
+ {...nativeProps}
796
+ accessibilityHint={computedAccessibilityHint}
797
+ accessibilityLabel={computedAccessibilityLabel}
798
+ accessibilityState={{
799
+ ...accessibilityState,
800
+ disabled,
801
+ }}
802
+ cursorColor={finalCursorColor}
803
+ defaultValue={nativeDefaultValue}
804
+ editable={interactive}
805
+ maxFontSizeMultiplier={maxFontSizeMultiplier}
806
+ maxLength={maxLength}
807
+ multiline={multiline}
808
+ numberOfLines={multiline ? resolvedMinRows : numberOfLines}
809
+ onBlur={handleBlur}
810
+ onChange={handleNativeChange}
811
+ onChangeText={handleChangeText}
812
+ onFocus={handleFocus}
813
+ onSubmitEditing={handleSubmitEditing}
814
+ placeholderTextColor={placeholderTextColor ?? visualColors.placeholderColor}
815
+ readOnly={readOnly || disabled}
816
+ selectionColor={finalSelectionColor}
817
+ selectionHandleColor={finalHandleColor}
818
+ style={[styles.input, inputBaseStyle, WEB_INPUT_RESET_STYLE, webCaretStyle, inputStyle]}
819
+ testID={testID}
820
+ underlineColorAndroid={underlineColorAndroid ?? 'transparent'}
821
+ value={nativeValue}
822
+ />
823
+
824
+ {showClearButton ? (
825
+ <Pressable
826
+ accessibilityLabel={finalClearAccessibilityLabel}
827
+ accessibilityRole="button"
828
+ hitSlop={{
829
+ bottom: wp(8),
830
+ left: wp(8),
831
+ right: wp(8),
832
+ top: wp(8),
833
+ }}
834
+ onPress={handleClear}
835
+ style={[
836
+ styles.clearButton,
837
+ {
838
+ height: metrics.clearButtonSize,
839
+ width: metrics.clearButtonSize,
840
+ },
841
+ clearButtonStyle,
842
+ ]}
843
+ >
844
+ {clearIcon ?? (
845
+ <MaterialIcons
846
+ color={visualColors.iconColor}
847
+ name="close"
848
+ size={metrics.clearIconSize}
849
+ />
850
+ )}
851
+ </Pressable>
852
+ ) : null}
853
+
854
+ {suffixNode}
855
+ </View>
856
+
857
+ {messageNode || countNode ? (
858
+ <View style={[styles.footerRow, { marginTop: metrics.messageGap }]}>
859
+ <View style={styles.messageSlot}>{messageNode}</View>
860
+ {countNode ? <View style={styles.countSlot}>{countNode}</View> : null}
861
+ </View>
862
+ ) : null}
863
+ </View>
864
+ );
865
+ });
866
+
867
+ TextInputBase.displayName = 'TextInput';
868
+
869
+ export const TextInput = Object.assign(TextInputBase, { State: TextInputPrimitive.State });
870
+
871
+ const styles = StyleSheet.create({
872
+ root: {
873
+ width: '100%',
874
+ },
875
+ labelRow: {
876
+ alignItems: 'center',
877
+ flexDirection: 'row',
878
+ justifyContent: 'space-between',
879
+ },
880
+ labelContent: {
881
+ alignItems: 'center',
882
+ flex: 1,
883
+ flexDirection: 'row',
884
+ minWidth: 0,
885
+ },
886
+ labelText: {
887
+ flexShrink: 1,
888
+ },
889
+ requiredMark: {
890
+ fontSize: wp(13),
891
+ fontWeight: '700',
892
+ lineHeight: wp(18),
893
+ marginLeft: wp(3),
894
+ },
895
+ labelAction: {
896
+ flexShrink: 0,
897
+ marginLeft: wp(12),
898
+ },
899
+ field: {
900
+ flexDirection: 'row',
901
+ overflow: 'hidden',
902
+ },
903
+ input: {
904
+ flex: 1,
905
+ margin: 0,
906
+ minWidth: 0,
907
+ },
908
+ affix: {
909
+ alignItems: 'center',
910
+ flexShrink: 0,
911
+ justifyContent: 'center',
912
+ minHeight: wp(24),
913
+ },
914
+ affixText: {
915
+ fontWeight: '500',
916
+ },
917
+ clearButton: {
918
+ alignItems: 'center',
919
+ borderRadius: wp(999),
920
+ flexShrink: 0,
921
+ justifyContent: 'center',
922
+ },
923
+ footerRow: {
924
+ alignItems: 'flex-start',
925
+ flexDirection: 'row',
926
+ justifyContent: 'space-between',
927
+ },
928
+ messageSlot: {
929
+ flex: 1,
930
+ minWidth: 0,
931
+ },
932
+ messageText: {
933
+ flexShrink: 1,
934
+ },
935
+ countSlot: {
936
+ flexShrink: 0,
937
+ marginLeft: wp(12),
938
+ },
939
+ countText: {
940
+ textAlign: 'right',
941
+ },
942
+ });