react-native-country-select 0.2.5 → 0.3.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.
@@ -0,0 +1,85 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ import React from 'react';
3
+ import {
4
+ Modal,
5
+ ModalProps,
6
+ NativeSyntheticEvent,
7
+ Pressable,
8
+ View,
9
+ } from 'react-native';
10
+
11
+ import {ICountrySelectStyle} from '../../interface';
12
+
13
+ interface FullscreenModalProps extends ModalProps {
14
+ visible: boolean;
15
+ onRequestClose: (event: NativeSyntheticEvent<any>) => void;
16
+ statusBarTranslucent?: boolean;
17
+ removedBackdrop?: boolean;
18
+ disabledBackdropPress?: boolean;
19
+ onBackdropPress?: () => void;
20
+ accessibilityLabelBackdrop?: string;
21
+ accessibilityHintBackdrop?: string;
22
+ styles: ICountrySelectStyle;
23
+ countrySelectStyle?: ICountrySelectStyle;
24
+ header?: React.ReactNode;
25
+ children: React.ReactNode;
26
+ }
27
+
28
+ export const FullscreenModal: React.FC<FullscreenModalProps> = ({
29
+ visible,
30
+ onRequestClose,
31
+ statusBarTranslucent,
32
+ removedBackdrop,
33
+ disabledBackdropPress,
34
+ onBackdropPress,
35
+ accessibilityLabelBackdrop,
36
+ accessibilityHintBackdrop,
37
+ styles,
38
+ countrySelectStyle,
39
+ header,
40
+ children,
41
+ ...props
42
+ }) => {
43
+ return (
44
+ <Modal
45
+ visible={visible}
46
+ transparent
47
+ animationType="fade"
48
+ onRequestClose={onRequestClose}
49
+ statusBarTranslucent={statusBarTranslucent}
50
+ {...props}>
51
+ <View
52
+ testID="countrySelectContainer"
53
+ style={[
54
+ styles.container,
55
+ countrySelectStyle?.container,
56
+ {flex: 1, width: '100%', height: '100%'},
57
+ ]}>
58
+ <Pressable
59
+ testID="countrySelectBackdrop"
60
+ accessibilityRole="button"
61
+ accessibilityLabel={accessibilityLabelBackdrop}
62
+ accessibilityHint={accessibilityHintBackdrop}
63
+ disabled={disabledBackdropPress || removedBackdrop}
64
+ style={[
65
+ styles.backdrop,
66
+ {alignItems: 'center', justifyContent: 'center'},
67
+ countrySelectStyle?.backdrop,
68
+ removedBackdrop && {backgroundColor: 'transparent'},
69
+ ]}
70
+ onPress={onBackdropPress || onRequestClose}
71
+ />
72
+ <View
73
+ testID="countrySelectContent"
74
+ style={[
75
+ styles.content,
76
+ countrySelectStyle?.content,
77
+ {borderRadius: 0, width: '100%', height: '100%'},
78
+ ]}>
79
+ {header}
80
+ <View style={{flex: 1, flexDirection: 'row'}}>{children}</View>
81
+ </View>
82
+ </View>
83
+ </Modal>
84
+ );
85
+ };
@@ -0,0 +1,77 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ import React from 'react';
3
+ import {
4
+ Modal,
5
+ ModalProps,
6
+ NativeSyntheticEvent,
7
+ Pressable,
8
+ View,
9
+ } from 'react-native';
10
+
11
+ import {ICountrySelectStyle} from '../../interface';
12
+
13
+ interface PopupModalProps extends ModalProps {
14
+ visible: boolean;
15
+ onRequestClose: (event: NativeSyntheticEvent<any>) => void;
16
+ statusBarTranslucent?: boolean;
17
+ removedBackdrop?: boolean;
18
+ disabledBackdropPress?: boolean;
19
+ onBackdropPress?: () => void;
20
+ accessibilityLabelBackdrop?: string;
21
+ accessibilityHintBackdrop?: string;
22
+ styles: ICountrySelectStyle;
23
+ countrySelectStyle?: ICountrySelectStyle;
24
+ header?: React.ReactNode;
25
+ children: React.ReactNode;
26
+ }
27
+
28
+ export const PopupModal: React.FC<PopupModalProps> = ({
29
+ visible,
30
+ onRequestClose,
31
+ statusBarTranslucent,
32
+ removedBackdrop,
33
+ disabledBackdropPress,
34
+ onBackdropPress,
35
+ accessibilityLabelBackdrop,
36
+ accessibilityHintBackdrop,
37
+ styles,
38
+ countrySelectStyle,
39
+ header,
40
+ children,
41
+ ...props
42
+ }) => {
43
+ return (
44
+ <Modal
45
+ visible={visible}
46
+ transparent
47
+ animationType="fade"
48
+ onRequestClose={onRequestClose}
49
+ statusBarTranslucent={statusBarTranslucent}
50
+ {...props}>
51
+ <View
52
+ testID="countrySelectContainer"
53
+ style={[styles.container, countrySelectStyle?.container]}>
54
+ <Pressable
55
+ testID="countrySelectBackdrop"
56
+ accessibilityRole="button"
57
+ accessibilityLabel={accessibilityLabelBackdrop}
58
+ accessibilityHint={accessibilityHintBackdrop}
59
+ disabled={disabledBackdropPress || removedBackdrop}
60
+ style={[
61
+ styles.backdrop,
62
+ {alignItems: 'center', justifyContent: 'center'},
63
+ countrySelectStyle?.backdrop,
64
+ removedBackdrop && {backgroundColor: 'transparent'},
65
+ ]}
66
+ onPress={onBackdropPress || onRequestClose}
67
+ />
68
+ <View
69
+ testID="countrySelectContent"
70
+ style={[styles.content, countrySelectStyle?.content]}>
71
+ {header}
72
+ <View style={{flex: 1, flexDirection: 'row'}}>{children}</View>
73
+ </View>
74
+ </View>
75
+ </Modal>
76
+ );
77
+ };
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import {TextInput} from 'react-native';
3
+
4
+ import {createStyles} from '../styles';
5
+ import {translations} from '../../utils/getTranslation';
6
+ import {ISearchInputProps, ICountrySelectLanguages} from '../../interface';
7
+
8
+ export const SearchInput: React.FC<ISearchInputProps> = ({
9
+ theme,
10
+ language,
11
+ value,
12
+ onChangeText,
13
+ countrySelectStyle,
14
+ searchPlaceholder,
15
+ searchPlaceholderTextColor,
16
+ searchSelectionColor,
17
+ accessibilityLabelSearchInput,
18
+ accessibilityHintSearchInput,
19
+ }) => {
20
+ const styles = createStyles(theme);
21
+ return (
22
+ <TextInput
23
+ testID="countrySelectSearchInput"
24
+ accessibilityRole="text"
25
+ accessibilityLabel={
26
+ accessibilityLabelSearchInput ||
27
+ translations.accessibilityLabelSearchInput[language]
28
+ }
29
+ accessibilityHint={
30
+ accessibilityHintSearchInput ||
31
+ translations.accessibilityHintSearchInput[language]
32
+ }
33
+ style={[styles.searchInput, countrySelectStyle?.searchInput]}
34
+ placeholder={
35
+ searchPlaceholder ||
36
+ translations.searchPlaceholder[language as ICountrySelectLanguages]
37
+ }
38
+ placeholderTextColor={
39
+ searchPlaceholderTextColor ||
40
+ (theme === 'dark' ? '#FFFFFF80' : '#00000080')
41
+ }
42
+ selectionColor={searchSelectionColor}
43
+ value={value}
44
+ onChangeText={onChangeText}
45
+ />
46
+ );
47
+ };
@@ -1,2 +1 @@
1
1
  export {CountrySelect} from './CountrySelect';
