react-native-country-select 0.1.1 → 0.1.2

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