react-native-country-select 0.1.1 → 0.1.3

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.
@@ -1,41 +1,59 @@
1
- import React, {memo} from 'react';
2
- import {View, Text, TouchableOpacity} from 'react-native';
3
-
4
- import {createStyles} from '../styles';
5
- import {ICountryItemProps, ICountrySelectLanguages} from '../../interface';
6
-
7
- const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
8
-
9
- export const CountryItem = memo<ICountryItemProps>(
10
- ({item, onSelect, onClose, theme = 'light', language}) => {
11
- const styles = createStyles(theme);
12
-
13
- return (
14
- <TouchableOpacity
15
- testID="countrySelectItem"
16
- accessibilityRole="button"
17
- accessibilityLabel="Country Select Item"
18
- accessibilityHint="Click to select a country"
19
- style={styles.countryItem}
20
- onPress={() => {
21
- onSelect(item);
22
- onClose();
23
- }}>
24
- <Text testID="countrySelectItemFlag" style={styles.flag}>
25
- {item.flag}
26
- </Text>
27
- <View style={styles.countryInfo}>
28
- <Text
29
- testID="countrySelectItemCallingCode"
30
- style={styles.callingCode}>
31
- {item.idd.root}
32
- </Text>
33
- <Text testID="countrySelectItemName" style={styles.countryName}>
34
- {item?.translations[language]?.common ||
35
- item?.translations[DEFAULT_LANGUAGE]?.common}
36
- </Text>
37
- </View>
38
- </TouchableOpacity>
39
- );
40
- },
41
- );
1
+ import React, {memo} from 'react';
2
+ import {View, Text, TouchableOpacity} from 'react-native';
3
+
4
+ import {createStyles} from '../styles';
5
+ import {ICountryItemProps, ICountrySelectLanguages} from '../../interface';
6
+
7
+ const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
8
+
9
+ export const CountryItem = memo<ICountryItemProps>(
10
+ ({
11
+ item,
12
+ onSelect,
13
+ onClose,
14
+ theme = 'light',
15
+ language,
16
+ countrySelectStyle,
17
+ }) => {
18
+ const styles = createStyles(theme);
19
+
20
+ return (
21
+ <TouchableOpacity
22
+ testID="countrySelectItem"
23
+ accessibilityRole="button"
24
+ accessibilityLabel="Country Select Item"
25
+ accessibilityHint="Click to select a country"
26
+ style={[styles.countryItem, countrySelectStyle?.popup?.countryItem]}
27
+ onPress={() => {
28
+ onSelect(item);
29
+ onClose();
30
+ }}>
31
+ <Text
32
+ testID="countrySelectItemFlag"
33
+ style={[styles.flag, countrySelectStyle?.popup?.flag]}>
34
+ {item.flag}
35
+ </Text>
36
+ <View
37
+ style={[styles.countryInfo, countrySelectStyle?.popup?.countryInfo]}>
38
+ <Text
39
+ testID="countrySelectItemCallingCode"
40
+ style={[
41
+ styles.callingCode,
42
+ countrySelectStyle?.popup?.callingCode,
43
+ ]}>
44
+ {item.idd.root}
45
+ </Text>
46
+ <Text
47
+ testID="countrySelectItemName"
48
+ style={[
49
+ styles.countryName,
50
+ countrySelectStyle?.popup?.countryName,
51
+ ]}>
52
+ {item?.translations[language]?.common ||
53
+ item?.translations[DEFAULT_LANGUAGE]?.common}
54
+ </Text>
55
+ </View>
56
+ </TouchableOpacity>
57
+ );
58
+ },
59
+ );
@@ -1,159 +1,319 @@
1
- /* eslint-disable react-native/no-inline-styles */
2
- /* eslint-disable react-hooks/exhaustive-deps */
3
- import React, {useCallback, useMemo, useState} from 'react';
4
- import {
5
- View,
6
- TextInput,
7
- FlatList,
8
- Pressable,
9
- ListRenderItem,
10
- Modal,
11
- } from 'react-native';
12
-
13
- import {CountryItem} from '../CountryItem';
14
-
15
- import {createStyles} from '../styles';
16
- import countries from '../../constants/countries.json';
17
- import {translations} from '../../utils/getTranslation';
18
- import {
19
- ICountry,
20
- ICountrySelectProps,
21
- ICountrySelectLanguages,
22
- } from '../../interface';
23
-
24
- const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
25
-
26
- export const CountrySelect: React.FC<ICountrySelectProps> = ({
27
- visible,
28
- onClose,
29
- onSelect,
30
- theme = 'light',
31
- language = DEFAULT_LANGUAGE,
32
- ...props
33
- }) => {
34
- const styles = createStyles(theme);
35
-
36
- const [searchQuery, setSearchQuery] = useState('');
37
-
38
- // Obtain the country name in the selected language
39
- const getCountryNameInLanguage = (country: ICountry): string => {
40
- return (
41
- country.translations[language]?.common ||
42
- country.translations[DEFAULT_LANGUAGE]?.common ||
43
- country.name.common ||
44
- ''
45
- );
46
- };
47
-
48
- // Normalize country name and remove accents
49
- const normalizeCountryName = (str: string) =>
50
- str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
51
-
52
- const sortCountriesAlphabetically = (
53
- countriesList: ICountry[],
54
- ): ICountry[] => {
55
- return [...countriesList].sort((a, b) => {
56
- const nameA = normalizeCountryName(
57
- getCountryNameInLanguage(a).toLowerCase(),
58
- );
59
- const nameB = normalizeCountryName(
60
- getCountryNameInLanguage(b).toLowerCase(),
61
- );
62
- return nameA.localeCompare(nameB);
63
- });
64
- };
65
-
66
- const getCountries = useMemo(() => {
67
- const query = searchQuery.toLowerCase();
68
-
69
- if (query.length > 0) {
70
- let countriesData = countries as unknown as ICountry[];
71
-
72
- const filteredCountries = countriesData.filter(country => {
73
- const countryName = getCountryNameInLanguage(country);
74
- const callingCode = country.idd.root.toLowerCase();
75
- const flag = country.flag.toLowerCase();
76
- return (
77
- countryName.includes(query) ||
78
- callingCode.includes(query) ||
79
- flag.includes(query)
80
- );
81
- });
82
-
83
- return sortCountriesAlphabetically(filteredCountries);
84
- }
85
-
86
- let allCountries = countries as unknown as ICountry[];
87
-
88
- const result: ICountry[] = allCountries;
89
-
90
- return result;
91
- }, [searchQuery]);
92
-
93
- const keyExtractor = useCallback((item: ICountry) => item.cca2, []);
94
-
95
- const renderItem: ListRenderItem<ICountry> = useCallback(
96
- ({item}) => {
97
- return (
98
- <CountryItem
99
- item={item as ICountry}
100
- onSelect={onSelect}
101
- onClose={onClose}
102
- theme={theme}
103
- language={language}
104
- />
105
- );
106
- },
107
- [onSelect, onClose, styles, language],
108
- );
109
-
110
- return (
111
- <Modal
112
- visible={visible}
113
- transparent
114
- animationType="fade"
115
- onRequestClose={onClose}
116
- statusBarTranslucent
117
- {...props}>
118
- <Pressable
119
- style={[
120
- styles.backdrop,
121
- {alignItems: 'center', justifyContent: 'center'},
122
- ]}
123
- onPress={onClose}>
124
- <Pressable style={styles.popupContainer}>
125
- <View style={styles.popupContent}>
126
- <View style={styles.searchContainer}>
127
- <TextInput
128
- testID="countrySelectSearchInput"
129
- accessibilityRole="text"
130
- accessibilityLabel="Country Select Search Input"
131
- accessibilityHint="Type to search for a country"
132
- style={styles.searchInput}
133
- placeholder={
134
- translations.searchPlaceholder[
135
- language as ICountrySelectLanguages
136
- ]
137
- }
138
- placeholderTextColor={styles.searchInputPlaceholder.color}
139
- value={searchQuery}
140
- onChangeText={setSearchQuery}
141
- />
142
- </View>
143
-
144
- <FlatList
145
- testID="countrySelectList"
146
- accessibilityRole="list"
147
- accessibilityLabel="Country Select List"
148
- accessibilityHint="List of countries"
149
- data={getCountries}
150
- keyExtractor={keyExtractor}
151
- renderItem={renderItem}
152
- keyboardShouldPersistTaps="handled"
153
- />
154
- </View>
155
- </Pressable>
156
- </Pressable>
157
- </Modal>
158
- );
159
- };
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ /* eslint-disable react-hooks/exhaustive-deps */
3
+ import React, {useCallback, useMemo, useState} from 'react';
4
+ import {
5
+ View,
6
+ TextInput,
7
+ FlatList,
8
+ Pressable,
9
+ ListRenderItem,
10
+ Modal,
11
+ Text,
12
+ TouchableOpacity,
13
+ } from 'react-native';
14
+
15
+ import {CountryItem} from '../CountryItem';
16
+
17
+ import {createStyles} from '../styles';
18
+ import countries from '../../constants/countries.json';
19
+ import {translations} from '../../utils/getTranslation';
20
+ import {
21
+ ICountry,
22
+ ICountrySelectProps,
23
+ ICountrySelectLanguages,
24
+ IListItem,
25
+ } from '../../interface';
26
+
27
+ const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
28
+
29
+ export const CountrySelect: React.FC<ICountrySelectProps> = ({
30
+ visible,
31
+ onClose,
32
+ onSelect,
33
+ theme = 'light',
34
+ isFullScreen = false,
35
+ countrySelectStyle,
36
+ popularCountries = [],
37
+ visibleCountries = [],
38
+ hiddenCountries = [],
39
+ language = DEFAULT_LANGUAGE,
40
+ showSearchInput = true,
41
+ searchPlaceholder,
42
+ disabledBackdropPress,
43
+ removedBackdrop,
44
+ onBackdropPress,
45
+ sectionTitleComponent,
46
+ countryItemComponent,
47
+ popularCountriesTitle,
48
+ allCountriesTitle,
49
+ showsVerticalScrollIndicator = false,
50
+ ...props
51
+ }) => {
52
+ const styles = createStyles(theme);
53
+
54
+ const [searchQuery, setSearchQuery] = useState('');
55
+
56
+ // Obtain the country name in the selected language
57
+ const getCountryNameInLanguage = (country: ICountry): string => {
58
+ return (
59
+ country.translations[language]?.common ||
60
+ country.translations[DEFAULT_LANGUAGE]?.common ||
61
+ country.name.common ||
62
+ ''
63
+ );
64
+ };
65
+
66
+ // Normalize country name and remove accents
67
+ const normalizeCountryName = (str: string) =>
68
+ str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
69
+
70
+ const sortCountriesAlphabetically = (
71
+ countriesList: ICountry[],
72
+ ): ICountry[] => {
73
+ return [...countriesList].sort((a, b) => {
74
+ const nameA = normalizeCountryName(
75
+ getCountryNameInLanguage(a).toLowerCase(),
76
+ );
77
+ const nameB = normalizeCountryName(
78
+ getCountryNameInLanguage(b).toLowerCase(),
79
+ );
80
+ return nameA.localeCompare(nameB);
81
+ });
82
+ };
83
+
84
+ const getCountries = useMemo(() => {
85
+ const query = searchQuery.toLowerCase();
86
+
87
+ if (query.length > 0) {
88
+ let countriesData = countries as unknown as ICountry[];
89
+
90
+ if (visibleCountries.length > 0) {
91
+ countriesData = (countries as unknown as ICountry[]).filter(country =>
92
+ visibleCountries.includes(country.cca2),
93
+ );
94
+ }
95
+
96
+ if (hiddenCountries.length > 0) {
97
+ countriesData = (countries as unknown as ICountry[]).filter(
98
+ country => !hiddenCountries.includes(country.cca2),
99
+ );
100
+ }
101
+
102
+ const filteredCountries = countriesData.filter(country => {
103
+ const countryName = getCountryNameInLanguage(country);
104
+ const normalizedCountryName = normalizeCountryName(
105
+ countryName.toLowerCase(),
106
+ );
107
+ const normalizedQuery = normalizeCountryName(query);
108
+ const callingCode = country.idd.root.toLowerCase();
109
+ const flag = country.flag.toLowerCase();
110
+ const countryCode = country.cca2.toLowerCase();
111
+
112
+ return (
113
+ normalizedCountryName.includes(normalizedQuery) ||
114
+ countryName.toLowerCase().includes(query) ||
115
+ callingCode.includes(query) ||
116
+ flag.includes(query) ||
117
+ countryCode.includes(query)
118
+ );
119
+ });
120
+
121
+ return sortCountriesAlphabetically(filteredCountries);
122
+ }
123
+
124
+ let allCountries = countries as unknown as ICountry[];
125
+
126
+ if (visibleCountries.length > 0) {
127
+ allCountries = (countries as unknown as ICountry[]).filter(country =>
128
+ visibleCountries.includes(country.cca2),
129
+ );
130
+ }
131
+
132
+ if (hiddenCountries.length > 0) {
133
+ allCountries = (countries as unknown as ICountry[]).filter(
134
+ country => !hiddenCountries.includes(country.cca2),
135
+ );
136
+ }
137
+
138
+ const popularCountriesData = sortCountriesAlphabetically(
139
+ allCountries.filter(country => popularCountries.includes(country.cca2)),
140
+ );
141
+
142
+ const otherCountriesData = sortCountriesAlphabetically(
143
+ allCountries.filter(country => !popularCountries.includes(country.cca2)),
144
+ );
145
+
146
+ const result: IListItem[] = [];
147
+
148
+ if (popularCountriesData.length > 0) {
149
+ result.push({
150
+ isSection: true as const,
151
+ title:
152
+ translations.popularCountriesTitle[
153
+ language as ICountrySelectLanguages
154
+ ],
155
+ });
156
+ result.push(...popularCountriesData);
157
+ result.push({
158
+ isSection: true as const,
159
+ title:
160
+ translations.allCountriesTitle[language as ICountrySelectLanguages],
161
+ });
162
+ }
163
+
164
+ result.push(...otherCountriesData);
165
+
166
+ return result;
167
+ }, [
168
+ searchQuery,
169
+ popularCountries,
170
+ language,
171
+ visibleCountries,
172
+ hiddenCountries,
173
+ ]);
174
+
175
+ const keyExtractor = useCallback(
176
+ (item: IListItem) => ('isSection' in item ? item.title : item.cca2),
177
+ [],
178
+ );
179
+
180
+ const renderItem: ListRenderItem<IListItem> = useCallback(
181
+ ({item, index}) => {
182
+ if ('isSection' in item) {
183
+ if (sectionTitleComponent) {
184
+ return sectionTitleComponent(item);
185
+ }
186
+ return (
187
+ <Text
188
+ testID="countrySelectSectionTitle"
189
+ accessibilityRole="header"
190
+ style={[
191
+ styles.sectionTitle,
192
+ countrySelectStyle?.popup?.sectionTitle,
193
+ ]}>
194
+ {popularCountriesTitle && index === 0
195
+ ? popularCountriesTitle
196
+ : allCountriesTitle && index > 0
197
+ ? allCountriesTitle
198
+ : item.title}
199
+ </Text>
200
+ );
201
+ }
202
+
203
+ if (countryItemComponent) {
204
+ return countryItemComponent(item as ICountry);
205
+ }
206
+
207
+ return (
208
+ <CountryItem
209
+ item={item as ICountry}
210
+ onSelect={onSelect}
211
+ onClose={onClose}
212
+ theme={theme}
213
+ language={language}
214
+ countrySelectStyle={countrySelectStyle}
215
+ />
216
+ );
217
+ },
218
+ [
219
+ onSelect,
220
+ onClose,
221
+ styles,
222
+ language,
223
+ countryItemComponent,
224
+ sectionTitleComponent,
225
+ ],
226
+ );
227
+
228
+ return (
229
+ <Modal
230
+ visible={visible}
231
+ transparent
232
+ animationType="fade"
233
+ onRequestClose={onClose}
234
+ statusBarTranslucent
235
+ {...props}>
236
+ <Pressable
237
+ style={[
238
+ styles.backdrop,
239
+ {alignItems: 'center', justifyContent: 'center'},
240
+ countrySelectStyle?.popup?.backdrop,
241
+ removedBackdrop && {backgroundColor: 'transparent'},
242
+ ]}
243
+ disabled={disabledBackdropPress || removedBackdrop}
244
+ onPress={onBackdropPress || onClose}>
245
+ <Pressable
246
+ style={[
247
+ styles.popupContainer,
248
+ countrySelectStyle?.popup?.popupContainer,
249
+ isFullScreen && {flex: 1, width: '100%', height: '100%'},
250
+ ]}>
251
+ <View
252
+ style={[
253
+ styles.popupContent,
254
+ countrySelectStyle?.popup?.popupContent,
255
+ ]}>
256
+ <View
257
+ style={[
258
+ styles.searchContainer,
259
+ countrySelectStyle?.popup?.searchContainer,
260
+ ]}>
261
+ {isFullScreen && (
262
+ <TouchableOpacity
263
+ style={[
264
+ styles.closeButton,
265
+ countrySelectStyle?.popup?.closeButton,
266
+ ]}
267
+ activeOpacity={0.6}
268
+ onPress={onClose}>
269
+ <Text
270
+ style={[
271
+ styles.closeButtonText,
272
+ countrySelectStyle?.popup?.closeButtonText,
273
+ ]}>
274
+ {'\u00D7'}
275
+ </Text>
276
+ </TouchableOpacity>
277
+ )}
278
+ {showSearchInput && (
279
+ <TextInput
280
+ testID="countrySelectSearchInput"
281
+ accessibilityRole="text"
282
+ accessibilityLabel="Country Select Search Input"
283
+ accessibilityHint="Type to search for a country"
284
+ style={[
285
+ styles.searchInput,
286
+ countrySelectStyle?.popup?.searchInput,
287
+ ]}
288
+ placeholder={
289
+ searchPlaceholder ||
290
+ translations.searchPlaceholder[
291
+ language as ICountrySelectLanguages
292
+ ]
293
+ }
294
+ placeholderTextColor={styles.searchInputPlaceholder.color}
295
+ value={searchQuery}
296
+ onChangeText={setSearchQuery}
297
+ />
298
+ )}
299
+ </View>
300
+
301
+ <FlatList
302
+ testID="countrySelectList"
303
+ accessibilityRole="list"
304
+ accessibilityLabel="Country Select List"
305
+ accessibilityHint="List of countries"
306
+ data={getCountries}
307
+ keyExtractor={keyExtractor}
308
+ renderItem={renderItem}
309
+ keyboardShouldPersistTaps="handled"
310
+ showsVerticalScrollIndicator={
311
+ showsVerticalScrollIndicator || false
312
+ }
313
+ />
314
+ </View>
315
+ </Pressable>
316
+ </Pressable>
317
+ </Modal>
318
+ );
319
+ };
@@ -1,2 +1,2 @@
1
- export {CountrySelect} from './CountrySelect';
2
- export {CountryItem} from './CountryItem';
1
+ export {CountrySelect} from './CountrySelect';
2
+ export {CountryItem} from './CountryItem';