react-native-country-select 0.2.6 → 0.3.1

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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <br>
2
2
 
3
3
  <div align = "center">
4
- <img src="lib/assets/images/preview.png" alt="React Native International Phone Number Input Lib preview">
4
+ <img src="lib/assets/images/preview.png" alt="React Native Country Picker and Select Lib preview">
5
5
  </div>
6
6
 
7
7
  <br>
@@ -9,7 +9,7 @@
9
9
  <h1 align="center">React Native Country Select</h1>
10
10
 
11
11
  <p>
12
- 🌍 A lightweight and customizable country picker for React Native with modern UI, flags, search engine, and i18n support. Includes TypeScript types, offline support and no dependencies.
12
+ 🌍 React Native country picker with flags, search, TypeScript, i18n, and offline support. Lightweight, customizable, and designed with a modern UI.
13
13
  </p>
14
14
 
15
15
  <br>
@@ -246,28 +246,75 @@ export default function App() {
246
246
 
247
247
  <br>
248
248
 
249
+ - Multi Select Country
250
+
251
+ ```tsx
252
+ import React, {useState} from 'react';
253
+ import {Text, TouchableOpacity, View} from 'react-native';
254
+
255
+ import CountrySelect, {ICountry} from 'react-native-country-select';
256
+
257
+ export default function App() {
258
+ const [showPicker, setShowPicker] = useState<boolean>(false);
259
+ const [selectedCountries, setSelectedCountries] = useState<ICountry[]>([]);
260
+
261
+ const handleCountrySelect = (countries: ICountry[]) => {
262
+ setSelectedCountries(countries);
263
+ };
264
+
265
+ return (
266
+ <View
267
+ style={{
268
+ flex: 1,
269
+ }}>
270
+ <TouchableOpacity onPress={() => setShowPicker(true)}>
271
+ <Text>Select Countries</Text>
272
+ </TouchableOpacity>
273
+ <Text>Countries: {selectedCountries.length}</Text>
274
+
275
+ <CountrySelect
276
+ visible={showPicker}
277
+ isMultiSelect
278
+ selectedCountries={selectedCountries}
279
+ onSelect={handleCountrySelect}
280
+ onClose={() => setShowPicker(false)}
281
+ />
282
+ </View>
283
+ );
284
+ }
285
+ ```
286
+
287
+ <br>
288
+
249
289
  ### Modal Styles ([modalStyles](https://github.com/AstrOOnauta/react-native-country-select/blob/main/lib/interface/countrySelectStyles.ts))
250
290
 
251
- | Property | Type | Description |
252
- | -------------------------- | --------- | ------------------------- |
253
- | `backdrop` | ViewStyle | Modal background overlay |
254
- | `container` | ViewStyle | Modal main container |
255
- | `content` | ViewStyle | Modal content area |
256
- | `dragHandleContainer` | ViewStyle | Drag Handle area |
257
- | `dragHandleIndicator` | ViewStyle | Drag Handle Indicator |
258
- | `searchContainer` | ViewStyle | Search input wrapper |
259
- | `searchInput` | TextStyle | Search input field |
260
- | `list` | ViewStyle | Countries list container |
261
- | `countryItem` | ViewStyle | Individual country row |
262
- | `flag` | TextStyle | Country flag in list |
263
- | `countryInfo` | ViewStyle | Country details container |
264
- | `callingCode` | TextStyle | Calling code in list |
265
- | `countryName` | TextStyle | Country name in list |
266
- | `sectionTitle` | TextStyle | Section headers |
267
- | `closeButton` | ViewStyle | Close button container |
268
- | `closeButtonText` | TextStyle | Close button text |
269
- | `countryNotFoundContainer` | ViewStyle | No results container |
270
- | `countryNotFoundMessage` | TextStyle | No results message |
291
+ | Property | Type | Description |
292
+ | ---------------------------- | --------- | ------------------------- |
293
+ | `backdrop` | ViewStyle | Modal background overlay |
294
+ | `container` | ViewStyle | Modal main container |
295
+ | `content` | ViewStyle | Modal content area |
296
+ | `dragHandleContainer` | ViewStyle | Drag Handle area |
297
+ | `dragHandleIndicator` | ViewStyle | Drag Handle Indicator |
298
+ | `searchContainer` | ViewStyle | Search input wrapper |
299
+ | `searchInput` | TextStyle | Search input field |
300
+ | `list` | ViewStyle | Countries list container |
301
+ | `countryItem` | ViewStyle | Individual country row |
302
+ | `flag` | TextStyle | Country flag in list |
303
+ | `countryInfo` | ViewStyle | Country details container |
304
+ | `callingCode` | TextStyle | Calling code in list |
305
+ | `countryName` | TextStyle | Country name in list |
306
+ | `sectionTitle` | TextStyle | Section headers |
307
+ | `closeButton` | ViewStyle | Close button container |
308
+ | `closeButtonText` | TextStyle | Close button text |
309
+ | `countryNotFoundContainer` | ViewStyle | No results container |
310
+ | `countryNotFoundMessage` | TextStyle | No results message |
311
+ | `alphabetContainer` | ViewStyle | Alphabet filter container |
312
+ | `alphabetLetter` | ViewStyle | Alphabet letter item |
313
+ | `alphabetLetterText` | TextStyle | Alphabet letter text |
314
+ | `alphabetLetterActive` | ViewStyle | Active letter state |
315
+ | `alphabetLetterDisabled` | ViewStyle | Disabled letter state |
316
+ | `alphabetLetterTextActive` | TextStyle | Active letter text |
317
+ | `alphabetLetterTextDisabled` | TextStyle | Disabled letter text |
271
318
 
272
319
  <br>
273
320
 
@@ -280,6 +327,8 @@ export default function App() {
280
327
  | onSelect | (country: [ICountry](lib/interfaces/country.ts)) => void | Yes | - | Callback function called when a country is selected |
281
328
  | modalType | 'bottomSheet' \| 'popup' | No | 'bottomSheet' | Type of modal to display |
282
329
  | countrySelectStyle | [ICountrySelectStyle](lib/interfaces/countrySelectStyles.ts) | No | - | Custom styles for the country picker |
330
+ | isMultiSelect | boolean | No | false | Whether the user can select multiple options |
331
+ | selectedCountries | [ICountry[]](lib/interfaces/country.ts) | No | - | Array of countries to show in multi select mode |
283
332
  | isFullScreen | boolean | No | false | Whether the modal should be full screen |
284
333
  | popularCountries | string[] | No | [] | Array of country codes to show in popular section |
285
334
  | visibleCountries | [ICountryCca2[]](lib/interfaces/countryCca2.ts) | No | [] | Array of country codes to show (whitelist) |
@@ -287,6 +336,7 @@ export default function App() {
287
336
  | theme | 'light' \| 'dark' | No | 'light' | Theme for the country picker |
288
337
  | language | [ICountrySelectLanguages](lib/interfaces/countrySelectLanguages.ts) | No | 'eng' | Language for country names (see supported languages below) |
289
338
  | showSearchInput | boolean | No | true | Whether to show the search input field |
339
+ | showAlphabetFilter | boolean | No | false | Whether to show the alphabetic filter on modal |
290
340
  | searchPlaceholder | string | No | 'Search country...' | Placeholder text for search input |
291
341
  | searchPlaceholderTextColor | string | No | '#00000080' | Placeholder text color for search input |
292
342
  | searchSelectionColor | string | No | default | Highlight, selection handle and cursor color of the search input |
@@ -381,7 +431,11 @@ Ensure your app is inclusive and usable by everyone by leveraging built-in React
381
431
  - `accessibilityLabelCountriesList`: Accessibility label for the countries list;
382
432
  - `accessibilityHintCountriesList`: Accessibility hint for the countries list;
383
433
  - `accessibilityLabelCountryItem`: Accessibility label for individual country items;
384
- - `accessibilityHintCountryItem`: Accessibility hint for individual country.
434
+ - `accessibilityHintCountryItem`: Accessibility hint for individual country;
435
+ - `accessibilityLabelAlphabetFilter`: Accessibility label for alphabet filter list;
436
+ - `accessibilityHintAlphabetFilter`: Accessibility hint for alphabet filter list;
437
+ - `accessibilityLabelAlphabetLetter`: Accessibility label for individual alphabet filter letter;
438
+ - `accessibilityHintAlphabetLetter`: Accessibility hint for individual alphabet filter letter.
385
439
 
386
440
  <br>
387
441
 
Binary file
@@ -0,0 +1,180 @@
1
+ /* eslint-disable no-undef-init */
2
+ /* eslint-disable react-hooks/exhaustive-deps */
3
+ /* eslint-disable react-native/no-inline-styles */
4
+ import React, {useEffect, useMemo, useRef} from 'react';
5
+ import {ScrollView, Text, TouchableOpacity, View} from 'react-native';
6
+
7
+ import {createStyles} from '../styles';
8
+ import {translations} from '../../utils/getTranslation';
9
+ import {createAlphabet} from '../../utils/createAlphabet';
10
+ import {AlphabeticFilterProps} from '../../interface/alfabeticFilterProps';
11
+ import {normalizeCountryName} from '../../utils/normalizeCountryName';
12
+
13
+ const ALPHABET_VIEWPORT_HEIGHT = 0;
14
+ const ALPHABET_ITEM_SIZE = 28;
15
+ const ALPHABET_VERTICAL_PADDING = 12;
16
+
17
+ export const AlphabeticFilter: React.FC<AlphabeticFilterProps> = ({
18
+ activeLetter,
19
+ onPressLetter,
20
+ theme = 'light',
21
+ language,
22
+ countries,
23
+ allCountriesStartIndex,
24
+ countrySelectStyle,
25
+ accessibilityLabelAlphabetFilter,
26
+ accessibilityHintAlphabetFilter,
27
+ accessibilityLabelAlphabetLetter,
28
+ accessibilityHintAlphabetLetter,
29
+ }) => {
30
+ const styles = createStyles(theme);
31
+ const alphabetScrollRef = useRef<ScrollView>(null);
32
+
33
+ const letterIndexMap = useMemo(() => {
34
+ const map: Record<string, number> = {};
35
+ for (let i = allCountriesStartIndex; i < countries.length; i++) {
36
+ const item = countries[i];
37
+ if ('isSection' in item) {
38
+ continue;
39
+ }
40
+ const country: any = item as any;
41
+ // Use English/common name to anchor alphabet jumps consistently
42
+ const name = country?.name?.common || '';
43
+ const first = (name?.[0] || '').toUpperCase();
44
+ if (first && map[first] === undefined) {
45
+ map[first] = i;
46
+ }
47
+ }
48
+ return map;
49
+ }, [countries, allCountriesStartIndex, language]);
50
+
51
+ const alphabet = createAlphabet();
52
+
53
+ const scrollAlphabetToLetter = (letter: string) => {
54
+ const letterIdx = alphabet.indexOf(letter);
55
+ if (letterIdx >= 0) {
56
+ const centerOffset = Math.max(
57
+ 0,
58
+ ALPHABET_VIEWPORT_HEIGHT / 2 - ALPHABET_ITEM_SIZE / 2,
59
+ );
60
+ const y = Math.max(
61
+ 0,
62
+ letterIdx * ALPHABET_ITEM_SIZE -
63
+ centerOffset +
64
+ ALPHABET_VERTICAL_PADDING,
65
+ );
66
+ alphabetScrollRef.current?.scrollTo({y, animated: true});
67
+ }
68
+ };
69
+
70
+ useEffect(() => {
71
+ if (!activeLetter) {
72
+ return;
73
+ }
74
+ scrollAlphabetToLetter(activeLetter);
75
+ }, [activeLetter, alphabet]);
76
+
77
+ return (
78
+ <ScrollView
79
+ testID="countrySelectAlphabetFilter"
80
+ accessibilityRole="list"
81
+ accessibilityLabel={
82
+ accessibilityLabelAlphabetFilter ||
83
+ translations.accessibilityLabelAlphabetFilter[language]
84
+ }
85
+ accessibilityHint={
86
+ accessibilityHintAlphabetFilter ||
87
+ translations.accessibilityHintAlphabetFilter[language]
88
+ }
89
+ ref={alphabetScrollRef}
90
+ style={[styles.alphabetContainer, countrySelectStyle?.alphabetContainer]}
91
+ contentContainerStyle={{alignItems: 'center', paddingVertical: 12}}
92
+ showsVerticalScrollIndicator={false}>
93
+ {alphabet.map(letter => {
94
+ const enabled = letterIndexMap[letter] !== undefined;
95
+ const isActive = activeLetter === letter;
96
+ if (enabled) {
97
+ return (
98
+ <TouchableOpacity
99
+ key={letter}
100
+ onPress={() => {
101
+ // Compute first index for this letter using normalized display name (same as sorting)
102
+ const lower = letter.toLowerCase();
103
+ let idxToGo: number | undefined = undefined;
104
+ for (
105
+ let i = allCountriesStartIndex;
106
+ i < countries.length;
107
+ i++
108
+ ) {
109
+ const it = countries[i] as any;
110
+ if ('isSection' in it) {
111
+ continue;
112
+ }
113
+ const displayName =
114
+ it?.translations?.[language]?.common ||
115
+ it?.name?.common ||
116
+ '';
117
+ const normalized = normalizeCountryName(
118
+ displayName.toLowerCase(),
119
+ );
120
+ if (normalized.startsWith(lower)) {
121
+ idxToGo = i;
122
+ break;
123
+ }
124
+ }
125
+ if (idxToGo !== undefined) {
126
+ onPressLetter(idxToGo);
127
+ }
128
+ scrollAlphabetToLetter(letter);
129
+ }}
130
+ style={[
131
+ styles.alphabetLetter,
132
+ isActive && styles.alphabetLetterActive,
133
+ countrySelectStyle?.alphabetLetter,
134
+ ]}
135
+ accessibilityRole="button"
136
+ accessibilityHint={
137
+ accessibilityHintAlphabetLetter ||
138
+ translations.accessibilityHintAlphabetLetter[language] +
139
+ ` ${letter}`
140
+ }
141
+ accessibilityLabel={
142
+ accessibilityLabelAlphabetLetter ||
143
+ translations.accessibilityLabelAlphabetLetter[language] +
144
+ ` ${letter}`
145
+ }>
146
+ <Text
147
+ style={[
148
+ styles.alphabetLetterText,
149
+ isActive && styles.alphabetLetterTextActive,
150
+ countrySelectStyle?.alphabetLetterText,
151
+ ]}>
152
+ {letter}
153
+ </Text>
154
+ </TouchableOpacity>
155
+ );
156
+ }
157
+ return (
158
+ <View
159
+ key={letter}
160
+ style={[
161
+ styles.alphabetLetter,
162
+ styles.alphabetLetterDisabled,
163
+ countrySelectStyle?.alphabetLetter,
164
+ ]}>
165
+ <Text
166
+ style={[
167
+ styles.alphabetLetterText,
168
+ styles.alphabetLetterTextDisabled,
169
+ countrySelectStyle?.alphabetLetterText,
170
+ ]}>
171
+ {letter}
172
+ </Text>
173
+ </View>
174
+ );
175
+ })}
176
+ </ScrollView>
177
+ );
178
+ };
179
+
180
+ export default AlphabeticFilter;
@@ -0,0 +1,223 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
3
+ import {
4
+ Animated,
5
+ Modal,
6
+ ModalProps,
7
+ Pressable,
8
+ View,
9
+ PanResponder,
10
+ Keyboard,
11
+ NativeSyntheticEvent,
12
+ } from 'react-native';
13
+
14
+ import parseHeight from '../../utils/parseHeight';
15
+ import {ICountrySelectStyle} from '../../interface';
16
+
17
+ interface BottomSheetModalProps extends ModalProps {
18
+ visible: boolean;
19
+ onRequestClose: (event: NativeSyntheticEvent<any>) => void;
20
+ statusBarTranslucent?: boolean;
21
+ removedBackdrop?: boolean;
22
+ disabledBackdropPress?: boolean;
23
+ onBackdropPress?: () => void;
24
+ accessibilityLabelBackdrop?: string;
25
+ accessibilityHintBackdrop?: string;
26
+ styles: ICountrySelectStyle;
27
+ countrySelectStyle?: ICountrySelectStyle;
28
+ minBottomsheetHeight?: number | string;
29
+ maxBottomsheetHeight?: number | string;
30
+ initialBottomsheetHeight?: number | string;
31
+ dragHandleIndicatorComponent?: () => React.ReactElement;
32
+ header?: React.ReactNode;
33
+ children: React.ReactNode;
34
+ }
35
+
36
+ const MIN_HEIGHT_PERCENTAGE = 0.3;
37
+ const MAX_HEIGHT_PERCENTAGE = 0.9;
38
+ const INITIAL_HEIGHT_PERCENTAGE = 0.5;
39
+
40
+ export const BottomSheetModal: React.FC<BottomSheetModalProps> = ({
41
+ visible,
42
+ onRequestClose,
43
+ statusBarTranslucent,
44
+ removedBackdrop,
45
+ disabledBackdropPress,
46
+ onBackdropPress,
47
+ accessibilityLabelBackdrop,
48
+ accessibilityHintBackdrop,
49
+ styles,
50
+ countrySelectStyle,
51
+ minBottomsheetHeight,
52
+ maxBottomsheetHeight,
53
+ initialBottomsheetHeight,
54
+ dragHandleIndicatorComponent,
55
+ header,
56
+ children,
57
+ ...props
58
+ }) => {
59
+ const [modalHeight, setModalHeight] = useState(0);
60
+ const [bottomSheetSize, setBottomSheetSize] = useState({
61
+ minHeight: 0,
62
+ maxHeight: 0,
63
+ initialHeight: 0,
64
+ });
65
+ const sheetHeight = useRef(new Animated.Value(0)).current;
66
+ const lastHeightRef = useRef(0);
67
+ const dragStartYRef = useRef(0);
68
+
69
+ useEffect(() => {
70
+ const DRAG_HANDLE_HEIGHT = 20;
71
+ const availableHeight = Math.max(0, modalHeight - DRAG_HANDLE_HEIGHT);
72
+ const parsedMinHeight = parseHeight(minBottomsheetHeight, availableHeight);
73
+ const parsedMaxHeight = parseHeight(maxBottomsheetHeight, availableHeight);
74
+ const parsedInitialHeight = parseHeight(
75
+ initialBottomsheetHeight,
76
+ availableHeight,
77
+ );
78
+ setBottomSheetSize({
79
+ minHeight: parsedMinHeight || MIN_HEIGHT_PERCENTAGE * availableHeight,
80
+ maxHeight: parsedMaxHeight || MAX_HEIGHT_PERCENTAGE * availableHeight,
81
+ initialHeight:
82
+ parsedInitialHeight || INITIAL_HEIGHT_PERCENTAGE * availableHeight,
83
+ });
84
+ }, [
85
+ modalHeight,
86
+ minBottomsheetHeight,
87
+ maxBottomsheetHeight,
88
+ initialBottomsheetHeight,
89
+ ]);
90
+
91
+ useEffect(() => {
92
+ if (visible) {
93
+ sheetHeight.setValue(bottomSheetSize.initialHeight);
94
+ lastHeightRef.current = bottomSheetSize.initialHeight;
95
+ }
96
+ }, [visible, bottomSheetSize.initialHeight, sheetHeight]);
97
+
98
+ useEffect(() => {
99
+ const show = Keyboard.addListener('keyboardDidShow', () => {
100
+ sheetHeight.setValue(bottomSheetSize.maxHeight);
101
+ lastHeightRef.current = bottomSheetSize.maxHeight;
102
+ });
103
+ const hide = Keyboard.addListener('keyboardDidHide', () => {
104
+ sheetHeight.setValue(lastHeightRef.current);
105
+ });
106
+ return () => {
107
+ show?.remove();
108
+ hide?.remove();
109
+ };
110
+ }, [bottomSheetSize.maxHeight, sheetHeight]);
111
+
112
+ const panHandlers = useMemo(
113
+ () =>
114
+ PanResponder.create({
115
+ onStartShouldSetPanResponder: () => true,
116
+ onMoveShouldSetPanResponder: (_evt, gestureState) =>
117
+ Math.abs(gestureState.dy) > 5,
118
+ onPanResponderGrant: e => {
119
+ dragStartYRef.current = e.nativeEvent.pageY;
120
+ sheetHeight.stopAnimation();
121
+ },
122
+ onPanResponderMove: e => {
123
+ const currentY = e.nativeEvent.pageY;
124
+ const dy = currentY - dragStartYRef.current;
125
+ const proposedHeight = lastHeightRef.current - dy;
126
+ sheetHeight.setValue(proposedHeight);
127
+ },
128
+ onPanResponderRelease: e => {
129
+ const currentY = e.nativeEvent.pageY;
130
+ const dy = currentY - dragStartYRef.current;
131
+ const currentHeight = lastHeightRef.current - dy;
132
+ if (currentHeight < bottomSheetSize.minHeight) {
133
+ Animated.timing(sheetHeight, {
134
+ toValue: 0,
135
+ duration: 200,
136
+ useNativeDriver: false,
137
+ }).start(() => onRequestClose({} as NativeSyntheticEvent<any>));
138
+ return;
139
+ }
140
+ const finalHeight = Math.min(
141
+ Math.max(currentHeight, bottomSheetSize.minHeight),
142
+ bottomSheetSize.maxHeight,
143
+ );
144
+ Animated.spring(sheetHeight, {
145
+ toValue: finalHeight,
146
+ useNativeDriver: false,
147
+ tension: 50,
148
+ friction: 12,
149
+ }).start(() => {
150
+ lastHeightRef.current = finalHeight;
151
+ });
152
+ },
153
+ onPanResponderTerminate: () => {
154
+ Animated.spring(sheetHeight, {
155
+ toValue: lastHeightRef.current,
156
+ useNativeDriver: false,
157
+ tension: 50,
158
+ friction: 12,
159
+ }).start();
160
+ },
161
+ }),
162
+ [bottomSheetSize, sheetHeight, onRequestClose],
163
+ );
164
+ return (
165
+ <Modal
166
+ visible={visible}
167
+ transparent
168
+ animationType="slide"
169
+ onRequestClose={onRequestClose}
170
+ statusBarTranslucent={statusBarTranslucent}
171
+ {...props}>
172
+ <View
173
+ testID="countrySelectContainer"
174
+ style={[styles.container, countrySelectStyle?.container]}
175
+ onLayout={e => setModalHeight(e.nativeEvent.layout.height)}>
176
+ <Pressable
177
+ testID="countrySelectBackdrop"
178
+ accessibilityRole="button"
179
+ accessibilityLabel={accessibilityLabelBackdrop}
180
+ accessibilityHint={accessibilityHintBackdrop}
181
+ disabled={disabledBackdropPress || removedBackdrop}
182
+ style={[
183
+ styles.backdrop,
184
+ countrySelectStyle?.backdrop,
185
+ removedBackdrop && {backgroundColor: 'transparent'},
186
+ ]}
187
+ onPress={onBackdropPress || onRequestClose}
188
+ />
189
+ <Animated.View
190
+ testID="countrySelectContent"
191
+ style={[
192
+ styles.content,
193
+ countrySelectStyle?.content,
194
+ {
195
+ height: sheetHeight,
196
+ },
197
+ ]}>
198
+ <View
199
+ {...panHandlers.panHandlers}
200
+ style={[
201
+ styles.dragHandleContainer,
202
+ countrySelectStyle?.dragHandleContainer,
203
+ ]}>
204
+ {dragHandleIndicatorComponent ? (
205
+ dragHandleIndicatorComponent()
206
+ ) : (
207
+ <View
208
+ style={[
209
+ styles.dragHandleIndicator,
210
+ countrySelectStyle?.dragHandleIndicator,
211
+ ]}
212
+ />
213
+ )}
214
+ </View>
215
+ {header}
216
+ <Animated.View style={{flex: 1, flexDirection: 'row'}}>
217
+ {children}
218
+ </Animated.View>
219
+ </Animated.View>
220
+ </View>
221
+ </Modal>
222
+ );
223
+ };
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import {Text, TouchableOpacity} from 'react-native';
3
+
4
+ import {createStyles} from '../styles';
5
+ import {ICloseButtonProps} from '../../interface';
6
+ import {translations} from '../../utils/getTranslation';
7
+
8
+ export const CloseButton: React.FC<ICloseButtonProps> = ({
9
+ theme,
10
+ language,
11
+ onClose,
12
+ countrySelectStyle,
13
+ accessibilityLabelCloseButton,
14
+ accessibilityHintCloseButton,
15
+ }) => {
16
+ const styles = createStyles(theme);
17
+ return (
18
+ <TouchableOpacity
19
+ testID="countrySelectCloseButton"
20
+ accessibilityRole="button"
21
+ accessibilityLabel={
22
+ accessibilityLabelCloseButton ||
23
+ translations.accessibilityLabelCloseButton[language]
24
+ }
25
+ accessibilityHint={
26
+ accessibilityHintCloseButton ||
27
+ translations.accessibilityHintCloseButton[language]
28
+ }
29
+ style={[styles.closeButton, countrySelectStyle?.closeButton]}
30
+ activeOpacity={0.6}
31
+ onPress={onClose}>
32
+ <Text
33
+ style={[styles.closeButtonText, countrySelectStyle?.closeButtonText]}>
34
+ {'\u00D7'}
35
+ </Text>
36
+ </TouchableOpacity>
37
+ );
38
+ };
@@ -3,17 +3,15 @@ import {View, Text, TouchableOpacity} from 'react-native';
3
3
 
4
4
  import {createStyles} from '../styles';
5
5
  import {translations} from '../../utils/getTranslation';
6
- import {ICountryItemProps, ICountrySelectLanguages} from '../../interface';
7
-
8
- const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
6
+ import {ICountryItemProps} from '../../interface';
9
7
 
10
8
  export const CountryItem = memo<ICountryItemProps>(
11
9
  ({
12
- item,
10
+ country,
11
+ isSelected,
13
12
  onSelect,
14
- onClose,
15
13
  theme = 'light',
16
- language,
14
+ language = 'eng',
17
15
  countrySelectStyle,
18
16
  accessibilityLabel,
19
17
  accessibilityHint,
@@ -26,33 +24,43 @@ export const CountryItem = memo<ICountryItemProps>(
26
24
  accessibilityRole="button"
27
25
  accessibilityLabel={
28
26
  accessibilityLabel ||
29
- translations.accessibilityLabelCountryItem[language]
27
+ translations.accessibilityLabelCountryItem[language] +
28
+ ` ${country.translations[language]?.common}`
30
29
  }
31
30
  accessibilityHint={
32
31
  accessibilityHint ||
33
- translations.accessibilityHintCountryItem[language]
32
+ translations.accessibilityHintCountryItem[language] +
33
+ ` ${country.translations[language]?.common}`
34
34
  }
35
- style={[styles.countryItem, countrySelectStyle?.countryItem]}
36
- onPress={() => {
37
- onSelect(item);
38
- onClose();
39
- }}>
35
+ style={[
36
+ styles.countryItem,
37
+ countrySelectStyle?.countryItem,
38
+ isSelected && styles.countryItemSelected,
39
+ ]}
40
+ onPress={() => onSelect(country)}>
40
41
  <Text
41
42
  testID="countrySelectItemFlag"
42
43
  style={[styles.flag, countrySelectStyle?.flag]}>
43
- {item.flag || item.cca2}
44
+ {country.flag || country.cca2}
44
45
  </Text>
45
46
  <View style={[styles.countryInfo, countrySelectStyle?.countryInfo]}>
46
47
  <Text
47
48
  testID="countrySelectItemCallingCode"
48
- style={[styles.callingCode, countrySelectStyle?.callingCode]}>
49
- {item.idd.root}
49
+ style={[
50
+ styles.callingCode,
51
+ countrySelectStyle?.callingCode,
52
+ isSelected && styles.callingCodeSelected,
53
+ ]}>
54
+ {country.idd.root}
50
55
  </Text>
51
56
  <Text
52
57
  testID="countrySelectItemName"
53
- style={[styles.countryName, countrySelectStyle?.countryName]}>
54
- {item?.translations[language]?.common ||
55
- item?.translations[DEFAULT_LANGUAGE]?.common}
58
+ style={[
59
+ styles.countryName,
60
+ countrySelectStyle?.countryName,
61
+ isSelected && styles.countryNameSelected,
62
+ ]}>
63
+ {country?.translations[language]?.common}
56
64
  </Text>
57
65
  </View>
58
66
  </TouchableOpacity>