react-native-phone-country-input 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 (172) hide show
  1. package/README.md +477 -0
  2. package/lib/commonjs/CountrySelector/CountrySelector.js +74 -0
  3. package/lib/commonjs/CountrySelector/CountrySelector.js.map +1 -0
  4. package/lib/commonjs/CountrySelector/CountrySelectorModal.js +267 -0
  5. package/lib/commonjs/CountrySelector/CountrySelectorModal.js.map +1 -0
  6. package/lib/commonjs/Keyboard/Keyboard.js +316 -0
  7. package/lib/commonjs/Keyboard/Keyboard.js.map +1 -0
  8. package/lib/commonjs/Keyboard/KeyboardToolbar.js +70 -0
  9. package/lib/commonjs/Keyboard/KeyboardToolbar.js.map +1 -0
  10. package/lib/commonjs/Keyboard/KeypadButton.js +66 -0
  11. package/lib/commonjs/Keyboard/KeypadButton.js.map +1 -0
  12. package/lib/commonjs/Keyboard/KeypadButtonContainer.js +65 -0
  13. package/lib/commonjs/Keyboard/KeypadButtonContainer.js.map +1 -0
  14. package/lib/commonjs/Keyboard/KeypadRow.js +34 -0
  15. package/lib/commonjs/Keyboard/KeypadRow.js.map +1 -0
  16. package/lib/commonjs/PhoneCountryInput/PhoneCountryInput.js +86 -0
  17. package/lib/commonjs/PhoneCountryInput/PhoneCountryInput.js.map +1 -0
  18. package/lib/commonjs/PhoneNumberField.js +36 -0
  19. package/lib/commonjs/PhoneNumberField.js.map +1 -0
  20. package/lib/commonjs/Styling/Colors.js +197 -0
  21. package/lib/commonjs/Styling/Colors.js.map +1 -0
  22. package/lib/commonjs/Styling/Sizing.js +111 -0
  23. package/lib/commonjs/Styling/Sizing.js.map +1 -0
  24. package/lib/commonjs/consts/KEYBOARD_LAYOUT.js +45 -0
  25. package/lib/commonjs/consts/KEYBOARD_LAYOUT.js.map +1 -0
  26. package/lib/commonjs/consts/regions.js +1503 -0
  27. package/lib/commonjs/consts/regions.js.map +1 -0
  28. package/lib/commonjs/enum/CountryIds.js +264 -0
  29. package/lib/commonjs/enum/CountryIds.js.map +1 -0
  30. package/lib/commonjs/hooks/UsePhoneFieldState.js +237 -0
  31. package/lib/commonjs/hooks/UsePhoneFieldState.js.map +1 -0
  32. package/lib/commonjs/index.js +56 -0
  33. package/lib/commonjs/index.js.map +1 -0
  34. package/lib/commonjs/package.json +1 -0
  35. package/lib/commonjs/utils/characterDeletion.js +20 -0
  36. package/lib/commonjs/utils/characterDeletion.js.map +1 -0
  37. package/lib/commonjs/utils/characterInsert.js +20 -0
  38. package/lib/commonjs/utils/characterInsert.js.map +1 -0
  39. package/lib/commonjs/utils/fromMaskedNumberToUnmaskedSelection.js +14 -0
  40. package/lib/commonjs/utils/fromMaskedNumberToUnmaskedSelection.js.map +1 -0
  41. package/lib/commonjs/utils/fromUnmaskedToMaskedPosition.js +20 -0
  42. package/lib/commonjs/utils/fromUnmaskedToMaskedPosition.js.map +1 -0
  43. package/lib/commonjs/utils/generateCountryCodeList.js +23 -0
  44. package/lib/commonjs/utils/generateCountryCodeList.js.map +1 -0
  45. package/lib/commonjs/utils/getDefaultRegion.js +33 -0
  46. package/lib/commonjs/utils/getDefaultRegion.js.map +1 -0
  47. package/lib/commonjs/utils/maskToPhoneNumber.js +23 -0
  48. package/lib/commonjs/utils/maskToPhoneNumber.js.map +1 -0
  49. package/lib/commonjs/utils/matchCountryCode.js +27 -0
  50. package/lib/commonjs/utils/matchCountryCode.js.map +1 -0
  51. package/lib/module/CountrySelector/CountrySelector.js +70 -0
  52. package/lib/module/CountrySelector/CountrySelector.js.map +1 -0
  53. package/lib/module/CountrySelector/CountrySelectorModal.js +262 -0
  54. package/lib/module/CountrySelector/CountrySelectorModal.js.map +1 -0
  55. package/lib/module/Keyboard/Keyboard.js +310 -0
  56. package/lib/module/Keyboard/Keyboard.js.map +1 -0
  57. package/lib/module/Keyboard/KeyboardToolbar.js +65 -0
  58. package/lib/module/Keyboard/KeyboardToolbar.js.map +1 -0
  59. package/lib/module/Keyboard/KeypadButton.js +61 -0
  60. package/lib/module/Keyboard/KeypadButton.js.map +1 -0
  61. package/lib/module/Keyboard/KeypadButtonContainer.js +59 -0
  62. package/lib/module/Keyboard/KeypadButtonContainer.js.map +1 -0
  63. package/lib/module/Keyboard/KeypadRow.js +30 -0
  64. package/lib/module/Keyboard/KeypadRow.js.map +1 -0
  65. package/lib/module/PhoneCountryInput/PhoneCountryInput.js +80 -0
  66. package/lib/module/PhoneCountryInput/PhoneCountryInput.js.map +1 -0
  67. package/lib/module/PhoneNumberField.js +31 -0
  68. package/lib/module/PhoneNumberField.js.map +1 -0
  69. package/lib/module/Styling/Colors.js +193 -0
  70. package/lib/module/Styling/Colors.js.map +1 -0
  71. package/lib/module/Styling/Sizing.js +107 -0
  72. package/lib/module/Styling/Sizing.js.map +1 -0
  73. package/lib/module/consts/KEYBOARD_LAYOUT.js +41 -0
  74. package/lib/module/consts/KEYBOARD_LAYOUT.js.map +1 -0
  75. package/lib/module/consts/regions.js +1498 -0
  76. package/lib/module/consts/regions.js.map +1 -0
  77. package/lib/module/enum/CountryIds.js +260 -0
  78. package/lib/module/enum/CountryIds.js.map +1 -0
  79. package/lib/module/hooks/UsePhoneFieldState.js +232 -0
  80. package/lib/module/hooks/UsePhoneFieldState.js.map +1 -0
  81. package/lib/module/index.js +16 -0
  82. package/lib/module/index.js.map +1 -0
  83. package/lib/module/package.json +1 -0
  84. package/lib/module/utils/characterDeletion.js +16 -0
  85. package/lib/module/utils/characterDeletion.js.map +1 -0
  86. package/lib/module/utils/characterInsert.js +16 -0
  87. package/lib/module/utils/characterInsert.js.map +1 -0
  88. package/lib/module/utils/fromMaskedNumberToUnmaskedSelection.js +10 -0
  89. package/lib/module/utils/fromMaskedNumberToUnmaskedSelection.js.map +1 -0
  90. package/lib/module/utils/fromUnmaskedToMaskedPosition.js +16 -0
  91. package/lib/module/utils/fromUnmaskedToMaskedPosition.js.map +1 -0
  92. package/lib/module/utils/generateCountryCodeList.js +19 -0
  93. package/lib/module/utils/generateCountryCodeList.js.map +1 -0
  94. package/lib/module/utils/getDefaultRegion.js +28 -0
  95. package/lib/module/utils/getDefaultRegion.js.map +1 -0
  96. package/lib/module/utils/maskToPhoneNumber.js +19 -0
  97. package/lib/module/utils/maskToPhoneNumber.js.map +1 -0
  98. package/lib/module/utils/matchCountryCode.js +23 -0
  99. package/lib/module/utils/matchCountryCode.js.map +1 -0
  100. package/lib/typescript/CountrySelector/CountrySelector.d.ts +19 -0
  101. package/lib/typescript/CountrySelector/CountrySelector.d.ts.map +1 -0
  102. package/lib/typescript/CountrySelector/CountrySelectorModal.d.ts +12 -0
  103. package/lib/typescript/CountrySelector/CountrySelectorModal.d.ts.map +1 -0
  104. package/lib/typescript/Keyboard/Keyboard.d.ts +25 -0
  105. package/lib/typescript/Keyboard/Keyboard.d.ts.map +1 -0
  106. package/lib/typescript/Keyboard/KeyboardToolbar.d.ts +9 -0
  107. package/lib/typescript/Keyboard/KeyboardToolbar.d.ts.map +1 -0
  108. package/lib/typescript/Keyboard/KeypadButton.d.ts +10 -0
  109. package/lib/typescript/Keyboard/KeypadButton.d.ts.map +1 -0
  110. package/lib/typescript/Keyboard/KeypadButtonContainer.d.ts +12 -0
  111. package/lib/typescript/Keyboard/KeypadButtonContainer.d.ts.map +1 -0
  112. package/lib/typescript/Keyboard/KeypadRow.d.ts +9 -0
  113. package/lib/typescript/Keyboard/KeypadRow.d.ts.map +1 -0
  114. package/lib/typescript/PhoneCountryInput/PhoneCountryInput.d.ts +16 -0
  115. package/lib/typescript/PhoneCountryInput/PhoneCountryInput.d.ts.map +1 -0
  116. package/lib/typescript/PhoneNumberField.d.ts +17 -0
  117. package/lib/typescript/PhoneNumberField.d.ts.map +1 -0
  118. package/lib/typescript/Styling/Colors.d.ts +189 -0
  119. package/lib/typescript/Styling/Colors.d.ts.map +1 -0
  120. package/lib/typescript/Styling/Sizing.d.ts +214 -0
  121. package/lib/typescript/Styling/Sizing.d.ts.map +1 -0
  122. package/lib/typescript/consts/KEYBOARD_LAYOUT.d.ts +18 -0
  123. package/lib/typescript/consts/KEYBOARD_LAYOUT.d.ts.map +1 -0
  124. package/lib/typescript/consts/regions.d.ts +10 -0
  125. package/lib/typescript/consts/regions.d.ts.map +1 -0
  126. package/lib/typescript/enum/CountryIds.d.ts +249 -0
  127. package/lib/typescript/enum/CountryIds.d.ts.map +1 -0
  128. package/lib/typescript/hooks/UsePhoneFieldState.d.ts +34 -0
  129. package/lib/typescript/hooks/UsePhoneFieldState.d.ts.map +1 -0
  130. package/lib/typescript/index.d.ts +15 -0
  131. package/lib/typescript/index.d.ts.map +1 -0
  132. package/lib/typescript/utils/characterDeletion.d.ts +3 -0
  133. package/lib/typescript/utils/characterDeletion.d.ts.map +1 -0
  134. package/lib/typescript/utils/characterInsert.d.ts +3 -0
  135. package/lib/typescript/utils/characterInsert.d.ts.map +1 -0
  136. package/lib/typescript/utils/fromMaskedNumberToUnmaskedSelection.d.ts +2 -0
  137. package/lib/typescript/utils/fromMaskedNumberToUnmaskedSelection.d.ts.map +1 -0
  138. package/lib/typescript/utils/fromUnmaskedToMaskedPosition.d.ts +2 -0
  139. package/lib/typescript/utils/fromUnmaskedToMaskedPosition.d.ts.map +1 -0
  140. package/lib/typescript/utils/generateCountryCodeList.d.ts +3 -0
  141. package/lib/typescript/utils/generateCountryCodeList.d.ts.map +1 -0
  142. package/lib/typescript/utils/getDefaultRegion.d.ts +4 -0
  143. package/lib/typescript/utils/getDefaultRegion.d.ts.map +1 -0
  144. package/lib/typescript/utils/maskToPhoneNumber.d.ts +2 -0
  145. package/lib/typescript/utils/maskToPhoneNumber.d.ts.map +1 -0
  146. package/lib/typescript/utils/matchCountryCode.d.ts +3 -0
  147. package/lib/typescript/utils/matchCountryCode.d.ts.map +1 -0
  148. package/package.json +92 -0
  149. package/src/CountrySelector/CountrySelector.tsx +77 -0
  150. package/src/CountrySelector/CountrySelectorModal.tsx +280 -0
  151. package/src/Keyboard/Keyboard.tsx +322 -0
  152. package/src/Keyboard/KeyboardToolbar.tsx +53 -0
  153. package/src/Keyboard/KeypadButton.tsx +58 -0
  154. package/src/Keyboard/KeypadButtonContainer.tsx +67 -0
  155. package/src/Keyboard/KeypadRow.tsx +29 -0
  156. package/src/PhoneCountryInput/PhoneCountryInput.tsx +98 -0
  157. package/src/PhoneNumberField.tsx +46 -0
  158. package/src/Styling/Colors.ts +206 -0
  159. package/src/Styling/Sizing.ts +110 -0
  160. package/src/consts/KEYBOARD_LAYOUT.ts +34 -0
  161. package/src/consts/regions.ts +268 -0
  162. package/src/enum/CountryIds.ts +256 -0
  163. package/src/hooks/UsePhoneFieldState.tsx +268 -0
  164. package/src/index.ts +27 -0
  165. package/src/utils/characterDeletion.ts +16 -0
  166. package/src/utils/characterInsert.ts +20 -0
  167. package/src/utils/fromMaskedNumberToUnmaskedSelection.ts +10 -0
  168. package/src/utils/fromUnmaskedToMaskedPosition.ts +13 -0
  169. package/src/utils/generateCountryCodeList.ts +22 -0
  170. package/src/utils/getDefaultRegion.ts +30 -0
  171. package/src/utils/maskToPhoneNumber.ts +30 -0
  172. package/src/utils/matchCountryCode.ts +23 -0