2
- export {CountryItem} from './CountryItem';
@@ -29,24 +29,22 @@ export const createStyles = (theme, modalType, isFullScreen) =>
29
29
  height: '60%',
30
30
  backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
31
31
  borderRadius: 20,
32
- padding: 16,
32
+ paddingVertical: 16,
33
33
  }
34
34
  : {
35
35
  marginTop: StatusBar.currentHeight,
36
36
  width: '100%',
37
+ borderTopLeftRadius: 20,
38
+ borderTopRightRadius: 20,
37
39
  backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
38
- padding: 16,
39
- paddingTop: 0,
40
40
  },
41
41
  dragHandleContainer: {
42
42
  width: '100%',
43
43
  height: 24,
44
44
  justifyContent: 'center',
45
45
  alignItems: 'center',
46
- backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
47
46
  borderTopLeftRadius: 20,
48
47
  borderTopRightRadius: 20,
49
- marginBottom: -1,
50
48
  },
51
49
  dragHandleIndicator: {
52
50
  width: 40,
@@ -55,8 +53,8 @@ export const createStyles = (theme, modalType, isFullScreen) =>
55
53
  borderRadius: 2,
56
54
  },
57
55
  searchContainer: {
58
- marginBottom: 16,
59
- paddingTop: 8,
56
+ paddingVertical: 8,
57
+ paddingHorizontal: 16,
60
58
  flexDirection: 'row',
61
59
  },
