react-native-country-select 0.1.2 โ 0.2.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.
- package/README.md +91 -23
- package/lib/assets/fonts/TwemojiMozilla.woff2 +0 -0
- package/lib/assets/images/preview.png +0 -0
- package/lib/components/CountryItem/index.tsx +44 -6
- package/lib/components/CountrySelect/index.tsx +389 -45
- package/lib/components/{styles.ts โ styles.js} +58 -3
- package/lib/index.d.ts +36 -0
- package/lib/index.tsx +25 -0
- package/lib/interface/countryItemProps.ts +3 -0
- package/lib/interface/countrySelectProps.ts +13 -2
- package/lib/interface/countrySelectStyles.ts +34 -0
- package/lib/interface/index.ts +1 -1
- package/lib/utils/countryHelpers.ts +59 -0
- package/lib/utils/parseHeight.ts +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
<br>
|
2
|
+
|
3
|
+
<div align = "center">
|
4
|
+
<img src="lib/assets/images/preview.png" alt="React Native International Phone Number Input Lib preview">
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<br>
|
8
|
+
|
1
9
|
<h1 align="center">React Native Country Select</h1>
|
2
10
|
|
3
11
|
<p>
|
@@ -39,11 +47,13 @@
|
|
39
47
|
|
40
48
|
## Features
|
41
49
|
|
42
|
-
- ๐ฑ Cross-
|
43
|
-
-
|
44
|
-
-
|
45
|
-
-
|
46
|
-
-
|
50
|
+
- ๐ฑ **Cross-Platform** โ Works seamlessly on **iOS, Android and Web**;
|
51
|
+
- ๐งฉ **Flexible Integration** โ Supports both **React Native CLI & Expo**;
|
52
|
+
- ๐จ **Modern UI** - Custom component with sleek design;
|
53
|
+
- ๐จโ๐ป **Component Versatility** - Works with **functional & class components**;
|
54
|
+
- ๐ถ **internationalization** - Supports **32 languages**;
|
55
|
+
- ๐งช **Test Ready** โ Smooth testing integration;
|
56
|
+
- โฟ **Accessibility** โ Accessibility standards to screen readers.
|
47
57
|
|
48
58
|
<br>
|
49
59
|
|
@@ -63,6 +73,51 @@ yarn add react-native-country-select
|
|
63
73
|
|
64
74
|
<br>
|
65
75
|
|
76
|
+
## Additional config to WEB
|
77
|
+
|
78
|
+
- ### Using React Native CLI:
|
79
|
+
|
80
|
+
Create a `react-native.config.js` file at the root of your react-native project with:
|
81
|
+
|
82
|
+
```bash
|
83
|
+
module.exports = {
|
84
|
+
project: {
|
85
|
+
ios: {},
|
86
|
+
android: {},
|
87
|
+
},
|
88
|
+
assets: [
|
89
|
+
'./node_modules/react-native-country-select/lib/assets/fonts',
|
90
|
+
],
|
91
|
+
};
|
92
|
+
```
|
93
|
+
|
94
|
+
Then link the font to your native projects with:
|
95
|
+
|
96
|
+
```bash
|
97
|
+
npx react-native-asset
|
98
|
+
```
|
99
|
+
|
100
|
+
- ### Using Expo:
|
101
|
+
|
102
|
+
1. Install [expo-fonts](https://docs.expo.dev/versions/latest/sdk/font/): `npx expo install expo-font`;
|
103
|
+
2. Initialize the `expo-font`:
|
104
|
+
|
105
|
+
```bash
|
106
|
+
import { useFonts } from 'expo-font';
|
107
|
+
|
108
|
+
...
|
109
|
+
|
110
|
+
useFonts({
|
111
|
+
'TwemojiMozilla': require('./node_modules/react-native-country-select/lib/assets/fonts/TwemojiMozilla.woff2'),
|
112
|
+
});
|
113
|
+
|
114
|
+
...
|
115
|
+
```
|
116
|
+
|
117
|
+
> Observation: you need to recompile your project after adding new fonts.
|
118
|
+
|
119
|
+
<br>
|
120
|
+
|
66
121
|
## Basic Usage
|
67
122
|
|
68
123
|
- Class Component
|
@@ -193,24 +248,34 @@ export default function App() {
|
|
193
248
|
|
194
249
|
## CountrySelect Props
|
195
250
|
|
196
|
-
| Prop | Type
|
197
|
-
| ---------------------------- |
|
198
|
-
| visible | boolean
|
199
|
-
| onClose | () => void
|
200
|
-
| onSelect | (country: ICountry) => void
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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 |
|
214
279
|
|
215
280
|
<br>
|
216
281
|
|
@@ -262,8 +327,11 @@ When utilizing this package, you may need to target the CountrySelect component
|
|
262
327
|
|
263
328
|
```js
|
264
329
|
const countrySelect = getByTestId('countrySelectSearchInput');
|
330
|
+
const countrySelectBackdrop = getByTestId('countrySelectBackdrop');
|
265
331
|
const countrySelectList = getByTestId('countrySelectList');
|
332
|
+
const countrySelectSearchInput = getByTestId('countrySelectSearchInput');
|
266
333
|
const countrySelectItem = getByTestId('countrySelectItem');
|
334
|
+
const countrySelectCloseButton = getByTestId('countrySelectCloseButton');
|
267
335
|
```
|
268
336
|
|
269
337
|
<br>
|
Binary file
|
Binary file
|
@@ -7,7 +7,15 @@ import {ICountryItemProps, ICountrySelectLanguages} from '../../interface';
|
|
7
7
|
const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
|
8
8
|
|
9
9
|
export const CountryItem = memo<ICountryItemProps>(
|
10
|
-
({
|
10
|
+
({
|
11
|
+
item,
|
12
|
+
onSelect,
|
13
|
+
onClose,
|
14
|
+
theme = 'light',
|
15
|
+
language,
|
16
|
+
modalType,
|
17
|
+
countrySelectStyle,
|
18
|
+
}) => {
|
11
19
|
const styles = createStyles(theme);
|
12
20
|
|
13
21
|
return (
|
@@ -16,21 +24,51 @@ export const CountryItem = memo<ICountryItemProps>(
|
|
16
24
|
accessibilityRole="button"
|
17
25
|
accessibilityLabel="Country Select Item"
|
18
26
|
accessibilityHint="Click to select a country"
|
19
|
-
style={
|
27
|
+
style={[
|
28
|
+
styles.countryItem,
|
29
|
+
modalType === 'popup'
|
30
|
+
? countrySelectStyle?.popup?.countryItem
|
31
|
+
: countrySelectStyle?.bottomSheet?.countryItem,
|
32
|
+
]}
|
20
33
|
onPress={() => {
|
21
34
|
onSelect(item);
|
22
35
|
onClose();
|
23
36
|
}}>
|
24
|
-
<Text
|
37
|
+
<Text
|
38
|
+
testID="countrySelectItemFlag"
|
39
|
+
style={[
|
40
|
+
styles.flag,
|
41
|
+
modalType === 'popup'
|
42
|
+
? countrySelectStyle?.popup?.flag
|
43
|
+
: countrySelectStyle?.bottomSheet?.flag,
|
44
|
+
]}>
|
25
45
|
{item.flag}
|
26
46
|
</Text>
|
27
|
-
<View
|
47
|
+
<View
|
48
|
+
style={[
|
49
|
+
styles.countryInfo,
|
50
|
+
modalType === 'popup'
|
51
|
+
? countrySelectStyle?.popup?.countryInfo
|
52
|
+
: countrySelectStyle?.bottomSheet?.countryInfo,
|
53
|
+
]}>
|
28
54
|
<Text
|
29
55
|
testID="countrySelectItemCallingCode"
|
30
|
-
style={
|
56
|
+
style={[
|
57
|
+
styles.callingCode,
|
58
|
+
modalType === 'popup'
|
59
|
+
? countrySelectStyle?.popup?.callingCode
|
60
|
+
: countrySelectStyle?.bottomSheet?.callingCode,
|
61
|
+
]}>
|
31
62
|
{item.idd.root}
|
32
63
|
</Text>
|
33
|
-
<Text
|
64
|
+
<Text
|
65
|
+
testID="countrySelectItemName"
|
66
|
+
style={[
|
67
|
+
styles.countryName,
|
68
|
+
modalType === 'popup'
|
69
|
+
? countrySelectStyle?.popup?.countryName
|
70
|
+
: countrySelectStyle?.bottomSheet?.countryName,
|
71
|
+
]}>
|
34
72
|
{item?.translations[language]?.common ||
|
35
73
|
item?.translations[DEFAULT_LANGUAGE]?.common}
|
36
74
|
</Text>
|
@@ -1,19 +1,25 @@
|
|
1
1
|
/* eslint-disable react-native/no-inline-styles */
|
2
2
|
/* eslint-disable react-hooks/exhaustive-deps */
|
3
|
-
import React, {useCallback, useMemo, useState} from 'react';
|
3
|
+
import React, {useCallback, useMemo, useState, useRef, useEffect} from 'react';
|
4
4
|
import {
|
5
5
|
View,
|
6
6
|
TextInput,
|
7
7
|
FlatList,
|
8
|
+
useWindowDimensions,
|
8
9
|
Pressable,
|
10
|
+
Animated,
|
11
|
+
PanResponder,
|
9
12
|
ListRenderItem,
|
10
13
|
Modal,
|
14
|
+
Keyboard,
|
11
15
|
Text,
|
16
|
+
TouchableOpacity,
|
12
17
|
} from 'react-native';
|
13
18
|
|
14
19
|
import {CountryItem} from '../CountryItem';
|
15
20
|
|
16
21
|
import {createStyles} from '../styles';
|
22
|
+
import parseHeight from '../../utils/parseHeight';
|
17
23
|
import countries from '../../constants/countries.json';
|
18
24
|
import {translations} from '../../utils/getTranslation';
|
19
25
|
import {
|
@@ -23,30 +29,207 @@ import {
|
|
23
29
|
IListItem,
|
24
30
|
} from '../../interface';
|
25
31
|
|
32
|
+
const ITEM_HEIGHT = 56;
|
33
|
+
const SECTION_HEADER_HEIGHT = 40;
|
34
|
+
|
35
|
+
const MIN_HEIGHT_PERCENTAGE = 0.3;
|
36
|
+
const MAX_HEIGHT_PERCENTAGE = 0.9;
|
37
|
+
const INITIAL_HEIGHT_PERCENTAGE = 0.5;
|
38
|
+
|
26
39
|
const DEFAULT_LANGUAGE: ICountrySelectLanguages = 'eng';
|
27
40
|
|
28
41
|
export const CountrySelect: React.FC<ICountrySelectProps> = ({
|
29
42
|
visible,
|
30
43
|
onClose,
|
31
44
|
onSelect,
|
45
|
+
modalType = 'bottomSheet',
|
32
46
|
theme = 'light',
|
47
|
+
isFullScreen = false,
|
48
|
+
countrySelectStyle,
|
33
49
|
popularCountries = [],
|
34
50
|
visibleCountries = [],
|
35
51
|
hiddenCountries = [],
|
36
52
|
language = DEFAULT_LANGUAGE,
|
53
|
+
showSearchInput = true,
|
54
|
+
searchPlaceholder,
|
55
|
+
showCloseButton = false,
|
56
|
+
minBottomsheetHeight,
|
57
|
+
maxBottomsheetHeight,
|
58
|
+
initialBottomsheetHeight,
|
37
59
|
disabledBackdropPress,
|
38
60
|
removedBackdrop,
|
39
61
|
onBackdropPress,
|
40
62
|
sectionTitleComponent,
|
41
63
|
countryItemComponent,
|
64
|
+
closeButtonComponent,
|
42
65
|
popularCountriesTitle,
|
43
66
|
allCountriesTitle,
|
44
67
|
showsVerticalScrollIndicator = false,
|
45
68
|
...props
|
46
69
|
}) => {
|
70
|
+
const {height: windowHeight} = useWindowDimensions();
|
47
71
|
const styles = createStyles(theme);
|
48
72
|
|
49
73
|
const [searchQuery, setSearchQuery] = useState('');
|
74
|
+
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
|
75
|
+
const [bottomSheetSize, setBottomSheetSize] = useState({
|
76
|
+
minHeight: MIN_HEIGHT_PERCENTAGE * windowHeight,
|
77
|
+
maxHeight: MAX_HEIGHT_PERCENTAGE * windowHeight,
|
78
|
+
initialHeight: INITIAL_HEIGHT_PERCENTAGE * windowHeight,
|
79
|
+
});
|
80
|
+
|
81
|
+
const sheetHeight = useRef(
|
82
|
+
new Animated.Value(bottomSheetSize.initialHeight),
|
83
|
+
).current;
|
84
|
+
const lastHeight = useRef(bottomSheetSize.initialHeight);
|
85
|
+
const dragStartY = useRef(0);
|
86
|
+
|
87
|
+
useEffect(() => {
|
88
|
+
if (modalType === 'popup') {
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
|
92
|
+
const DRAG_HANDLE_HEIGHT = 20;
|
93
|
+
const availableHeight = windowHeight - DRAG_HANDLE_HEIGHT;
|
94
|
+
|
95
|
+
const parsedMinHeight = parseHeight(minBottomsheetHeight, availableHeight);
|
96
|
+
const parsedMaxHeight = parseHeight(maxBottomsheetHeight, availableHeight);
|
97
|
+
const parsedInitialHeight = parseHeight(
|
98
|
+
initialBottomsheetHeight,
|
99
|
+
availableHeight,
|
100
|
+
);
|
101
|
+
|
102
|
+
setBottomSheetSize({
|
103
|
+
minHeight: parsedMinHeight || MIN_HEIGHT_PERCENTAGE * availableHeight,
|
104
|
+
maxHeight: parsedMaxHeight || MAX_HEIGHT_PERCENTAGE * availableHeight,
|
105
|
+
initialHeight:
|
106
|
+
parsedInitialHeight || INITIAL_HEIGHT_PERCENTAGE * availableHeight,
|
107
|
+
});
|
108
|
+
}, [
|
109
|
+
minBottomsheetHeight,
|
110
|
+
maxBottomsheetHeight,
|
111
|
+
initialBottomsheetHeight,
|
112
|
+
windowHeight,
|
113
|
+
modalType,
|
114
|
+
]);
|
115
|
+
|
116
|
+
// Resets to initial height when the modal is opened
|
117
|
+
useEffect(() => {
|
118
|
+
if (modalType === 'popup') {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
|
122
|
+
if (visible) {
|
123
|
+
sheetHeight.setValue(bottomSheetSize.initialHeight);
|
124
|
+
lastHeight.current = bottomSheetSize.initialHeight;
|
125
|
+
}
|
126
|
+
}, [visible, bottomSheetSize.initialHeight, modalType]);
|
127
|
+
|
128
|
+
useEffect(() => {
|
129
|
+
if (modalType === 'popup') {
|
130
|
+
return;
|
131
|
+
}
|
132
|
+
|
133
|
+
if (isKeyboardVisible) {
|
134
|
+
sheetHeight.setValue(
|
135
|
+
parseHeight(bottomSheetSize.maxHeight, windowHeight),
|
136
|
+
);
|
137
|
+
lastHeight.current = bottomSheetSize.maxHeight;
|
138
|
+
} else {
|
139
|
+
sheetHeight.setValue(parseHeight(lastHeight.current, windowHeight));
|
140
|
+
}
|
141
|
+
}, [isKeyboardVisible, modalType]);
|
142
|
+
|
143
|
+
// Monitors keyboard events
|
144
|
+
useEffect(() => {
|
145
|
+
if (modalType === 'popup') {
|
146
|
+
return;
|
147
|
+
}
|
148
|
+
|
149
|
+
const keyboardDidShowListener = Keyboard.addListener(
|
150
|
+
'keyboardDidShow',
|
151
|
+
() => {
|
152
|
+
setIsKeyboardVisible(true);
|
153
|
+
},
|
154
|
+
);
|
155
|
+
const keyboardDidHideListener = Keyboard.addListener(
|
156
|
+
'keyboardDidHide',
|
157
|
+
() => {
|
158
|
+
setIsKeyboardVisible(false);
|
159
|
+
},
|
160
|
+
);
|
161
|
+
|
162
|
+
return () => {
|
163
|
+
keyboardDidShowListener?.remove();
|
164
|
+
keyboardDidHideListener?.remove();
|
165
|
+
};
|
166
|
+
}, [modalType]);
|
167
|
+
|
168
|
+
const handlePanResponder = useMemo(
|
169
|
+
() =>
|
170
|
+
PanResponder.create({
|
171
|
+
onStartShouldSetPanResponder: () => {
|
172
|
+
// Only respond to touches on the drag handle
|
173
|
+
return true;
|
174
|
+
},
|
175
|
+
onMoveShouldSetPanResponder: (evt, gestureState) => {
|
176
|
+
// Only respond to vertical movements with sufficient distance
|
177
|
+
return Math.abs(gestureState.dy) > 5;
|
178
|
+
},
|
179
|
+
onPanResponderGrant: e => {
|
180
|
+
dragStartY.current = e.nativeEvent.pageY;
|
181
|
+
sheetHeight.stopAnimation();
|
182
|
+
},
|
183
|
+
onPanResponderMove: e => {
|
184
|
+
const currentY = e.nativeEvent.pageY;
|
185
|
+
const dy = currentY - dragStartY.current;
|
186
|
+
const proposedHeight = lastHeight.current - dy;
|
187
|
+
|
188
|
+
// Allows free dragging but with smooth limits
|
189
|
+
sheetHeight.setValue(proposedHeight);
|
190
|
+
},
|
191
|
+
onPanResponderRelease: e => {
|
192
|
+
const currentY = e.nativeEvent.pageY;
|
193
|
+
const dy = currentY - dragStartY.current;
|
194
|
+
const currentHeight = lastHeight.current - dy;
|
195
|
+
|
196
|
+
// Close modal if the final height is less than minHeight
|
197
|
+
if (currentHeight < bottomSheetSize.minHeight) {
|
198
|
+
Animated.timing(sheetHeight, {
|
199
|
+
toValue: 0,
|
200
|
+
duration: 200,
|
201
|
+
useNativeDriver: false,
|
202
|
+
}).start(onClose);
|
203
|
+
return;
|
204
|
+
}
|
205
|
+
|
206
|
+
// Snap to nearest limit
|
207
|
+
const finalHeight = Math.min(
|
208
|
+
Math.max(currentHeight, bottomSheetSize.minHeight),
|
209
|
+
bottomSheetSize.maxHeight,
|
210
|
+
);
|
211
|
+
|
212
|
+
Animated.spring(sheetHeight, {
|
213
|
+
toValue: finalHeight,
|
214
|
+
useNativeDriver: false,
|
215
|
+
tension: 50,
|
216
|
+
friction: 12,
|
217
|
+
}).start(() => {
|
218
|
+
lastHeight.current = finalHeight;
|
219
|
+
});
|
220
|
+
},
|
221
|
+
onPanResponderTerminate: () => {
|
222
|
+
// Reset to last stable height if gesture is terminated
|
223
|
+
Animated.spring(sheetHeight, {
|
224
|
+
toValue: lastHeight.current,
|
225
|
+
useNativeDriver: false,
|
226
|
+
tension: 50,
|
227
|
+
friction: 12,
|
228
|
+
}).start();
|
229
|
+
},
|
230
|
+
}),
|
231
|
+
[bottomSheetSize, sheetHeight, onClose],
|
232
|
+
);
|
50
233
|
|
51
234
|
// Obtain the country name in the selected language
|
52
235
|
const getCountryNameInLanguage = (country: ICountry): string => {
|
@@ -96,15 +279,24 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
|
|
96
279
|
|
97
280
|
const filteredCountries = countriesData.filter(country => {
|
98
281
|
const countryName = getCountryNameInLanguage(country);
|
282
|
+
const normalizedCountryName = normalizeCountryName(
|
283
|
+
countryName.toLowerCase(),
|
284
|
+
);
|
285
|
+
const normalizedQuery = normalizeCountryName(query);
|
99
286
|
const callingCode = country.idd.root.toLowerCase();
|
100
287
|
const flag = country.flag.toLowerCase();
|
288
|
+
const countryCode = country.cca2.toLowerCase();
|
289
|
+
|
101
290
|
return (
|
102
|
-
|
291
|
+
normalizedCountryName.includes(normalizedQuery) ||
|
292
|
+
countryName.toLowerCase().includes(query) ||
|
103
293
|
callingCode.includes(query) ||
|
104
|
-
flag.includes(query)
|
294
|
+
flag.includes(query) ||
|
295
|
+
countryCode.includes(query)
|
105
296
|
);
|
106
297
|
});
|
107
298
|
|
299
|
+
// Ordenar os paรญses filtrados alfabeticamente
|
108
300
|
return sortCountriesAlphabetically(filteredCountries);
|
109
301
|
}
|
110
302
|
|
@@ -164,6 +356,98 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
|
|
164
356
|
[],
|
165
357
|
);
|
166
358
|
|
359
|
+
const getItemLayout = useCallback(
|
360
|
+
(data: IListItem[] | null | undefined, index: number) => {
|
361
|
+
let offset = 0;
|
362
|
+
let length = ITEM_HEIGHT;
|
363
|
+
|
364
|
+
if (data) {
|
365
|
+
const item = data[index];
|
366
|
+
if ('isSection' in item) {
|
367
|
+
length = SECTION_HEADER_HEIGHT;
|
368
|
+
}
|
369
|
+
}
|
370
|
+
|
371
|
+
return {
|
372
|
+
length,
|
373
|
+
offset: offset + index * ITEM_HEIGHT,
|
374
|
+
index,
|
375
|
+
};
|
376
|
+
},
|
377
|
+
[],
|
378
|
+
);
|
379
|
+
|
380
|
+
const renderCloseButton = () => {
|
381
|
+
if (closeButtonComponent) {
|
382
|
+
return closeButtonComponent();
|
383
|
+
}
|
384
|
+
|
385
|
+
return (
|
386
|
+
<TouchableOpacity
|
387
|
+
testID="countrySelectCloseButton"
|
388
|
+
accessibilityRole="button"
|
389
|
+
accessibilityLabel="Country Select Modal Close Button"
|
390
|
+
accessibilityHint="Click to close the Country Select modal"
|
391
|
+
style={[styles.closeButton, countrySelectStyle?.popup?.closeButton]}
|
392
|
+
activeOpacity={0.6}
|
393
|
+
onPress={onClose}>
|
394
|
+
<Text
|
395
|
+
style={[
|
396
|
+
styles.closeButtonText,
|
397
|
+
countrySelectStyle?.popup?.closeButtonText,
|
398
|
+
]}>
|
399
|
+
{'\u00D7'}
|
400
|
+
</Text>
|
401
|
+
</TouchableOpacity>
|
402
|
+
);
|
403
|
+
};
|
404
|
+
|
405
|
+
const renderSearchInput = () => {
|
406
|
+
return (
|
407
|
+
<TextInput
|
408
|
+
testID="countrySelectSearchInput"
|
409
|
+
accessibilityRole="text"
|
410
|
+
accessibilityLabel="Country Select Search Input"
|
411
|
+
accessibilityHint="Type to search for a country"
|
412
|
+
style={[
|
413
|
+
styles.searchInput,
|
414
|
+
modalType === 'popup'
|
415
|
+
? countrySelectStyle?.popup?.searchInput
|
416
|
+
: countrySelectStyle?.bottomSheet?.searchInput,
|
417
|
+
]}
|
418
|
+
placeholder={
|
419
|
+
searchPlaceholder ||
|
420
|
+
translations.searchPlaceholder[language as ICountrySelectLanguages]
|
421
|
+
}
|
422
|
+
placeholderTextColor={
|
423
|
+
(modalType === 'popup'
|
424
|
+
? countrySelectStyle?.popup?.searchInputPlaceholder?.color
|
425
|
+
: countrySelectStyle?.bottomSheet?.searchInputPlaceholder?.color) ||
|
426
|
+
styles.searchInputPlaceholder.color
|
427
|
+
}
|
428
|
+
value={searchQuery}
|
429
|
+
onChangeText={setSearchQuery}
|
430
|
+
/>
|
431
|
+
);
|
432
|
+
};
|
433
|
+
|
434
|
+
const renderFlatList = () => {
|
435
|
+
return (
|
436
|
+
<FlatList
|
437
|
+
testID="countrySelectList"
|
438
|
+
accessibilityRole="list"
|
439
|
+
accessibilityLabel="Country Select List"
|
440
|
+
accessibilityHint="List of countries"
|
441
|
+
data={getCountries}
|
442
|
+
keyExtractor={keyExtractor}
|
443
|
+
renderItem={renderItem}
|
444
|
+
getItemLayout={getItemLayout}
|
445
|
+
keyboardShouldPersistTaps="handled"
|
446
|
+
showsVerticalScrollIndicator={showsVerticalScrollIndicator || false}
|
447
|
+
/>
|
448
|
+
);
|
449
|
+
};
|
450
|
+
|
167
451
|
const renderItem: ListRenderItem<IListItem> = useCallback(
|
168
452
|
({item, index}) => {
|
169
453
|
if ('isSection' in item) {
|
@@ -174,7 +458,12 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
|
|
174
458
|
<Text
|
175
459
|
testID="countrySelectSectionTitle"
|
176
460
|
accessibilityRole="header"
|
177
|
-
style={
|
461
|
+
style={[
|
462
|
+
styles.sectionTitle,
|
463
|
+
modalType === 'popup'
|
464
|
+
? countrySelectStyle?.popup?.sectionTitle
|
465
|
+
: countrySelectStyle?.bottomSheet?.sectionTitle,
|
466
|
+
]}>
|
178
467
|
{popularCountriesTitle && index === 0
|
179
468
|
? popularCountriesTitle
|
180
469
|
: allCountriesTitle && index > 0
|
@@ -195,6 +484,8 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
|
|
195
484
|
onClose={onClose}
|
196
485
|
theme={theme}
|
197
486
|
language={language}
|
487
|
+
modalType={modalType}
|
488
|
+
countrySelectStyle={countrySelectStyle}
|
198
489
|
/>
|
199
490
|
);
|
200
491
|
},
|
@@ -208,58 +499,111 @@ export const CountrySelect: React.FC<ICountrySelectProps> = ({
|
|
208
499
|
],
|
209
500
|
);
|
210
501
|
|
502
|
+
if (modalType === 'popup' || isFullScreen) {
|
503
|
+
return (
|
504
|
+
<Modal
|
505
|
+
visible={visible}
|
506
|
+
transparent
|
507
|
+
animationType="fade"
|
508
|
+
onRequestClose={onClose}
|
509
|
+
statusBarTranslucent
|
510
|
+
{...props}>
|
511
|
+
<Pressable
|
512
|
+
testID="countrySelectBackdrop"
|
513
|
+
accessibilityRole="button"
|
514
|
+
accessibilityLabel="Country Select Modal Backdrop"
|
515
|
+
accessibilityHint="Click to close the Country Select modal"
|
516
|
+
disabled={disabledBackdropPress || removedBackdrop}
|
517
|
+
style={[
|
518
|
+
styles.backdrop,
|
519
|
+
{alignItems: 'center', justifyContent: 'center'},
|
520
|
+
countrySelectStyle?.popup?.backdrop,
|
521
|
+
removedBackdrop && {backgroundColor: 'transparent'},
|
522
|
+
]}
|
523
|
+
onPress={onBackdropPress || onClose}>
|
524
|
+
<Pressable
|
525
|
+
style={[
|
526
|
+
styles.popupContainer,
|
527
|
+
countrySelectStyle?.popup?.popupContainer,
|
528
|
+
isFullScreen && {
|
529
|
+
flex: 1,
|
530
|
+
width: '100%',
|
531
|
+
height: '100%',
|
532
|
+
},
|
533
|
+
]}>
|
534
|
+
<View
|
535
|
+
style={[
|
536
|
+
styles.popupContent,
|
537
|
+
countrySelectStyle?.popup?.popupContent,
|
538
|
+
isFullScreen && {
|
539
|
+
borderRadius: 0,
|
540
|
+
},
|
541
|
+
]}>
|
542
|
+
{(isFullScreen || showSearchInput || showCloseButton) && (
|
543
|
+
<View
|
544
|
+
style={[
|
545
|
+
styles.searchContainer,
|
546
|
+
countrySelectStyle?.popup?.searchContainer,
|
547
|
+
]}>
|
548
|
+
{(isFullScreen || showCloseButton) && renderCloseButton()}
|
549
|
+
{showSearchInput && renderSearchInput()}
|
550
|
+
</View>
|
551
|
+
)}
|
552
|
+
|
553
|
+
{renderFlatList()}
|
554
|
+
</View>
|
555
|
+
</Pressable>
|
556
|
+
</Pressable>
|
557
|
+
</Modal>
|
558
|
+
);
|
559
|
+
}
|
560
|
+
|
211
561
|
return (
|
212
562
|
<Modal
|
213
563
|
visible={visible}
|
214
564
|
transparent
|
215
|
-
animationType="
|
565
|
+
animationType="slide"
|
216
566
|
onRequestClose={onClose}
|
217
567
|
statusBarTranslucent
|
218
568
|
{...props}>
|
219
|
-
<
|
569
|
+
<View
|
220
570
|
style={[
|
221
571
|
styles.backdrop,
|
222
|
-
{alignItems: 'center', justifyContent: 'center'},
|
223
572
|
removedBackdrop && {backgroundColor: 'transparent'},
|
224
|
-
]}
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
/>
|
573
|
+
]}>
|
574
|
+
<Pressable
|
575
|
+
testID="countrySelectBackdrop"
|
576
|
+
accessibilityRole="button"
|
577
|
+
accessibilityLabel="Country Select Modal Backdrop"
|
578
|
+
accessibilityHint="Click to close the Country Select modal"
|
579
|
+
disabled={disabledBackdropPress || removedBackdrop}
|
580
|
+
style={{flex: 1}}
|
581
|
+
onPress={onBackdropPress || onClose}
|
582
|
+
/>
|
583
|
+
<View style={styles.sheetContainer} pointerEvents="auto">
|
584
|
+
<View {...handlePanResponder.panHandlers} style={styles.dragHandle}>
|
585
|
+
<View style={styles.dragIndicator} />
|
260
586
|
</View>
|
261
|
-
|
262
|
-
|
587
|
+
<Animated.View
|
588
|
+
style={[
|
589
|
+
styles.sheetContent,
|
590
|
+
{
|
591
|
+
height: sheetHeight,
|
592
|
+
minHeight: bottomSheetSize.minHeight,
|
593
|
+
maxHeight: bottomSheetSize.maxHeight,
|
594
|
+
},
|
595
|
+
]}>
|
596
|
+
{(showSearchInput || showCloseButton) && (
|
597
|
+
<View style={styles.searchContainer}>
|
598
|
+
{showCloseButton && renderCloseButton()}
|
599
|
+
{showSearchInput && renderSearchInput()}
|
600
|
+
</View>
|
601
|
+
)}
|
602
|
+
|
603
|
+
<Animated.View style={{flex: 1}}>{renderFlatList()}</Animated.View>
|
604
|
+
</Animated.View>
|
605
|
+
</View>
|
606
|
+
</View>
|
263
607
|
</Modal>
|
264
608
|
);
|
265
609
|
};
|
@@ -1,12 +1,13 @@
|
|
1
|
-
import {StyleSheet} from 'react-native';
|
1
|
+
import {Platform, StatusBar, StyleSheet} from 'react-native';
|
2
2
|
|
3
|
-
export const createStyles =
|
3
|
+
export const createStyles = theme =>
|
4
4
|
StyleSheet.create({
|
5
5
|
backdrop: {
|
6
6
|
flex: 1,
|
7
7
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
8
8
|
},
|
9
9
|
popupContainer: {
|
10
|
+
marginTop: StatusBar.currentHeight,
|
10
11
|
width: '90%',
|
11
12
|
maxWidth: 600,
|
12
13
|
height: '60%',
|
@@ -22,15 +23,47 @@ export const createStyles = (theme: 'light' | 'dark') =>
|
|
22
23
|
alignSelf: 'center',
|
23
24
|
padding: 16,
|
24
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
|
+
},
|
41
|
+
dragHandle: {
|
42
|
+
width: '100%',
|
43
|
+
height: 24,
|
44
|
+
justifyContent: 'center',
|
45
|
+
alignItems: 'center',
|
46
|
+
backgroundColor: theme === 'dark' ? '#202020' : '#FFFFFF',
|
47
|
+
borderTopLeftRadius: 20,
|
48
|
+
borderTopRightRadius: 20,
|
49
|
+
marginBottom: -1,
|
50
|
+
},
|
51
|
+
dragIndicator: {
|
52
|
+
width: 40,
|
53
|
+
height: 4,
|
54
|
+
backgroundColor: theme === 'dark' ? '#FFFFFF40' : '#00000040',
|
55
|
+
borderRadius: 2,
|
56
|
+
},
|
25
57
|
searchContainer: {
|
26
58
|
marginBottom: 16,
|
27
|
-
paddingTop:
|
59
|
+
paddingTop: 8,
|
28
60
|
flexDirection: 'row',
|
29
61
|
},
|
30
62
|
searchInput: {
|
31
63
|
flex: 1,
|
32
64
|
borderRadius: 8,
|
33
65
|
paddingHorizontal: 16,
|
66
|
+
minHeight: 44,
|
34
67
|
fontSize: 16,
|
35
68
|
borderColor: theme === 'dark' ? '#F3F3F3' : '#303030',
|
36
69
|
borderWidth: 1,
|
@@ -57,6 +90,13 @@ export const createStyles = (theme: 'light' | 'dark') =>
|
|
57
90
|
flex: 0.1,
|
58
91
|
fontSize: 16,
|
59
92
|
color: theme === 'dark' ? '#FFFFFF' : '#000000',
|
93
|
+
fontFamily:
|
94
|
+
Platform.OS === 'web'
|
95
|
+
? typeof navigator !== 'undefined' &&
|
96
|
+
navigator?.userAgent?.includes('Win')
|
97
|
+
? 'TwemojiMozilla'
|
98
|
+
: 'System'
|
99
|
+
: 'System',
|
60
100
|
},
|
61
101
|
countryInfo: {
|
62
102
|
flex: 0.9,
|
@@ -80,4 +120,19 @@ export const createStyles = (theme: 'light' | 'dark') =>
|
|
80
120
|
paddingVertical: 8,
|
81
121
|
color: theme === 'dark' ? '#FFFFFF' : '#000000',
|
82
122
|
},
|
123
|
+
closeButton: {
|
124
|
+
marginRight: 10,
|
125
|
+
paddingHorizontal: 18,
|
126
|
+
alignItems: 'center',
|
127
|
+
justifyContent: 'center',
|
128
|
+
backgroundColor: theme === 'dark' ? '#303030' : '#F3F3F3',
|
129
|
+
borderColor: theme === 'dark' ? '#F3F3F3' : '#303030',
|
130
|
+
borderWidth: 1,
|
131
|
+
borderRadius: 12,
|
132
|
+
},
|
133
|
+
closeButtonText: {
|
134
|
+
fontSize: 24,
|
135
|
+
lineHeight: 28,
|
136
|
+
color: theme === 'dark' ? '#FFFFFF' : '#000000',
|
137
|
+
},
|
83
138
|
});
|
package/lib/index.d.ts
CHANGED
@@ -5,9 +5,35 @@ import {
|
|
5
5
|
ICountryCca2,
|
6
6
|
ICountryItemProps,
|
7
7
|
ICountrySelectProps,
|
8
|
+
ICountrySelectStyle,
|
8
9
|
ICountrySelectLanguages,
|
9
10
|
} from './interface';
|
10
11
|
|
12
|
+
declare function getAllCountries(): ICountry[];
|
13
|
+
|
14
|
+
declare function getCountryByCca2(cca2: string): ICountry | undefined;
|
15
|
+
|
16
|
+
declare function getCountryByCca3(cca3: string): ICountry | undefined;
|
17
|
+
|
18
|
+
declare function getCountriesByCallingCode(
|
19
|
+
callingCode: string,
|
20
|
+
): ICountry[] | undefined;
|
21
|
+
|
22
|
+
declare function getCountriesByName(
|
23
|
+
name: string,
|
24
|
+
language: ICountrySelectLanguages,
|
25
|
+
): ICountry[] | undefined;
|
26
|
+
|
27
|
+
declare function getCountriesByRegion(region: string): ICountry[] | undefined;
|
28
|
+
|
29
|
+
declare function getCountriesBySubregion(
|
30
|
+
subregion: string,
|
31
|
+
): ICountry[] | undefined;
|
32
|
+
|
33
|
+
declare function getContriesDependents(cca2: string): ICountry[] | undefined;
|
34
|
+
|
35
|
+
declare function getCountriesIndependents(): ICountry[] | undefined;
|
36
|
+
|
11
37
|
declare const CountrySelect: React.FC<ICountrySelectProps>;
|
12
38
|
|
13
39
|
export default CountrySelect;
|
@@ -17,5 +43,15 @@ export {
|
|
17
43
|
ICountryCca2,
|
18
44
|
ICountryItemProps,
|
19
45
|
ICountrySelectProps,
|
46
|
+
ICountrySelectStyle,
|
20
47
|
ICountrySelectLanguages,
|
48
|
+
getAllCountries,
|
49
|
+
getCountryByCca2,
|
50
|
+
getCountryByCca3,
|
51
|
+
getCountriesByCallingCode,
|
52
|
+
getCountriesByName,
|
53
|
+
getCountriesByRegion,
|
54
|
+
getCountriesBySubregion,
|
55
|
+
getContriesDependents,
|
56
|
+
getCountriesIndependents,
|
21
57
|
};
|
package/lib/index.tsx
CHANGED
@@ -4,15 +4,40 @@ import {
|
|
4
4
|
ICountryCca2,
|
5
5
|
ICountryItemProps,
|
6
6
|
ICountrySelectProps,
|
7
|
+
ICountrySelectStyle,
|
7
8
|
ICountrySelectLanguages,
|
8
9
|
} from './interface';
|
10
|
+
import {
|
11
|
+
getAllCountries,
|
12
|
+
getCountryByCca2,
|
13
|
+
getCountryByCca3,
|
14
|
+
getCountriesByCallingCode,
|
15
|
+
getCountriesByName,
|
16
|
+
getCountriesByRegion,
|
17
|
+
getCountriesBySubregion,
|
18
|
+
getContriesDependents,
|
19
|
+
getCountriesIndependents,
|
20
|
+
} from './utils/countryHelpers';
|
9
21
|
|
10
22
|
export default CountrySelect;
|
11
23
|
|
24
|
+
export {
|
25
|
+
getAllCountries,
|
26
|
+
getCountryByCca2,
|
27
|
+
getCountryByCca3,
|
28
|
+
getCountriesByCallingCode,
|
29
|
+
getCountriesByName,
|
30
|
+
getCountriesByRegion,
|
31
|
+
getCountriesBySubregion,
|
32
|
+
getContriesDependents,
|
33
|
+
getCountriesIndependents,
|
34
|
+
};
|
35
|
+
|
12
36
|
export type {
|
13
37
|
ICountry,
|
14
38
|
ICountryCca2,
|
15
39
|
ICountryItemProps,
|
16
40
|
ICountrySelectProps,
|
41
|
+
ICountrySelectStyle,
|
17
42
|
ICountrySelectLanguages,
|
18
43
|
};
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import {ICountry} from './country';
|
2
|
+
import {ICountrySelectStyle} from './countrySelectStyles';
|
2
3
|
import {ICountrySelectLanguages} from './countrySelectLanguages';
|
3
4
|
|
4
5
|
export interface ICountryItemProps {
|
@@ -7,4 +8,6 @@ export interface ICountryItemProps {
|
|
7
8
|
onClose: () => void;
|
8
9
|
language: ICountrySelectLanguages;
|
9
10
|
theme?: 'light' | 'dark';
|
11
|
+
modalType?: 'bottomSheet' | 'popup';
|
12
|
+
countrySelectStyle?: ICountrySelectStyle;
|
10
13
|
}
|
@@ -1,24 +1,35 @@
|
|
1
|
+
import * as React from 'react';
|
1
2
|
import {ModalProps} from 'react-native';
|
2
|
-
|
3
3
|
import {ICountry} from './country';
|
4
4
|
import {ICountryCca2} from './countryCca2';
|
5
|
-
import {ISectionTitle} from './sectionTitle';
|
6
5
|
import {ICountrySelectLanguages} from './countrySelectLanguages';
|
6
|
+
import {ICountrySelectStyle} from './countrySelectStyles';
|
7
|
+
import {ISectionTitle} from './sectionTitle';
|
7
8
|
|
8
9
|
export interface ICountrySelectProps extends ModalProps {
|
9
10
|
visible: boolean;
|
10
11
|
onClose: () => void;
|
11
12
|
onSelect: (country: ICountry) => void;
|
13
|
+
modalType?: 'bottomSheet' | 'popup';
|
14
|
+
countrySelectStyle?: ICountrySelectStyle;
|
12
15
|
theme?: 'light' | 'dark';
|
16
|
+
isFullScreen?: boolean;
|
13
17
|
popularCountries?: string[];
|
14
18
|
visibleCountries?: ICountryCca2[];
|
15
19
|
hiddenCountries?: ICountryCca2[];
|
16
20
|
language?: ICountrySelectLanguages;
|
21
|
+
showSearchInput?: boolean;
|
22
|
+
searchPlaceholder?: string;
|
23
|
+
showCloseButton?: boolean;
|
24
|
+
minBottomsheetHeight?: number | string;
|
25
|
+
maxBottomsheetHeight?: number | string;
|
26
|
+
initialBottomsheetHeight?: number | string;
|
17
27
|
disabledBackdropPress?: boolean;
|
18
28
|
removedBackdrop?: boolean;
|
19
29
|
onBackdropPress?: () => void;
|
20
30
|
countryItemComponent?: (item: ICountry) => React.ReactElement;
|
21
31
|
sectionTitleComponent?: (item: ISectionTitle) => React.ReactElement;
|
32
|
+
closeButtonComponent?: () => React.ReactElement;
|
22
33
|
popularCountriesTitle?: string;
|
23
34
|
allCountriesTitle?: string;
|
24
35
|
showsVerticalScrollIndicator?: boolean;
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import {StyleProp, TextStyle, ViewStyle} from 'react-native';
|
2
|
+
|
3
|
+
interface IBaseModalStyle {
|
4
|
+
backdrop?: StyleProp<ViewStyle>;
|
5
|
+
closeButton?: StyleProp<ViewStyle>;
|
6
|
+
closeButtonText?: StyleProp<TextStyle>;
|
7
|
+
searchContainer?: StyleProp<ViewStyle>;
|
8
|
+
searchInput?: StyleProp<TextStyle>;
|
9
|
+
searchInputPlaceholder?: {
|
10
|
+
color: string;
|
11
|
+
};
|
12
|
+
sectionTitle?: StyleProp<TextStyle>;
|
13
|
+
list?: StyleProp<ViewStyle>;
|
14
|
+
countryItem?: StyleProp<ViewStyle>;
|
15
|
+
flag?: StyleProp<TextStyle>;
|
16
|
+
countryInfo?: StyleProp<ViewStyle>;
|
17
|
+
callingCode?: StyleProp<TextStyle>;
|
18
|
+
countryName?: StyleProp<TextStyle>;
|
19
|
+
}
|
20
|
+
|
21
|
+
interface IPopupStyle extends IBaseModalStyle {
|
22
|
+
popupContainer?: StyleProp<ViewStyle>;
|
23
|
+
popupContent?: StyleProp<ViewStyle>;
|
24
|
+
}
|
25
|
+
|
26
|
+
interface IBottomSheetStyle extends IBaseModalStyle {
|
27
|
+
sheetContainer?: StyleProp<ViewStyle>;
|
28
|
+
sheetContent?: StyleProp<ViewStyle>;
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface ICountrySelectStyle {
|
32
|
+
popup?: IPopupStyle;
|
33
|
+
bottomSheet?: IBottomSheetStyle;
|
34
|
+
}
|
package/lib/interface/index.ts
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
// Re-export all interfaces from their respective files
|
2
1
|
export * from './countryCca2';
|
3
2
|
export * from './countrySelectLanguages';
|
4
3
|
export * from './country';
|
@@ -6,3 +5,4 @@ export * from './countrySelectProps';
|
|
6
5
|
export * from './countryItemProps';
|
7
6
|
export * from './sectionTitle';
|
8
7
|
export * from './itemList';
|
8
|
+
export * from './countrySelectStyles';
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import {ICountry, ICountryCca2} from '../interface';
|
2
|
+
import countriesData from '../constants/countries.json';
|
3
|
+
|
4
|
+
const countries: ICountry[] = countriesData as unknown as ICountry[];
|
5
|
+
|
6
|
+
export const getAllCountries = (): ICountry[] => {
|
7
|
+
return countries;
|
8
|
+
};
|
9
|
+
|
10
|
+
export const getCountriesByCallingCode = (callingCode: string): ICountry[] => {
|
11
|
+
return countries.filter(
|
12
|
+
(country: ICountry) => country.idd.root === callingCode,
|
13
|
+
);
|
14
|
+
};
|
15
|
+
|
16
|
+
export const getCountriesByName = (
|
17
|
+
name: string,
|
18
|
+
language: keyof ICountry['translations'] = 'eng',
|
19
|
+
): ICountry[] => {
|
20
|
+
return countries.filter((country: ICountry) => {
|
21
|
+
const translation = country.translations[language];
|
22
|
+
if (translation) {
|
23
|
+
return (
|
24
|
+
translation.common.toLowerCase().includes(name.toLowerCase()) ||
|
25
|
+
translation.official.toLowerCase().includes(name.toLowerCase())
|
26
|
+
);
|
27
|
+
}
|
28
|
+
return (
|
29
|
+
country.name.common.toLowerCase().includes(name.toLowerCase()) ||
|
30
|
+
country.name.official.toLowerCase().includes(name.toLowerCase())
|
31
|
+
);
|
32
|
+
});
|
33
|
+
};
|
34
|
+
|
35
|
+
export const getCountryByCca2 = (cca2: ICountryCca2): ICountry | undefined => {
|
36
|
+
return countries.find((country: ICountry) => country.cca2 === cca2);
|
37
|
+
};
|
38
|
+
|
39
|
+
export const getCountryByCca3 = (cca3: string): ICountry | undefined => {
|
40
|
+
return countries.find((country: ICountry) => country.cca3 === cca3);
|
41
|
+
};
|
42
|
+
|
43
|
+
export const getCountriesByRegion = (region: string): ICountry[] => {
|
44
|
+
return countries.filter((country: ICountry) => country.region === region);
|
45
|
+
};
|
46
|
+
|
47
|
+
export const getCountriesBySubregion = (subregion: string): ICountry[] => {
|
48
|
+
return countries.filter(
|
49
|
+
(country: ICountry) => country.subregion === subregion,
|
50
|
+
);
|
51
|
+
};
|
52
|
+
|
53
|
+
export const getCountriesIndependents = (): ICountry[] => {
|
54
|
+
return countries.filter((country: ICountry) => country.independent);
|
55
|
+
};
|
56
|
+
|
57
|
+
export const getContriesDependents = (): ICountry[] => {
|
58
|
+
return countries.filter((country: ICountry) => !country.independent);
|
59
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
const parseHeight = (
|
2
|
+
value: number | string | undefined,
|
3
|
+
windowHeight: number,
|
4
|
+
): number => {
|
5
|
+
if (value === undefined) {
|
6
|
+
return 0;
|
7
|
+
}
|
8
|
+
|
9
|
+
const MIN_ALLOWED_PERCENTAGE = 0.1; // 10%
|
10
|
+
const MAX_ALLOWED_PERCENTAGE = windowHeight; // 100%
|
11
|
+
|
12
|
+
if (typeof value === 'number') {
|
13
|
+
return Math.min(
|
14
|
+
Math.max(value, MIN_ALLOWED_PERCENTAGE * MAX_ALLOWED_PERCENTAGE),
|
15
|
+
MAX_ALLOWED_PERCENTAGE,
|
16
|
+
);
|
17
|
+
}
|
18
|
+
|
19
|
+
if (typeof value === 'string') {
|
20
|
+
const percentageMatch = value.match(/^(\d+(?:\.\d+)?)%$/);
|
21
|
+
|
22
|
+
if (percentageMatch) {
|
23
|
+
const percentage = parseFloat(percentageMatch[1]) / 100;
|
24
|
+
const height = percentage * MAX_ALLOWED_PERCENTAGE;
|
25
|
+
return Math.min(
|
26
|
+
Math.max(height, MIN_ALLOWED_PERCENTAGE * MAX_ALLOWED_PERCENTAGE),
|
27
|
+
MAX_ALLOWED_PERCENTAGE,
|
28
|
+
);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return 0;
|
33
|
+
};
|
34
|
+
|
35
|
+
export default parseHeight;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "react-native-country-select",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.0",
|
4
4
|
"description": "๐ 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.",
|
5
5
|
"main": "lib/index.tsx",
|
6
6
|
"types": "lib/index.d.ts",
|