@@ -0,0 +1,322 @@
1
+ import React, { memo, MutableRefObject, useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { GAP, KEYBOARD_LAYOUT, KEYPAD_KEY } from '../consts/KEYBOARD_LAYOUT';
3
+ import KeypadRow from './KeypadRow';
4
+ import { Pressable, View, StyleSheet, Dimensions, Modal, Text } from 'react-native';
5
+ import { Portal } from 'react-native-teleport';
6
+ import Animated, {
7
+ useAnimatedStyle,
8
+ useDerivedValue,
9
+ useSharedValue,
10
+ withDelay,
11
+ withTiming,
12
+ } from 'react-native-reanimated';
13
+ import KeyboardToolbar from './KeyboardToolbar';
14
+ import { spacing, radius, padding } from '../Styling/Sizing';
15
+ import { colors } from '../Styling/Colors';
16
+ import { Feather } from '@expo/vector-icons';
17
+
18
+ export interface PasteErrorModalProps extends React.ComponentProps<typeof Modal> {
19
+ isPasteErrorVisible: boolean;
20
+ dismissPasteError: () => void;
21
+ }
22
+
23
+ export interface CopySuccessModalProps extends React.ComponentProps<typeof Modal> {
24
+ isCopySuccessVisible: boolean;
25
+ dismissCopySuccess: () => void;
26
+ }
27
+
28
+ export interface KeyboardProps {
29
+ value?: string;
30
+ onKeyPress: (_value: KEYPAD_KEY) => void;
31
+ onCopy: () => void;
32
+ onPaste: () => Promise<boolean>;
33
+ isOpen: boolean;
34
+ onClose: () => void;
35
+ underlinePasteErrorModal?: React.ComponentType<PasteErrorModalProps> | null;
36
+ underlineCopySuccessModal?: React.ComponentType<CopySuccessModalProps> | null;
37
+ }
38
+
39
+ function Keyboard(props: KeyboardProps) {
40
+ const PasteErrorModal = useMemo(
41
+ () => props.underlinePasteErrorModal ?? null,
42
+ [props.underlinePasteErrorModal]
43
+ );
44
+ const CopySuccessModal = useMemo(
45
+ () => props.underlineCopySuccessModal ?? null,
46
+ [props.underlineCopySuccessModal]
47
+ );
48
+
49
+ const [isPasteErrorVisible, setIsPasteErrorVisible] = useState(false);
50
+ const dismissPasteError = useCallback(() => setIsPasteErrorVisible(false), []);
51
+
52
+ const [isCopySuccessVisible, setIsCopySuccessVisible] = useState(false);
53
+ const dismissCopySuccess = useCallback(() => setIsCopySuccessVisible(false), []);
54
+
55
+ const handlePaste = useCallback(() => {
56
+ props.onPaste().then((success) => {
57
+ if (!success) setIsPasteErrorVisible(true);
58
+ });
59
+ }, [props.onPaste]);
60
+
61
+ const handleCopy = useCallback(() => {
62
+ props.onCopy();
63
+ setIsCopySuccessVisible(true);
64
+ }, [props.onCopy]);
65
+
66
+ const isOpenShared = useSharedValue(props.isOpen);
67
+
68
+ useEffect(() => {
69
+ isOpenShared.value = props.isOpen;
70
+ }, [isOpenShared, props.isOpen]);
71
+
72
+ const height = useSharedValue(0);
73
+ const progress = useDerivedValue(() => withTiming(isOpenShared.value ? 0 : 1, { duration: 250 }));
74
+
75
+ const sheetStyle = useAnimatedStyle(() => ({
76
+ transform: [{ translateY: progress.value * 2 * height.value }],
77
+ }));
78
+
79
+ const backdropStyle = useAnimatedStyle(() => ({
80
+ opacity: 1 - progress.value,
81
+ zIndex: isOpenShared.value ? 1 : withDelay(250, withTiming(-1, { duration: 0 })),
82
+ }));
83
+
84
+ const layout = useMemo(() => {
85
+ console.debug('-- ⚠️ Rerender Keyboard--');
86
+ return KEYBOARD_LAYOUT.map((row, i) => {
87
+ return <KeypadRow row={row} key={'row-' + i} onPress={props.onKeyPress} />;
88
+ });
89
+ }, [props.onKeyPress]);
90
+
91
+ const { height: screenHeight } = Dimensions.get('window');
92
+
93
+ return (
94
+ <Portal hostName="ipad-keyboard">
95
+ {props.isOpen && (
96
+ <Pressable style={[StyleSheet.absoluteFillObject, { zIndex: 0 }]} onPress={props.onClose} />
97
+ )}
98
+ <Animated.View
99
+ onLayout={(e) => {
100
+ height.value = e.nativeEvent.layout.height;
101
+ }}
102
+ style={[sheetStyles.sheet, { height: screenHeight / 2.5 }, sheetStyle, backdropStyle]}>
103
+ <View
104
+ style={{
105
+ flex: 1,
106
+ gap: GAP,
107
+ paddingBottom: spacing[14],
108
+ paddingHorizontal: spacing[4],
109
+ }}>
110
+ <KeyboardToolbar
111
+ onClose={props.onClose}
112
+ value={props.value}
113
+ onCopy={handleCopy}
114
+ onPaste={handlePaste}
115
+ />
116
+ {layout}
117
+ </View>
118
+ </Animated.View>
119
+ {PasteErrorModal ? (
120
+ <PasteErrorModal
121
+ visible={isPasteErrorVisible}
122
+ onRequestClose={dismissPasteError}
123
+ isPasteErrorVisible={isPasteErrorVisible}
124
+ dismissPasteError={dismissPasteError}
125
+ transparent
126
+ animationType="fade"
127
+ />
128
+ ) : (
129
+ <Modal
130
+ visible={isPasteErrorVisible}
131
+ onRequestClose={dismissPasteError}
132
+ transparent
133
+ animationType="fade">
134
+ <Pressable style={pasteErrorStyles.overlay} onPress={dismissPasteError}>
135
+ <View style={pasteErrorStyles.card}>
136
+ <View style={pasteErrorStyles.cardContent}>
137
+ <View style={pasteErrorStyles.textContent}>
138
+ <View style={pasteErrorStyles.titleContainer}>
139
+ <Feather name="alert-triangle" size={spacing[6]} />
140
+ <Text style={pasteErrorStyles.title}>Cannot Paste</Text>
141
+ </View>
142
+ <Text style={pasteErrorStyles.message}>
143
+ Clipboard does not contain a valid phone number.
144
+ </Text>
145
+ </View>
146
+ <Pressable style={pasteErrorStyles.button} onPress={dismissPasteError}>
147
+ <Text style={pasteErrorStyles.buttonText}>OK</Text>
148
+ </Pressable>
149
+ </View>
150
+ </View>
151
+ </Pressable>
152
+ </Modal>
153
+ )}
154
+ {CopySuccessModal ? (
155
+ <CopySuccessModal
156
+ visible={isCopySuccessVisible}
157
+ onRequestClose={dismissCopySuccess}
158
+ isCopySuccessVisible={isCopySuccessVisible}
159
+ dismissCopySuccess={dismissCopySuccess}
160
+ transparent
161
+ animationType="fade"
162
+ />
163
+ ) : (
164
+ <Modal
165
+ visible={isCopySuccessVisible}
166
+ onRequestClose={dismissCopySuccess}
167
+ transparent
168
+ animationType="fade">
169
+ <Pressable style={copySuccessStyles.overlay} onPress={dismissCopySuccess}>
170
+ <View style={copySuccessStyles.card}>
171
+ <View style={copySuccessStyles.cardContent}>
172
+ <View style={copySuccessStyles.textContent}>
173
+ <View style={copySuccessStyles.titleContainer}>
174
+ <Feather name="clipboard" size={spacing[6]} />
175
+ <Text style={copySuccessStyles.title}>Copied</Text>
176
+ </View>
177
+ <Text style={copySuccessStyles.message}>Phone number copied to clipboard.</Text>
178
+ </View>
179
+ <Pressable style={copySuccessStyles.button} onPress={dismissCopySuccess}>
180
+ <Text style={copySuccessStyles.buttonText}>OK</Text>
181
+ </Pressable>
182
+ </View>
183
+ </View>
184
+ </Pressable>
185
+ </Modal>
186
+ )}
187
+ </Portal>
188
+ );
189
+ }
190
+
191
+ export default memo(Keyboard);
192
+
193
+ const sheetStyles = StyleSheet.create({
194
+ sheet: {
195
+ width: '100%',
196
+ position: 'absolute',
197
+ bottom: 0,
198
+ borderTopRightRadius: 20,
199
+ borderTopLeftRadius: 20,
200
+ backgroundColor: '#D1D5DB',
201
+ zIndex: 3,
202
+ gap: 12,
203
+ },
204
+ });
205
+
206
+ const pasteErrorStyles = StyleSheet.create({
207
+ overlay: {
208
+ flex: 1,
209
+ backgroundColor: 'rgba(0,0,0,0.4)',
210
+ justifyContent: 'center',
211
+ alignItems: 'center',
212
+ padding: spacing[6],
213
+ },
214
+ card: {
215
+ backgroundColor: colors.white,
216
+ borderRadius: radius.xl,
217
+ padding: padding[5],
218
+ width: '100%',
219
+ maxWidth: 250,
220
+ maxHeight: 220,
221
+ alignItems: 'center',
222
+ justifyContent: 'center',
223
+ },
224
+ cardContent: {},
225
+ textContent: {
226
+ flex: 4,
227
+ gap: spacing[2],
228
+ alignItems: 'center',
229
+ justifyContent: 'center',
230
+ },
231
+ titleContainer: {
232
+ flexDirection: 'row',
233
+ gap: spacing[2],
234
+ alignItems: 'center',
235
+ },
236
+ title: {
237
+ fontSize: spacing[6],
238
+ fontWeight: '600',
239
+ color: colors.black,
240
+ },
241
+ message: {
242
+ fontSize: 14,
243
+ color: colors.gray[500],
244
+ textAlign: 'center',
245
+ },
246
+ button: {
247
+ marginTop: spacing[2],
248
+ backgroundColor: colors.red[800],
249
+ borderRadius: radius.lg,
250
+ paddingVertical: spacing[2],
251
+ paddingHorizontal: spacing[8],
252
+ flex: 1,
253
+ alignItems: 'center',
254
+ justifyContent: 'center',
255
+ },
256
+ buttonText: {
257
+ fontSize: 15,
258
+ fontWeight: '500',
259
+ color: colors.white,
260
+ textAlign: 'center',
261
+ },
262
+ });
263
+
264
+ const copySuccessStyles = StyleSheet.create({
265
+ overlay: {
266
+ flex: 1,
267
+ backgroundColor: 'rgba(0,0,0,0.4)',
268
+ justifyContent: 'center',
269
+ alignItems: 'center',
270
+ padding: spacing[6],
271
+ },
272
+ card: {
273
+ backgroundColor: colors.white,
274
+ borderRadius: radius.xl,
275
+ padding: padding[5],
276
+ width: '100%',
277
+ maxWidth: 250,
278
+ maxHeight: 220,
279
+ alignItems: 'center',
280
+ justifyContent: 'center',
281
+ },
282
+ cardContent: {},
283
+ textContent: {
284
+ flex: 4,
285
+ gap: spacing[2],
286
+ // justifyContent: 'center',
287
+ alignItems: 'center',
288
+ justifyContent: 'center',
289
+ },
290
+ titleContainer: {
291
+ flexDirection: 'row',
292
+ gap: spacing[1],
293
+ alignItems: 'center',
294
+ },
295
+ title: {
296
+ fontSize: spacing[6],
297
+ fontWeight: '600',
298
+ color: colors.black,
299
+ },
300
+ message: {
301
+ fontSize: 14,
302
+ color: colors.gray[500],
303
+
304
+ textAlign: 'center',
305
+ },
306
+ button: {
307
+ marginTop: spacing[2],
308
+ backgroundColor: colors.green[800],
309
+ borderRadius: radius.lg,
310
+ paddingVertical: spacing[2],
311
+ paddingHorizontal: spacing[8],
312
+ flex: 1,
313
+ alignItems: 'center',
314
+ justifyContent: 'center',
315
+ },
316
+ buttonText: {
317
+ fontSize: 15,
318
+ fontWeight: '500',
319
+ color: colors.white,
320
+ textAlign: 'center',
321
+ },
322
+ });
@@ -0,0 +1,53 @@
1
+ import React, { useMemo } from 'react';
2
+ import { View, Text, Pressable, StyleSheet } from 'react-native';
3
+ import { Feather } from '@expo/vector-icons';
4
+ import { GAP } from '../consts/KEYBOARD_LAYOUT';
5
+ import { spacing } from '../Styling/Sizing';
6
+ import { colors } from '../Styling/Colors';
7
+
8
+ export interface KeyboardToolbarProps {
9
+ value?: string;
10
+ onClose: () => void;
11
+ onCopy?: () => void;
12
+ onPaste?: () => void;
13
+ }
14
+
15
+ export default function KeyboardToolbar({ onClose, value, onCopy, onPaste }: KeyboardToolbarProps) {
16
+ const isCopyDisabled = useMemo(() => {
17
+ return (value ?? '').length < 3;
18
+ }, [value]);
19
+
20
+ return (
21
+ <View style={styles.container}>
22
+ <View style={styles.leftGroup}>
23
+ <Pressable disabled={isCopyDisabled} onPress={onCopy}>
24
+ <Feather name="copy" size={20} color={!isCopyDisabled ? '#000' : colors.gray[600]} />
25
+ </Pressable>
26
+ <Pressable onPress={onPaste}>
27
+ <Feather name="clipboard" size={20} color="#000" />
28
+ </Pressable>
29
+ </View>
30
+ <Pressable onPress={onClose}>
31
+ <Text style={styles.doneText}>Done</Text>
32
+ </Pressable>
33
+ </View>
34
+ );
35
+ }
36
+
37
+ const styles = StyleSheet.create({
38
+ container: {
39
+ height: 56,
40
+ flexDirection: 'row',
41
+ justifyContent: 'space-between',
42
+ alignItems: 'center',
43
+ paddingHorizontal: spacing[2],
44
+ },
45
+ leftGroup: {
46
+ flexDirection: 'row',
47
+ gap: GAP,
48
+ },
49
+ doneText: {
50
+ color: '#000',
51
+ fontSize: 16,
52
+ },
53
+ });
@@ -0,0 +1,58 @@
1
+ import { memo, useCallback, useMemo } from 'react';
2
+ import { BACK_BUTTON, CLEAR_BUTTON, GLOBE_BUTTON, KEYPAD_KEY } from '../consts/KEYBOARD_LAYOUT';
3
+ import KeypadButtonContainer from './KeypadButtonContainer';
4
+ import { Platform, Text } from 'react-native';
5
+ import { Feather } from '@expo/vector-icons';
6
+
7
+ export interface KeypadButtonProps {
8
+ currentKey: KEYPAD_KEY;
9
+ onPress: (_key: KEYPAD_KEY) => void;
10
+ }
11
+
12
+ function KeypadButton({ currentKey, onPress }: KeypadButtonProps) {
13
+ const handlePress = useCallback(() => onPress(currentKey), [onPress, currentKey]);
14
+ const handleClear = useCallback(() => onPress({ main: CLEAR_BUTTON }), [onPress]);
15
+
16
+ const isBackButton = useMemo(() => currentKey.main === BACK_BUTTON, [currentKey.main]);
17
+ const isGlobeButton = useMemo(() => currentKey.main === GLOBE_BUTTON, [currentKey.main]);
18
+
19
+ if (isGlobeButton) {
20
+ return (
21
+ <KeypadButtonContainer
22
+ bgColor={null}
23
+ shouldDisableShadow={Platform.OS === 'android'}
24
+ bgPressedColor={'background-color: rgba(0, 0, 0, 0.1);'}
25
+ onPress={handlePress}
26
+ key="country-button">
27
+ <Feather name="globe" size={24} />
28
+ </KeypadButtonContainer>
29
+ );
30
+ }
31
+
32
+ if (isBackButton) {
33
+ return (
34
+ <KeypadButtonContainer
35
+ bgColor={null}
36
+ bgPressedColor={
37
+ 'background-color: rgba(0, 0, 0, 0.1);'
38
+ }
39
+ shouldDisableShadow={Platform.OS === 'android'}
40
+ onPress={handlePress}
41
+ onLongPress={handleClear}
42
+ key="back-button">
43
+ <Feather name="delete" size={24} />
44
+ <Text style={{ fontWeight: 'bold' }}>clear</Text>
45
+ </KeypadButtonContainer>
46
+ );
47
+ }
48
+
49
+ return (
50
+ <KeypadButtonContainer onPress={handlePress}>
51
+ <Text style={{}}>{currentKey.main}</Text>
52
+ {(currentKey?.subtext ?? '').length > 0 && <Text>{currentKey.subtext}</Text>}
53
+ </KeypadButtonContainer>
54
+ );
55
+ }
56
+
57
+ export { KeypadButton };
58
+ export default memo(KeypadButton);
@@ -0,0 +1,67 @@
1
+ import React, { useState } from 'react';
2
+ import { Pressable, PressableProps, StyleSheet, View, Text } from 'react-native';
3
+ import { colors } from '../Styling/Colors';
4
+ import { spacing } from '@/Styling/Sizing';
5
+ // import { Box } from '@/components/ui/box';
6
+ // import { GetColor } from '@/utils/get-color';
7
+
8
+ interface KeypadButtonContainerProps extends PressableProps {
9
+ children: React.ReactNode;
10
+ onPress?: PressableProps['onPress'];
11
+ bgColor?: string | null;
12
+ bgPressedColor?: string | null;
13
+ shouldDisableShadow?: boolean;
14
+ }
15
+
16
+ export default function KeypadButtonContainer({
17
+ children,
18
+ shouldDisableShadow,
19
+ bgColor = colors.white,
20
+ bgPressedColor = colors.gray[300],
21
+ ...props
22
+ }: KeypadButtonContainerProps) {
23
+ const [isCurrentlyPressed, setIsCurrentlyPressed] = useState(false);
24
+
25
+ return (
26
+ <Pressable
27
+ style={styles.pressable}
28
+ unstable_pressDelay={0}
29
+ onPressIn={() => {
30
+ setIsCurrentlyPressed(true);
31
+ }}
32
+ onPressOut={() => {
33
+ setIsCurrentlyPressed(false);
34
+ }}
35
+ {...props}>
36
+ <View
37
+ style={[
38
+ styles.container,
39
+ { backgroundColor: (isCurrentlyPressed ? bgPressedColor : bgColor) ?? undefined },
40
+ !shouldDisableShadow && {
41
+ shadowColor: '#000',
42
+ shadowOffset: { width: 0, height: 1 },
43
+ shadowOpacity: 0.25,
44
+ shadowRadius: 2,
45
+ elevation: 2,
46
+ }
47
+ ]}>
48
+ {children}
49
+ </View>
50
+ </Pressable>
51
+ );
52
+ }
53
+
54
+ const styles = StyleSheet.create({
55
+ pressable: {
56
+ flex: 1,
57
+ },
58
+ container: {
59
+ flex: 1,
60
+ flexDirection: 'column',
61
+ justifyContent: 'center',
62
+ alignItems: 'center',
63
+ gap: 4,
64
+ borderRadius: 12,
65
+ paddingVertical: spacing[2],
66
+ },
67
+ });
@@ -0,0 +1,29 @@
1
+ import { View } from 'react-native';
2
+ import { GAP, KEYPAD_KEY, KEYPAD_ROW } from '../consts/KEYBOARD_LAYOUT';
3
+ import { KeypadButton } from './KeypadButton';
4
+ import { memo, useMemo } from 'react';
5
+
6
+ export interface KeypadRowProps {
7
+ row: KEYPAD_ROW;
8
+ onPress: (_value: KEYPAD_KEY) => void;
9
+ }
10
+
11
+ function KeypadRow({ row, onPress }: KeypadRowProps) {
12
+ const keyboardRow = useMemo(() => {
13
+ return row.map((_key, i) => {
14
+ return <KeypadButton currentKey={_key} key={_key.main + (i + '')} onPress={onPress} />;
15
+ });
16
+ }, [onPress, row]);
17
+ return (
18
+ <View
19
+ style={{
20
+ flex: 1,
21
+ flexDirection: 'row',
22
+ gap: GAP,
23
+ }}>
24
+ {keyboardRow}
25
+ </View>
26
+ );
27
+ }
28
+
29
+ export default memo(KeypadRow);
@@ -0,0 +1,98 @@
1
+ import React, { useEffect } from 'react';
2
+ import { View, Modal, Pressable } from 'react-native';
3
+ import { spacing } from '../Styling/Sizing';
4
+ import { PhoneFieldOutcome, PhoneNumberField, PhoneNumberFieldProps } from '../PhoneNumberField';
5
+ import { CountrySelector } from '../CountrySelector/CountrySelector';
6
+ import { usePhoneFieldState } from '../hooks/UsePhoneFieldState';
7
+ import { CountryId } from '../enum/CountryIds';
8
+
9
+ import Keyboard, { CopySuccessModalProps, PasteErrorModalProps } from '../Keyboard/Keyboard';
10
+
11
+ export interface PhoneCountryInputProps extends PhoneNumberFieldProps {
12
+ underlineButton?: React.ComponentType<React.ComponentProps<typeof Pressable>> | null;
13
+ underlineModal?: React.ComponentType<React.ComponentProps<typeof Modal>> | null;
14
+ underlinePasteErrorModal?: React.ComponentType<PasteErrorModalProps> | null;
15
+ underlineCopySuccessModal?: React.ComponentType<CopySuccessModalProps> | null;
16
+ allowedCountryCodes?: CountryId[] | null;
17
+ disallowedCountryCodes?: CountryId[] | null;
18
+ onOutcomeChange: (outcome?: PhoneFieldOutcome) => void;
19
+ }
20
+
21
+ export function PhoneCountryInput({
22
+ underlineButton,
23
+ underlineModal,
24
+ underlinePasteErrorModal,
25
+ underlineCopySuccessModal,
26
+ style,
27
+ allowedCountryCodes,
28
+ disallowedCountryCodes,
29
+ onOutcomeChange,
30
+ ...props
31
+ }: PhoneCountryInputProps) {
32
+ const {
33
+ filteredCountryCodes,
34
+ outcome,
35
+ onChangeText,
36
+ onChangeFlag,
37
+ onKeyPress,
38
+ phoneNumber,
39
+ onCopy,
40
+ onPaste,
41
+ isKeyboardOpen,
42
+ openKeyboard,
43
+ closeKeyboard,
44
+ isCountrySelectorOpen,
45
+ openCountrySelector,
46
+ closeCountrySelector,
47
+ onTextSelection,
48
+ cursorPosition,
49
+ } = usePhoneFieldState({
50
+ allowedCountryCodes,
51
+ disallowedCountryCodes,
52
+ });
53
+
54
+ useEffect(() => {
55
+ onOutcomeChange(outcome);
56
+ }, [onOutcomeChange, outcome]);
57
+
58
+ return (
59
+ <>
60
+ <View
61
+ style={[
62
+ {
63
+ flexDirection: 'row',
64
+ gap: spacing[1],
65
+ },
66
+ style,
67
+ ]}>
68
+ <CountrySelector
69
+ value={outcome?.countryDetails ?? null}
70
+ onSelectCountry={onChangeFlag}
71
+ underlineButton={underlineButton}
72
+ underlineModal={underlineModal}
73
+ filtedredCountryCodes={filteredCountryCodes}
74
+ isOpen={isCountrySelectorOpen}
75
+ onOpenChange={(open) => (open ? openCountrySelector() : closeCountrySelector())}
76
+ />
77
+ <PhoneNumberField
78
+ {...props}
79
+ value={phoneNumber}
80
+ onChangeText={onChangeText}
81
+ onOpen={openKeyboard}
82
+ onTextSelection={onTextSelection}
83
+ selection={cursorPosition}
84
+ />
85
+ </View>
86
+ <Keyboard
87
+ onKeyPress={onKeyPress}
88
+ value={phoneNumber}
89
+ onCopy={onCopy}
90
+ onPaste={onPaste}
91
+ isOpen={isKeyboardOpen}
92
+ onClose={closeKeyboard}
93
+ underlinePasteErrorModal={underlinePasteErrorModal}
94
+ underlineCopySuccessModal={underlineCopySuccessModal}
95
+ />
96
+ </>
97
+ );
98
+ }
@@ -0,0 +1,46 @@
1
+ import React, { useMemo } from 'react';
2
+ import { NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData } from 'react-native';
3
+
4
+ import { CountryCode } from './consts/regions';
5
+
6
+ export interface PhoneFieldOutcome {
7
+ countryDetails: CountryCode | null;
8
+ phoneNumber: string;
9
+ isValid: boolean;
10
+ correctLength: number;
11
+ }
12
+
13
+ export interface PhoneNumberFieldProps extends React.ComponentProps<typeof TextInput> {
14
+ underlineInput?: React.ComponentType<React.ComponentProps<typeof TextInput>> | null;
15
+ onInputChange?: (outcome: PhoneFieldOutcome) => void;
16
+ onOpen?: () => void;
17
+ onTextSelection?: (e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void;
18
+ }
19
+
20
+ export function PhoneNumberField(props: PhoneNumberFieldProps) {
21
+ const { underlineInput, onOpen, onTextSelection, ...textInputProps } = props;
22
+
23
+ const Input = useMemo((): React.ComponentType<React.ComponentProps<typeof TextInput>> => {
24
+ if (!underlineInput) {
25
+ return TextInput;
26
+ }
27
+ return underlineInput;
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ }, [underlineInput, props.selection]);
30
+
31
+ return (
32
+ <Input
33
+ {...textInputProps}
34
+ value={'+' + props.value}
35
+ showSoftInputOnFocus={false}
36
+ onPressIn={onOpen}
37
+ onSelectionChange={onTextSelection}
38
+ style={[
39
+ props.style,
40
+ {
41
+ zIndex: 1,
42
+ },
43
+ ]}
44
+ />
45
+ );
46
+ }