62
60
  searchInput: {
@@ -71,6 +69,8 @@ export const createStyles = (theme, modalType, isFullScreen) =>
71
69
  },
72
70
  list: {
73
71
  flex: 1,
72
+ padding: 16,
73
+ paddingTop: 0,
74
74
  },
75
75
  countryItem: {
76
76
  flex: 1,
@@ -83,6 +83,10 @@ export const createStyles = (theme, modalType, isFullScreen) =>
83
83
  borderWidth: 1,
84
84
  borderRadius: 8,
85
85
  },
86
+ countryItemSelected: {
87
+ backgroundColor: 'rgba(33, 150, 243, 0.15)',
88
+ borderColor: '#2196F3',
89
+ },
86
90
  flag: {
87
91
  flex: 0.1,
88
92
  marginRight: 10,
@@ -107,12 +111,18 @@ export const createStyles = (theme, modalType, isFullScreen) =>
107
111
  fontSize: 14,
108
112
  color: theme === 'dark' ? '#FFFFFF80' : '#00000080',
109
113
  },
114
+ callingCodeSelected: {
115
+ color: '#1976D2',
116
+ },
110
117
  countryName: {
111
118
  flex: 0.8,
112
119
  fontSize: 16,
113
120
  fontWeight: '500',
114
121
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
115
122
  },
123
+ countryNameSelected: {
124
+ color: '#1976D2',
125
+ },
116
126
  sectionTitle: {
117
127
  fontSize: 16,
118
128
  fontWeight: '600',
@@ -143,4 +153,33 @@ export const createStyles = (theme, modalType, isFullScreen) =>
143
153
  fontSize: 16,
144
154
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
145
155
  },
156
+ alphabetContainer: {
157
+ marginRight: 16,
158
+ },
159
+ alphabetLetter: {
160
+ width: 24,
161
+ height: 24,
162
+ borderRadius: 12,
163
+ marginVertical: 2,
164
+ alignItems: 'center',
165
+ justifyContent: 'center',
166
+ backgroundColor: 'transparent',
167
+ },
168
+ alphabetLetterActive: {
169
+ backgroundColor: theme === 'dark' ? '#FFFFFF30' : '#00000020',
170
+ },
171
+ alphabetLetterDisabled: {
172
+ opacity: 0.4,
173
+ },
174
+ alphabetLetterText: {
175
+ fontSize: 12,
176
+ color: theme === 'dark' ? '#FFFFFFB3' : '#000000B3',
177
+ fontWeight: '600',
178
+ },
179
+ alphabetLetterTextActive: {
180
+ color: theme === 'dark' ? '#FFFFFF' : '#000000',
181
+ },
182
+ alphabetLetterTextDisabled: {
183
+ color: theme === 'dark' ? '#FFFFFF80' : '#00000080',
184
+ },
146
185
  });
