react-native-country-select 0.2.0 → 0.2.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.
package/README.md CHANGED
@@ -248,34 +248,37 @@ export default function App() {
248
248
 
249
249
  ## CountrySelect Props
250
250
 
251
- | Prop | Type | Required | Default | Description |
252
- | ---------------------------- | ----------------------------------------------------------------------- | -------- | ------------------- | ---------------------------------------------------------- |
253
- | visible | boolean | Yes | false | Controls the visibility of the country picker modal |
254
- | onClose | () => void | Yes | - | Callback function called when the modal is closed |
255
- | onSelect | (country: [ICountry](lib/interfaces/country.ts)) => void | Yes | - | Callback function called when a country is selected |
256
- | modalType | 'bottomSheet' \| 'popup' | No | 'bottomSheet' | Type of modal to display |
257
- | countrySelectStyle | [ICountrySelectStyle](lib/interfaces/countrySelectStyles.ts) | No | - | Custom styles for the country picker |
258
- | isFullScreen | boolean | No | false | Whether the modal should be full screen |
259
- | popularCountries | string[] | No | [] | Array of country codes to show in popular section |
260
- | visibleCountries | [ICountryCca2[]](lib/interfaces/countryCca2.ts) | No | [] | Array of country codes to show (whitelist) |
261
- | hiddenCountries | [ICountryCca2[]](lib/interfaces/countryCca2.ts) | No | [] | Array of country codes to hide (blacklist) |
262
- | theme | 'light' \| 'dark' | No | 'light' | Theme for the country picker |
263
- | language | [ICountrySelectLanguages](lib/interfaces/countrySelectLanguages.ts) | No | 'eng' | Language for country names (see supported languages below) |
264
- | showSearchInput | boolean | No | true | Whether to show the search input field |
265
- | searchPlaceholder | string | No | 'Search country...' | Placeholder text for search input |
266
- | minBottomsheetHeight | number \| string | No | 30% | Minimum height for bottom sheet modal |
267
- | maxBottomsheetHeight | number \| string | No | 80% | Maximum height for bottom sheet modal |
268
- | initialBottomsheetHeight | number \| string | No | 50% | Initial height for bottom sheet modal |
269
- | disabledBackdropPress | boolean | No | false | Whether to disable backdrop press to close |
270
- | removedBackdrop | boolean | No | false | Whether to remove the backdrop completely |
271
- | onBackdropPress | () => void | No | - | Custom callback for backdrop press |
272
- | countryItemComponent | (item: [ICountry](lib/interfaces/country.ts)) => ReactElement | No | - | Custom component for country items |
273
- | sectionTitleComponent | (item: [ISectionTitle](lib/interfaces/sectionTitle.ts)) => ReactElement | No | - | Custom component for section titles |
274
- | closeButtonComponent | () => ReactElement | No | - | Custom component for closeButton |
275
- | showCloseButton | boolean | No | false | Whether to show the close button |
276
- | popularCountriesTitle | string | No | 'Popular Countries' | Popular Countries section title |
277
- | allCountriesTitle | string | No | 'All Countries' | All Countries section title |
278
- | showsVerticalScrollIndicator | boolean | No | false | Displays a horizontal scroll indicator |
251
+ | Prop | Type | Required | Default | Description |
252
+ | ---------------------------- | ----------------------------------------------------------------------- | -------- | -------------------- | ---------------------------------------------------------------- |
253
+ | visible | boolean | Yes | false | Controls the visibility of the country picker modal |
254
+ | onClose | () => void | Yes | - | Callback function called when the modal is closed |
255
+ | onSelect | (country: [ICountry](lib/interfaces/country.ts)) => void | Yes | - | Callback function called when a country is selected |
256
+ | modalType | 'bottomSheet' \| 'popup' | No | 'bottomSheet' | Type of modal to display |
257
+ | countrySelectStyle | [ICountrySelectStyle](lib/interfaces/countrySelectStyles.ts) | No | - | Custom styles for the country picker |
258
+ | isFullScreen | boolean | No | false | Whether the modal should be full screen |
259
+ | popularCountries | string[] | No | [] | Array of country codes to show in popular section |
260
+ | visibleCountries | [ICountryCca2[]](lib/interfaces/countryCca2.ts) | No | [] | Array of country codes to show (whitelist) |
261
+ | hiddenCountries | [ICountryCca2[]](lib/interfaces/countryCca2.ts) | No | [] | Array of country codes to hide (blacklist) |
262
+ | theme | 'light' \| 'dark' | No | 'light' | Theme for the country picker |
263
+ | language | [ICountrySelectLanguages](lib/interfaces/countrySelectLanguages.ts) | No | 'eng' | Language for country names (see supported languages below) |
264
+ | showSearchInput | boolean | No | true | Whether to show the search input field |
265
+ | searchPlaceholder | string | No | 'Search country...' | Placeholder text for search input |
266
+ | searchPlaceholderTextColor | string | No | '#00000080' | Placeholder text color for search input |
267
+ | searchSelectionColor | string | No | default | Highlight, selection handle and cursor color of the search input |
268
+ | minBottomsheetHeight | number \| string | No | 30% | Minimum height for bottom sheet modal |
269
+ | maxBottomsheetHeight | number \| string | No | 80% | Maximum height for bottom sheet modal |
270
+ | initialBottomsheetHeight | number \| string | No | 50% | Initial height for bottom sheet modal |
271
+ | disabledBackdropPress | boolean | No | false | Whether to disable backdrop press to close |
272
+ | removedBackdrop | boolean | No | false | Whether to remove the backdrop completely |
273
+ | onBackdropPress | () => void | No | - | Custom callback for backdrop press |
274
+ | countryItemComponent | (item: [ICountry](lib/interfaces/country.ts)) => ReactElement | No | - | Custom component for country items |
275
+ | sectionTitleComponent | (item: [ISectionTitle](lib/interfaces/sectionTitle.ts)) => ReactElement | No | - | Custom component for section titles |
276
+ | closeButtonComponent | () => ReactElement | No | - | Custom component for closeButton |
277
+ | showCloseButton | boolean | No | false | Whether to show the close button |
278
+ | popularCountriesTitle | string | No | 'Popular Countries' | Popular Countries section title |
279
+ | allCountriesTitle | string | No | 'All Countries' | All Countries section title |
280
+ | showsVerticalScrollIndicator | boolean | No | false | Displays a horizontal scroll indicator |
281
+ | countryNotFoundMessage | string | No | "No countries found" | Country not found in search |
279
282
 
280
283
  <br>
281
284
 
@@ -13,7 +13,6 @@ export const CountryItem = memo<ICountryItemProps>(
13
13
  onClose,
14
14
  theme = 'light',
15
15
  language,
16
- modalType,
17
16
  countrySelectStyle,
18
17
  }) => {
19
18
  const styles = createStyles(theme);
@@ -24,51 +23,25 @@ export const CountryItem = memo<ICountryItemProps>(
24
23
  accessibilityRole="button"
25
24
  accessibilityLabel="Country Select Item"
26
25
  accessibilityHint="Click to select a country"
27
- style={[
28
- styles.countryItem,
29
- modalType === 'popup'
30
- ? countrySelectStyle?.popup?.countryItem
31
- : countrySelectStyle?.bottomSheet?.countryItem,
32
- ]}
26
+ style={[styles.countryItem, countrySelectStyle?.countryItem]}
33
27
  onPress={() => {
34
28
  onSelect(item);
35
29
  onClose();
36
30
  }}>
37
31
  <Text
38
32
  testID="countrySelectItemFlag"
39
- style={[
40
- styles.flag,
41
- modalType === 'popup'
42
- ? countrySelectStyle?.popup?.flag
43
- : countrySelectStyle?.bottomSheet?.flag,
44
- ]}>
45
- {item.flag}
33
+ style={[styles.flag, countrySelectStyle?.flag]}>
34
+ {item.flag || item.cca2}
46
35
  </Text>
47
- <View
48
- style={[
49
- styles.countryInfo,
50
- modalType === 'popup'
51
- ? countrySelectStyle?.popup?.countryInfo
52
- : countrySelectStyle?.bottomSheet?.countryInfo,
53
- ]}>
36
+ <View style={[styles.countryInfo, countrySelectStyle?.countryInfo]}>
54
37
  <Text
55
38
  testID="countrySelectItemCallingCode"
56
- style={[
57
- styles.callingCode,
58
- modalType === 'popup'
59
- ? countrySelectStyle?.popup?.callingCode
60
- : countrySelectStyle?.bottomSheet?.callingCode,
61
- ]}>
39
+ style={[styles.callingCode, countrySelectStyle?.callingCode]}>
62
40
  {item.idd.root}
63
41
  </Text>
64
42
  <Text
65
43
  testID="countrySelectItemName"
66
- style={[
67
- styles.countryName,
68
- modalType === 'popup'
69
- ? countrySelectStyle?.popup?.countryName
70
- : countrySelectStyle?.bottomSheet?.countryName,
71
- ]}>
44
+ style={[styles.countryName, countrySelectStyle?.countryName]}>
72
45
  {item?.translations[language]?.common ||
73
46
  item?.translations[DEFAULT_LANGUAGE]?.common}
74
47
  </Text>
@@ -52,6 +52,8 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
52
52
  language = DEFAULT_LANGUAGE,
53
53
  showSearchInput = true,
54
54
  searchPlaceholder,
55
+ searchPlaceholderTextColor,
56
+ searchSelectionColor,
55
57
  showCloseButton = false,
56
58
  minBottomsheetHeight,
57
59
  maxBottomsheetHeight,
@@ -65,10 +67,11 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
65
67
  popularCountriesTitle,
66
68
  allCountriesTitle,
67
69
  showsVerticalScrollIndicator = false,
70
+ countryNotFoundMessage,
68
71
  ...props
69
72
  }) => {
70
73
  const {height: windowHeight} = useWindowDimensions();
71
- const styles = createStyles(theme);
74
+ const styles = createStyles(theme, modalType, isFullScreen);
72
75
 
73
76
  const [searchQuery, setSearchQuery] = useState('');
74
77
  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
@@ -262,21 +265,29 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
262
265
  const getCountries = useMemo(() => {
263
266
  const query = searchQuery.toLowerCase();
264
267
 
265
- if (query.length > 0) {
266
- let countriesData = countries as unknown as ICountry[];
268
+ let countriesData = countries as unknown as ICountry[];
267
269
 
268
- if (visibleCountries.length > 0) {
269
- countriesData = (countries as unknown as ICountry[]).filter(country =>
270
- visibleCountries.includes(country.cca2),
271
- );
272
- }
270
+ if (visibleCountries.length > 0 && hiddenCountries.length > 0) {
271
+ countriesData = (countries as unknown as ICountry[]).filter(
272
+ country =>
273
+ visibleCountries.includes(country.cca2) &&
274
+ !hiddenCountries.includes(country.cca2),
275
+ );
276
+ }
273
277
 
274
- if (hiddenCountries.length > 0) {
275
- countriesData = (countries as unknown as ICountry[]).filter(
276
- country => !hiddenCountries.includes(country.cca2),
277
- );
278
- }
278
+ if (visibleCountries.length > 0 && hiddenCountries.length === 0) {
279
+ countriesData = (countries as unknown as ICountry[]).filter(country =>
280
+ visibleCountries.includes(country.cca2),
281
+ );
282
+ }
279
283
 
284
+ if (hiddenCountries.length > 0 && visibleCountries.length === 0) {
285
+ countriesData = (countries as unknown as ICountry[]).filter(
286
+ country => !hiddenCountries.includes(country.cca2),
287
+ );
288
+ }
289
+
290
+ if (query.length > 0) {
280
291
  const filteredCountries = countriesData.filter(country => {
281
292
  const countryName = getCountryNameInLanguage(country);
282
293
  const normalizedCountryName = normalizeCountryName(
@@ -296,30 +307,15 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
296
307
  );
297
308
  });
298
309
 
299
- // Ordenar os países filtrados alfabeticamente
300
310
  return sortCountriesAlphabetically(filteredCountries);
301
311
  }
302
312
 
303
- let allCountries = countries as unknown as ICountry[];
304
-
305
- if (visibleCountries.length > 0) {
306
- allCountries = (countries as unknown as ICountry[]).filter(country =>
307
- visibleCountries.includes(country.cca2),
308
- );
309
- }
310
-
311
- if (hiddenCountries.length > 0) {
312
- allCountries = (countries as unknown as ICountry[]).filter(
313
- country => !hiddenCountries.includes(country.cca2),
314
- );
315
- }
316
-
317
313
  const popularCountriesData = sortCountriesAlphabetically(
318
- allCountries.filter(country => popularCountries.includes(country.cca2)),
314
+ countriesData.filter(country => popularCountries.includes(country.cca2)),
319
315
  );
320
316
 
321
317
  const otherCountriesData = sortCountriesAlphabetically(
322
- allCountries.filter(country => !popularCountries.includes(country.cca2)),
318
+ countriesData.filter(country => !popularCountries.includes(country.cca2)),
323
319
  );
324
320
 
325
321
  const result: IListItem[] = [];
@@ -388,14 +384,11 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
388
384
  accessibilityRole="button"
389
385
  accessibilityLabel="Country Select Modal Close Button"
390
386
  accessibilityHint="Click to close the Country Select modal"
391
- style={[styles.closeButton, countrySelectStyle?.popup?.closeButton]}
387
+ style={[styles.closeButton, countrySelectStyle?.closeButton]}
392
388
  activeOpacity={0.6}
393
389
  onPress={onClose}>
394
390
  <Text
395
- style={[
396
- styles.closeButtonText,
397
- countrySelectStyle?.popup?.closeButtonText,
398
- ]}>
391
+ style={[styles.closeButtonText, countrySelectStyle?.closeButtonText]}>
399
392
  {'\u00D7'}
400
393
  </Text>
401
394
  </TouchableOpacity>
@@ -409,22 +402,16 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
409
402
  accessibilityRole="text"
410
403
  accessibilityLabel="Country Select Search Input"
411
404
  accessibilityHint="Type to search for a country"
412
- style={[
413
- styles.searchInput,
414
- modalType === 'popup'
415
- ? countrySelectStyle?.popup?.searchInput
416
- : countrySelectStyle?.bottomSheet?.searchInput,
417
- ]}
405
+ style={[styles.searchInput, countrySelectStyle?.searchInput]}
418
406
  placeholder={
419
407
  searchPlaceholder ||
420
408
  translations.searchPlaceholder[language as ICountrySelectLanguages]
421
409
  }
422
410
  placeholderTextColor={
423
- (modalType === 'popup'
424
- ? countrySelectStyle?.popup?.searchInputPlaceholder?.color
425
- : countrySelectStyle?.bottomSheet?.searchInputPlaceholder?.color) ||
426
- styles.searchInputPlaceholder.color
411
+ searchPlaceholderTextColor ||
412
+ (theme === 'dark' ? '#FFFFFF80' : '#00000080')
427
413
  }
414
+ selectionColor={searchSelectionColor}
428
415
  value={searchQuery}
429
416
  onChangeText={setSearchQuery}
430
417
  />
@@ -432,6 +419,26 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
432
419
  };
433
420
 
434
421
  const renderFlatList = () => {
422
+ if (getCountries.length === 0) {
423
+ return (
424
+ <View
425
+ style={[
426
+ styles.countryNotFoundContainer,
427
+ countrySelectStyle?.countryNotFoundContainer,
428
+ ]}>
429
+ <Text
430
+ style={[
431
+ styles.countryNotFoundMessage,
432
+ countrySelectStyle?.countryNotFoundMessage,
433
+ ]}>
434
+ {countryNotFoundMessage ||
435
+ translations.searchNotFoundMessage[
436
+ language as ICountrySelectLanguages
437
+ ]}
438
+ </Text>
439
+ </View>
440
+ );
441
+ }
435
442
  return (
436
443
  <FlatList
437
444
  testID="countrySelectList"
@@ -458,12 +465,7 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
458
465
  <Text
459
466
  testID="countrySelectSectionTitle"
460
467
  accessibilityRole="header"
461
- style={[
462
- styles.sectionTitle,
463
- modalType === 'popup'
464
- ? countrySelectStyle?.popup?.sectionTitle
465
- : countrySelectStyle?.bottomSheet?.sectionTitle,
466
- ]}>
468
+ style={[styles.sectionTitle, countrySelectStyle?.sectionTitle]}>
467
469
  {popularCountriesTitle && index === 0
468
470
  ? popularCountriesTitle
469
471
  : allCountriesTitle && index > 0
@@ -484,7 +486,6 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
484
486
  onClose={onClose}
485
487
  theme={theme}
486
488
  language={language}
487
- modalType={modalType}
488
489
  countrySelectStyle={countrySelectStyle}
489
490
  />
490
491
  );
@@ -517,14 +518,14 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
517
518
  style={[
518
519
  styles.backdrop,
519
520
  {alignItems: 'center', justifyContent: 'center'},
520
- countrySelectStyle?.popup?.backdrop,
521
+ countrySelectStyle?.backdrop,
521
522
  removedBackdrop && {backgroundColor: 'transparent'},
522
523
  ]}
523
524
  onPress={onBackdropPress || onClose}>
524
525
  <Pressable
525
526
  style={[
526
- styles.popupContainer,
527
- countrySelectStyle?.popup?.popupContainer,
527
+ styles.container,
528
+ countrySelectStyle?.container,
528
529
  isFullScreen && {
529
530
  flex: 1,
530
531
  width: '100%',
@@ -533,8 +534,8 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
533
534
  ]}>
534
535
  <View
535
536
  style={[
536
- styles.popupContent,
537
- countrySelectStyle?.popup?.popupContent,
537
+ styles.content,
538
+ countrySelectStyle?.content,
538
539
  isFullScreen && {
539
540
  borderRadius: 0,
540
541
  },
@@ -543,7 +544,7 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
543
544
  <View
544
545
  style={[
545
546
  styles.searchContainer,
546
- countrySelectStyle?.popup?.searchContainer,
547
+ countrySelectStyle?.searchContainer,
547
548
  ]}>
548
549
  {(isFullScreen || showCloseButton) && renderCloseButton()}
549
550
  {showSearchInput && renderSearchInput()}
@@ -569,6 +570,7 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
569
570
  <View
570
571
  style={[
571
572
  styles.backdrop,
573
+ countrySelectStyle?.backdrop,
572
574
  removedBackdrop && {backgroundColor: 'transparent'},
573
575
  ]}>
574
576
  <Pressable
@@ -580,13 +582,16 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
580
582
  style={{flex: 1}}
581
583
  onPress={onBackdropPress || onClose}
582
584
  />
583
- <View style={styles.sheetContainer} pointerEvents="auto">
585
+ <View
586
+ style={[styles.container, countrySelectStyle?.container]}
587
+ pointerEvents="auto">
584
588
  <View {...handlePanResponder.panHandlers} style={styles.dragHandle}>
585
589
  <View style={styles.dragIndicator} />
586
590
  </View>
587
591
  <Animated.View
588
592
  style={[
589
- styles.sheetContent,
593
+ styles.content,
594
+ countrySelectStyle?.content,
590
595
  {
591
596
  height: sheetHeight,
592
597
  minHeight: bottomSheetSize.minHeight,
@@ -594,7 +599,11 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
594
599
  },
595
600
  ]}>
596
601
  {(showSearchInput || showCloseButton) && (
597
- <View style={styles.searchContainer}>
602
+ <View
603
+ style={[
604
+ styles.searchContainer,
605
+ countrySelectStyle?.searchContainer,
606
+ ]}>
598
607
  {showCloseButton && renderCloseButton()}
599
608
  {showSearchInput && renderSearchInput()}
600
609
  </View>
@@ -1,43 +1,47 @@
1
1
  import {Platform, StatusBar, StyleSheet} from 'react-native';
2
2
 
3
- export const createStyles = theme =>
3
+ export const createStyles = (theme, modalType, isFullScreen) =>
4
4
  StyleSheet.create({
5
5
  backdrop: {
6
6
  flex: 1,
7
7
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
8
8
  },
9
- popupContainer: {
10
- marginTop: StatusBar.currentHeight,
11
- width: '90%',
12
- maxWidth: 600,
13
- height: '60%',
14
- alignItems: 'center',
15
- justifyContent: 'center',
16
- alignSelf: 'center',
17
- },
18
- popupContent: {
19
- flex: 1,
20
- width: '100%',
21
- backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
22
- borderRadius: 20,
23
- alignSelf: 'center',
24
- padding: 16,
25
- },
26
- sheetContainer: {
27
- marginTop: StatusBar.currentHeight,
28
- position: 'absolute',
29
- bottom: 0,
30
- left: 0,
31
- right: 0,
32
- width: '100%',
33
- alignItems: 'center',
34
- },
35
- sheetContent: {
36
- width: '100%',
37
- backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
38
- padding: 16,
39
- paddingTop: 0,
40
- },
9
+ container:
10
+ modalType === 'popup' || isFullScreen
11
+ ? {
12
+ marginTop: StatusBar.currentHeight,
13
+ width: '90%',
14
+ maxWidth: 600,
15
+ height: '60%',
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ alignSelf: 'center',
19
+ }
20
+ : {
21
+ marginTop: StatusBar.currentHeight,
22
+ position: 'absolute',
23
+ bottom: 0,
24
+ left: 0,
25
+ right: 0,
26
+ width: '100%',
27
+ alignItems: 'center',
28
+ },
29
+ content:
30
+ modalType === 'popup' || isFullScreen
31
+ ? {
32
+ flex: 1,
33
+ width: '100%',
34
+ backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
35
+ borderRadius: 20,
36
+ alignSelf: 'center',
37
+ padding: 16,
38
+ }
39
+ : {
40
+ width: '100%',
41
+ backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
42
+ padding: 16,
43
+ paddingTop: 0,
44
+ },
41
45
  dragHandle: {
42
46
  width: '100%',
43
47
  height: 24,
@@ -69,9 +73,6 @@ export const createStyles = theme =>
69
73
  borderWidth: 1,
70
74
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
71
75
  },
72
- searchInputPlaceholder: {
73
- color: theme === 'dark' ? '#FFFFFF80' : '#00000080',
74
- },
75
76
  list: {
76
77
  flex: 1,
77
78
  },
@@ -88,8 +89,10 @@ export const createStyles = theme =>
88
89
  },
89
90
  flag: {
90
91
  flex: 0.1,
92
+ marginRight: 10,
91
93
  fontSize: 16,
92
94
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
95
+ textAlign: 'center',
93
96
  fontFamily:
94
97
  Platform.OS === 'web'
95
98
  ? typeof navigator !== 'undefined' &&
@@ -135,4 +138,13 @@ export const createStyles = theme =>
135
138
  lineHeight: 28,
136
139
  color: theme === 'dark' ? '#FFFFFF' : '#000000',
137
140
  },
141
+ countryNotFoundContainer: {
142
+ flex: 1,
143
+ justifyContent: 'center',
144
+ alignItems: 'center',
145
+ },
146
+ countryNotFoundMessage: {
147
+ fontSize: 16,
148
+ color: theme === 'dark' ? '#FFFFFF' : '#000000',
149
+ },
138
150
  });