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.
@@ -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';
@@ -34,7 +34,8 @@ export const createStyles = (theme, modalType, isFullScreen) =>
34
34
  : {
35
35
  marginTop: StatusBar.currentHeight,
36
36
  width: '100%',
37
- borderRadius: 20,
37
+ borderTopLeftRadius: 20,
38
+ borderTopRightRadius: 20,
38
39
  backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
39
40
  },
40
41
  dragHandleContainer: {
@@ -82,6 +83,10 @@ export const createStyles = (theme, modalType, isFullScreen) =>
82
83
  borderWidth: 1,
83
84
  borderRadius: 8,
84
85
  },
86
+ countryItemSelected: {
87
+ backgroundColor: 'rgba(33, 150, 243, 0.15)',
88
+ borderColor: '#2196F3',
89
+ },
85
90
  flag: {
86
91
  flex: 0.1,
87
92
  marginRight: 10,
@@ -106,12 +111,18 @@ export const createStyles = (theme, modalType, isFullScreen) =>
106
111
  fontSize: 14,
107
112
  color: theme === 'dark' ? '#FFFFFF80' : '#00000080',
108
113
  },
114
+ callingCodeSelected: {
115
+ color: '#1976D2',
116
+ },
109
117
  countryName: {
110
118
  flex: 0.8,
111
119
  fontSize: 16,
112
120
  fontWeight: '500',
113
121
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
114
122
  },
123
+ countryNameSelected: {
124
+ color: '#1976D2',
125
+ },
115
126
  sectionTitle: {
116
127
  fontSize: 16,
117
128
  fontWeight: '600',
@@ -142,4 +153,33 @@ export const createStyles = (theme, modalType, isFullScreen) =>
142
153
  fontSize: 16,
143
154
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
144
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
+ },
145
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
+ };