@@ -0,0 +1,18 @@
1
+ import {IThemeProps} from './theme';
2
+ import {ICountrySelectStyle} from './countrySelectStyles';
3
+ import {ICountrySelectLanguages} from './countrySelectLanguages';
4
+ import {IListItem} from './itemList';
5
+
6
+ export interface AlphabeticFilterProps {
7
+ theme?: IThemeProps;
8
+ activeLetter: string | null;
9
+ onPressLetter: (index: number) => void;
10
+ language: ICountrySelectLanguages;
11
+ countries: IListItem[];
12
+ allCountriesStartIndex: number;
13
+ countrySelectStyle?: ICountrySelectStyle;
14
+ accessibilityLabelAlphabetFilter?: string;
15
+ accessibilityHintAlphabetFilter?: string;
16
+ accessibilityLabelAlphabetLetter?: string;
17
+ accessibilityHintAlphabetLetter?: string;
18
+ }
@@ -0,0 +1,12 @@
1
+ import {ICountrySelectLanguages} from './countrySelectLanguages';
2
+ import {ICountrySelectStyle} from './countrySelectStyles';
3
+ import {IThemeProps} from './theme';
4
+
5
+ export interface ICloseButtonProps {
6
+ theme?: IThemeProps;
7
+ language: ICountrySelectLanguages;
8
+ onClose: () => void;
9
+ countrySelectStyle?: ICountrySelectStyle;
10
+ accessibilityLabelCloseButton?: string;
11
+ accessibilityHintCloseButton?: string;
12
+ }
@@ -1,13 +1,14 @@
1
1
  import {ICountry} from './country';
2
+ import {IThemeProps} from './theme';
2
3
  import {ICountrySelectStyle} from './countrySelectStyles';
3
4
  import {ICountrySelectLanguages} from './countrySelectLanguages';
4
5
 
5
6
  export interface ICountryItemProps {
6
- item: ICountry;
7
+ country: ICountry;
8
+ theme?: IThemeProps;
9
+ isSelected?: boolean;
7
10
  onSelect: (country: ICountry) => void;
8
- onClose: () => void;
9
11
  language: ICountrySelectLanguages;
10
- theme?: 'light' | 'dark';
11
12
  countrySelectStyle?: ICountrySelectStyle;
12
13
  accessibilityLabel?: string;
13
14
  accessibilityHint?: string;
@@ -1,24 +1,25 @@
1
1
  import * as React from 'react';
2
2
  import {ModalProps} from 'react-native';
3
+
3
4
  import {ICountry} from './country';
5
+ import {IThemeProps} from './theme';
4
6
  import {ICountryCca2} from './countryCca2';
5
- import {ICountrySelectLanguages} from './countrySelectLanguages';
6
- import {ICountrySelectStyle} from './countrySelectStyles';
7
7
  import {ISectionTitle} from './sectionTitle';
8
+ import {ICountrySelectStyle} from './countrySelectStyles';
9
+ import {ICountrySelectLanguages} from './countrySelectLanguages';
8
10
 
9
- export interface ICountrySelectProps extends ModalProps {
11
+ interface ICountrySelectBaseProps extends ModalProps, IThemeProps {
10
12
  visible: boolean;
11
13
  onClose: () => void;
12
- onSelect: (country: ICountry) => void;
13
14
  modalType?: 'bottomSheet' | 'popup';
14
15
  countrySelectStyle?: ICountrySelectStyle;
15
- theme?: 'light' | 'dark';
16
16
  isFullScreen?: boolean;
17
17
  popularCountries?: string[];
18
18
  visibleCountries?: ICountryCca2[];
19
19
  hiddenCountries?: ICountryCca2[];
20
20
  language?: ICountrySelectLanguages;
21
21
  showSearchInput?: boolean;
22
+ showAlphabetFilter?: boolean;
22
23
  searchPlaceholder?: string;
23
24
  searchPlaceholderTextColor?: string;
24
25
  searchSelectionColor?: string;
@@ -47,4 +48,23 @@ export interface ICountrySelectProps extends ModalProps {
47
48
  accessibilityHintCountriesList?: string;
48
49
  accessibilityLabelCountryItem?: string;
49
50
  accessibilityHintCountryItem?: string;
51
+ accessibilityLabelAlphabetFilter?: string;
52
+ accessibilityHintAlphabetFilter?: string;
53
+ accessibilityLabelAlphabetLetter?: string;
54
+ accessibilityHintAlphabetLetter?: string;
50
55
  }
56
+
57
+ interface ICountrySelectSingleProps extends ICountrySelectBaseProps {
58
+ isMultiSelect?: false;
59
+ onSelect: (country: ICountry) => void;
60
+ }
61
+
62
+ interface ICountrySelectMultiProps extends ICountrySelectBaseProps {
63
+ isMultiSelect: true;
64
+ selectedCountries: ICountry[];
65
+ onSelect: (country: ICountry[]) => void;
66
+ }
67
+
68
+ export type ICountrySelectProps =
69
+ | ICountrySelectSingleProps
70
+ | ICountrySelectMultiProps;
@@ -19,4 +19,11 @@ export interface ICountrySelectStyle {
19
19
  countryName?: StyleProp<TextStyle>;
20
20
  countryNotFoundContainer?: StyleProp<ViewStyle>;
21
21
  countryNotFoundMessage?: StyleProp<TextStyle>;
22
+ alphabetContainer?: StyleProp<ViewStyle>;
23
+ alphabetLetter?: StyleProp<ViewStyle>;
24
+ alphabetLetterText?: StyleProp<TextStyle>;
25
+ alphabetLetterActive?: StyleProp<ViewStyle>;
26
+ alphabetLetterDisabled?: StyleProp<ViewStyle>;
27
+ alphabetLetterTextActive?: StyleProp<TextStyle>;
28
+ alphabetLetterTextDisabled?: StyleProp<TextStyle>;
22
29
  }
@@ -6,3 +6,6 @@ export * from './countryItemProps';
6
6
  export * from './sectionTitle';
7
7
  export * from './itemList';
8
8
  export * from './countrySelectStyles';
9
+ export * from './closeButtonProps';
10
+ export * from './searchInputProps';
11
+ export * from './theme';
@@ -0,0 +1,16 @@
1
+ import {ICountrySelectLanguages} from './countrySelectLanguages';
2
+ import {ICountrySelectStyle} from './countrySelectStyles';
3
+ import {IThemeProps} from './theme';
4
+
5
+ export interface ISearchInputProps {
6
+ theme?: IThemeProps;
7
+ language: ICountrySelectLanguages;
8
+ value: string;
9
+ onChangeText: (text: string) => void;
10
+ countrySelectStyle?: ICountrySelectStyle;
11
+ searchPlaceholder?: string;
12
+ searchPlaceholderTextColor?: string;
13
+ searchSelectionColor?: string;
14
+ accessibilityLabelSearchInput?: string;
15
+ accessibilityHintSearchInput?: string;
16
+ }
@@ -0,0 +1,3 @@
1
+ export interface IThemeProps {
2
+ theme?: 'light' | 'dark';
3
+ }
@@ -0,0 +1,3 @@
1
+ export const createAlphabet = (): string[] => {
2
+ return Array.from({length: 26}, (_v, i) => String.fromCharCode(65 + i));
3
+ };
@@ -0,0 +1,100 @@
1
+ import {translations} from './getTranslation';
2
+ import countries from '../constants/countries.json';
3
+ import {normalizeCountryName} from './normalizeCountryName';
4
+ import {ICountry, ICountrySelectLanguages, IListItem} from '../interface';
5
+ import {sortCountriesAlphabetically} from './sortCountriesAlphabetically';
6
+
7
+ type Params = {
8
+ searchQuery: string;
9
+ popularCountries: string[];
10
+ language: ICountrySelectLanguages;
11
+ visibleCountries: string[];
12
+ hiddenCountries: string[];
13
+ };
14
+
15
+ export function getCountriesList({
16
+ searchQuery,
17
+ popularCountries,
18
+ language,
19
+ visibleCountries,
20
+ hiddenCountries,
21
+ }: Params): IListItem[] {
22
+ const query = searchQuery.toLowerCase().trim();
23
+
24
+ let countriesData = countries as unknown as ICountry[];
25
+
26
+ if (visibleCountries.length > 0 && hiddenCountries.length > 0) {
27
+ countriesData = (countries as unknown as ICountry[]).filter(country => {
28
+ return (
29
+ visibleCountries.includes(country.cca2) &&
30
+ !hiddenCountries.includes(country.cca2)
31
+ );
32
+ });
33
+ }
34
+
35
+ if (visibleCountries.length > 0 && hiddenCountries.length === 0) {
36
+ countriesData = (countries as unknown as ICountry[]).filter(country =>
37
+ visibleCountries.includes(country.cca2),
38
+ );
39
+ }
40
+
41
+ if (hiddenCountries.length > 0 && visibleCountries.length === 0) {
42
+ countriesData = (countries as unknown as ICountry[]).filter(
43
+ country => !hiddenCountries.includes(country.cca2),
44
+ );
45
+ }
46
+
47
+ if (query.length > 0) {
48
+ const filteredCountries = countriesData.filter(country => {
49
+ const countryName =
50
+ country.translations[language]?.common || country.name.common || '';
51
+ const normalizedCountryName = normalizeCountryName(
52
+ countryName.toLowerCase(),
53
+ );
54
+ const normalizedQuery = normalizeCountryName(query);
55
+ const callingCode = country.idd.root.toLowerCase();
56
+ const flag = country.flag.toLowerCase();
57
+ const countryCode = country.cca2.toLowerCase();
58
+
59
+ return (
60
+ normalizedCountryName.includes(normalizedQuery) ||
61
+ countryName.toLowerCase().includes(query) ||
62
+ callingCode.includes(query) ||
63
+ flag.includes(query) ||
64
+ countryCode.includes(query)
65
+ );
66
+ });
67
+
68
+ return sortCountriesAlphabetically(filteredCountries, language);
69
+ }
70
+
71
+ const popularCountriesData = sortCountriesAlphabetically(
72
+ countriesData.filter(country => popularCountries.includes(country.cca2)),
73
+ language,
74
+ );
75
+
76
+ const otherCountriesData = sortCountriesAlphabetically(
77
+ countriesData.filter(country => !popularCountries.includes(country.cca2)),
78
+ language,
79
+ );
80
+
81
+ const result: IListItem[] = [];
82
+
83
+ if (popularCountriesData.length > 0) {
84
+ result.push({
85
+ isSection: true as const,
86
+ title:
87
+ translations.popularCountriesTitle[language as ICountrySelectLanguages],
88
+ });
89
+
90
+ result.push(...popularCountriesData);
91
+ result.push({
92
+ isSection: true as const,
93
+ title:
94
+ translations.allCountriesTitle[language as ICountrySelectLanguages],
95
+ });
96
+ }
97
+
98
+ result.push(...otherCountriesData);
99
+ return result;
100
+ }
@@ -0,0 +1,8 @@
1
+ import {ICountrySelectLanguages, ICountry} from '../interface';
2
+
3
+ export const getCountryNameInLanguage = (
4
+ country: ICountry,
5
+ language: ICountrySelectLanguages = 'eng',
6
+ ): string => {
7
+ return country.translations[language]?.common || country.name.common || '';
8
+ };