├── .gitattributes
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── examples
├── demo.js
├── demo1.js
├── demo2.js
└── demo3.js
├── index.d.ts
├── index.js
├── package.json
└── src
├── SelectDropdown.js
├── components
├── DropdownModal.js
├── DropdownOverlay.js
├── DropdownWindow.js
└── Input.js
├── helpers
├── deepSearchInArr.js
├── findIndexInArr.js
├── getDropdownHeight.js
└── isExist.js
└── hooks
├── useKeyboardHeight.js
├── useLayoutDropdown.js
├── useRefs.js
└── useSelectDropdown.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # OSX
3 | #
4 | .DS_Store
5 |
6 | # node.js
7 | #
8 | node_modules/
9 | npm-debug.log
10 | yarn-error.log
11 |
12 |
13 | # Xcode
14 | #
15 | build/
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata
25 | *.xccheckout
26 | *.moved-aside
27 | DerivedData
28 | *.hmap
29 | *.ipa
30 | *.xcuserstate
31 | project.xcworkspace
32 |
33 |
34 | # Android/IntelliJ
35 | #
36 | build/
37 | .idea
38 | .gradle
39 | local.properties
40 | *.iml
41 |
42 | # BUCK
43 | buck-out/
44 | \.buckd/
45 | *.keystore
46 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | arrowParens: 'avoid',
7 | printWidth: 120,
8 | };
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 Adel Reda
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the
4 | Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
5 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
10 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
11 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
12 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-select-dropdown
2 |
3 | react-native-select-dropdown is a highly customized dropdown | select | picker | menu for react native that works for android and iOS platforms.
4 |
5 | ## Installation
6 |
7 | #### # Using npm
8 |
9 | ```bash
10 | npm install react-native-select-dropdown
11 | ```
12 |
13 | #### # Using yarn
14 |
15 | ```bash
16 | yarn add react-native-select-dropdown
17 | ```
18 |
19 | ## Demo
20 |
21 | #### Code provided in Examples folder.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | #### Search Functionality (Code provided in Examples folder).
30 |
31 |
32 |
33 |
34 |
35 | ## 🚀 Major Changes
36 |
37 | ### Version 4.0
38 |
39 | - (defaultButtonText, buttonTextAfterSelection, buttonStyle, buttonTextStyle, renderCustomizedButtonChild, renderDropdownIcon, dropdownIconPosition) have been removed and (renderButton) has been added to customize dropdown button
40 | - (rowTextForSelection, rowStyle, rowTextStyle, selectedRowStyle, selectedRowTextStyle, renderCustomizedRowChild) have been removed and (renderItem) has been added to customize each dropdown item
41 | - testID added to scroll the dropdown menu in e2e tests.
42 | - Most of issues have been fixed.
43 | - Updated readme.md file
44 | - More examples in examples folder.
45 |
46 | ## Usage
47 |
48 | ```
49 | import SelectDropdown from 'react-native-select-dropdown'
50 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
51 | ...
52 | const emojisWithIcons = [
53 | {title: 'happy', icon: 'emoticon-happy-outline'},
54 | {title: 'cool', icon: 'emoticon-cool-outline'},
55 | {title: 'lol', icon: 'emoticon-lol-outline'},
56 | {title: 'sad', icon: 'emoticon-sad-outline'},
57 | {title: 'cry', icon: 'emoticon-cry-outline'},
58 | {title: 'angry', icon: 'emoticon-angry-outline'},
59 | {title: 'confused', icon: 'emoticon-confused-outline'},
60 | {title: 'excited', icon: 'emoticon-excited-outline'},
61 | {title: 'kiss', icon: 'emoticon-kiss-outline'},
62 | {title: 'devil', icon: 'emoticon-devil-outline'},
63 | {title: 'dead', icon: 'emoticon-dead-outline'},
64 | {title: 'wink', icon: 'emoticon-wink-outline'},
65 | {title: 'sick', icon: 'emoticon-sick-outline'},
66 | {title: 'frown', icon: 'emoticon-frown-outline'},
67 | ];
68 | ...
69 | {
72 | console.log(selectedItem, index);
73 | }}
74 | renderButton={(selectedItem, isOpened) => {
75 | return (
76 |
77 | {selectedItem && (
78 |
79 | )}
80 |
81 | {(selectedItem && selectedItem.title) || 'Select your mood'}
82 |
83 |
84 |
85 | );
86 | }}
87 | renderItem={(item, index, isSelected) => {
88 | return (
89 |
90 |
91 | {item.title}
92 |
93 | );
94 | }}
95 | showsVerticalScrollIndicator={false}
96 | dropdownStyle={styles.dropdownMenuStyle}
97 | />
98 | ...
99 | const styles = StyleSheet.create({
100 | dropdownButtonStyle: {
101 | width: 200,
102 | height: 50,
103 | backgroundColor: '#E9ECEF',
104 | borderRadius: 12,
105 | flexDirection: 'row',
106 | justifyContent: 'center',
107 | alignItems: 'center',
108 | paddingHorizontal: 12,
109 | },
110 | dropdownButtonTxtStyle: {
111 | flex: 1,
112 | fontSize: 18,
113 | fontWeight: '500',
114 | color: '#151E26',
115 | },
116 | dropdownButtonArrowStyle: {
117 | fontSize: 28,
118 | },
119 | dropdownButtonIconStyle: {
120 | fontSize: 28,
121 | marginRight: 8,
122 | },
123 | dropdownMenuStyle: {
124 | backgroundColor: '#E9ECEF',
125 | borderRadius: 8,
126 | },
127 | dropdownItemStyle: {
128 | width: '100%',
129 | flexDirection: 'row',
130 | paddingHorizontal: 12,
131 | justifyContent: 'center',
132 | alignItems: 'center',
133 | paddingVertical: 8,
134 | },
135 | dropdownItemTxtStyle: {
136 | flex: 1,
137 | fontSize: 18,
138 | fontWeight: '500',
139 | color: '#151E26',
140 | },
141 | dropdownItemIconStyle: {
142 | fontSize: 28,
143 | marginRight: 8,
144 | },
145 | });
146 | ```
147 |
148 | ### Props
149 |
150 | - [`data`](#data)
151 |
152 | - [`onSelect`](#onSelect)
153 |
154 | - [`renderButton`](#renderButton)
155 |
156 | - [`renderItem`](#renderItem)
157 |
158 | - [`defaultValue`](#defaultValue)
159 |
160 | - [`defaultValueByIndex`](#defaultValueByIndex)
161 |
162 | - [`disabled`](#disabled)
163 |
164 | - [`disabledIndexes`](#disabledIndexes)
165 |
166 | - [`disableAutoScroll`](#disableAutoScroll)
167 |
168 | - [`testID`](#testID)
169 |
170 | - [`onFocus`](#onFocus)
171 |
172 | - [`onBlur`](#onBlur)
173 |
174 | - [`onScrollEndReached`](#onScrollEndReached)
175 |
176 | - [`statusBarTranslucent`](#statusBarTranslucent)
177 |
178 | - [`dropdownStyle`](#dropdownStyle)
179 |
180 | - [`dropdownOverlayColor`](#dropdownOverlayColor)
181 |
182 | - [`showsVerticalScrollIndicator`](#showsVerticalScrollIndicator)
183 |
184 | - [`search`](#search)
185 |
186 | - [`searchInputStyle`](#searchInputStyle)
187 |
188 | - [`searchInputTxtColor`](#searchInputTxtColor)
189 |
190 | - [`searchInputTxtStyle`](#searchInputTxtStyle)
191 |
192 | - [`searchPlaceHolder`](#searchPlaceHolder)
193 |
194 | - [`searchPlaceHolderColor`](#searchPlaceHolderColor)
195 |
196 | - [`renderSearchInputLeftIcon`](#renderSearchInputLeftIcon)
197 |
198 | - [`renderSearchInputRightIcon`](#renderSearchInputRightIcon)
199 |
200 | - [`onChangeSearchInputText`](#onChangeSearchInputText)
201 |
202 | ### Methods
203 |
204 | - [`reset`](#License)
205 | - [`openDropdown`](#License)
206 | - [`closeDropdown`](#License)
207 | - [`selectIndex`](#License)
208 |
209 | ---
210 |
211 | ### data
212 |
213 | array of data that will be represented in dropdown 'can be array of objects
214 |
215 | | Type | Required |
216 | | ----- | -------- |
217 | | array | Yes |
218 |
219 | ---
220 |
221 | ### onSelect
222 |
223 | function recieves selected item and its index in data array
224 |
225 | | Type | Required |
226 | | -------- | -------- |
227 | | function | Yes |
228 |
229 | ---
230 |
231 | ### renderButton
232 |
233 | function returns React component for the dropdown button
234 |
235 | | Type | Required |
236 | | -------- | -------- |
237 | | function | Yes |
238 |
239 | ---
240 |
241 | ### renderItem
242 |
243 | function returns React component for each dropdown item
244 |
245 | | Type | Required |
246 | | -------- | -------- |
247 | | function | Yes |
248 |
249 | ---
250 |
251 | ### defaultValue
252 |
253 | default selected item in dropdown ( check examples in Demo1)
254 |
255 | | Type | Required |
256 | | ---- | -------- |
257 | | any | No |
258 |
259 | ---
260 |
261 | ### defaultValueByIndex
262 |
263 | default selected item index
264 |
265 | | Type | Required |
266 | | ------- | -------- |
267 | | integer | No |
268 |
269 | ---
270 |
271 | ### disabled
272 |
273 | disable dropdown
274 |
275 | | Type | Required |
276 | | ------- | -------- |
277 | | boolean | No |
278 |
279 | ---
280 |
281 | ### disabledIndexes
282 |
283 | array of disabled items index
284 |
285 | | Type | Required |
286 | | ----- | -------- |
287 | | array | No |
288 |
289 | ---
290 |
291 | ### disableAutoScroll
292 |
293 | disable auto scroll to selected value
294 |
295 | | Type | Required |
296 | | ------- | -------- |
297 | | boolean | No |
298 |
299 | ---
300 |
301 | ### testID
302 |
303 | dropdown menu testID
304 |
305 | | Type | Required |
306 | | ------ | -------- |
307 | | string | No |
308 |
309 | ---
310 |
311 | ### onFocus
312 |
313 | function fires when dropdown is opened
314 |
315 | | Type | Required |
316 | | -------- | -------- |
317 | | function | No |
318 |
319 | ---
320 |
321 | ### onBlur
322 |
323 | function fires when dropdown is closed
324 |
325 | | Type | Required |
326 | | -------- | -------- |
327 | | function | No |
328 |
329 | ---
330 |
331 | ### onScrollEndReached
332 |
333 | function fires when dropdown scrolls to the end (for paginations)
334 |
335 | | Type | Required |
336 | | -------- | -------- |
337 | | function | No |
338 |
339 | ---
340 |
341 | ### statusBarTranslucent
342 |
343 | required to set true when statusbar is translucent `(android only)`
344 |
345 | | Type | Required |
346 | | ------- | -------- |
347 | | boolean | No |
348 |
349 | ---
350 |
351 | ### dropdownStyle
352 |
353 | style object for dropdown view
354 |
355 | | Type | Required |
356 | | ------ | -------- |
357 | | object | No |
358 |
359 | ---
360 |
361 | ### dropdownOverlayColor
362 |
363 | backdrop color when dropdown is opened
364 |
365 | | Type | Required |
366 | | ------ | -------- |
367 | | string | No |
368 |
369 | ---
370 |
371 | ### showsVerticalScrollIndicator
372 |
373 | When true, shows a vertical scroll indicator.
374 |
375 | | Type | Required |
376 | | ------- | -------- |
377 | | boolean | No |
378 |
379 | ---
380 |
381 | ### search
382 |
383 | enable search functionality
384 |
385 | | Type | Required |
386 | | ------- | -------- |
387 | | boolean | No |
388 |
389 | ---
390 |
391 | ### searchInputStyle
392 |
393 | style object for search input
394 |
395 | | Type | Required |
396 | | ------ | -------- |
397 | | object | No |
398 |
399 | ---
400 |
401 | ### searchInputTxtColor
402 |
403 | text color for search input
404 |
405 | | Type | Required |
406 | | ------ | -------- |
407 | | string | No |
408 |
409 | ---
410 |
411 | ### searchInputTxtStyle
412 |
413 | style object for search input text
414 |
415 | | Type | Required |
416 | | ------ | -------- |
417 | | object | No |
418 |
419 | ---
420 |
421 | ### searchPlaceHolder
422 |
423 | placeholder text for search input
424 |
425 | | Type | Required |
426 | | ------ | -------- |
427 | | string | No |
428 |
429 | ---
430 |
431 | ### searchPlaceHolderColor
432 |
433 | text color for search input placeholder
434 |
435 | | Type | Required |
436 | | ------ | -------- |
437 | | string | No |
438 |
439 | ---
440 |
441 | ### renderSearchInputLeftIcon
442 |
443 | function returns React component for search input icon
444 |
445 | | Type | Required |
446 | | -------- | -------- |
447 | | function | No |
448 |
449 | ---
450 |
451 | ### renderSearchInputRightIcon
452 |
453 | function returns React component for search input icon
454 |
455 | | Type | Required |
456 | | -------- | -------- |
457 | | function | No |
458 |
459 | ---
460 |
461 | ### onChangeSearchInputText
462 |
463 | function callback when the search input text changes, this will automatically disable the dropdown's internal search to be implemented manually outside the component
464 |
465 | | Type | Required |
466 | | -------- | -------- |
467 | | function | No |
468 |
469 | ---
470 |
471 | | Method | Description |
472 | | -------------------- | -------------------------------- |
473 | | `reset()` | Remove selection & reset it |
474 | | `openDropdown()` | Open the dropdown. |
475 | | `closeDropdown()` | Close the dropdown. |
476 | | `selectIndex(index)` | Select a specific item by index. |
477 |
478 | ---
479 |
480 | ## License
481 |
482 | [MIT](https://choosealicense.com/licenses/mit/)
483 |
--------------------------------------------------------------------------------
/examples/demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {StyleSheet, Text, View} from 'react-native';
3 | import SelectDropdown from 'react-native-select-dropdown';
4 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
5 |
6 | const Demo = () => {
7 | const emojisWithIcons = [
8 | {title: 'happy', icon: 'emoticon-happy-outline'},
9 | {title: 'cool', icon: 'emoticon-cool-outline'},
10 | {title: 'lol', icon: 'emoticon-lol-outline'},
11 | {title: 'sad', icon: 'emoticon-sad-outline'},
12 | {title: 'cry', icon: 'emoticon-cry-outline'},
13 | {title: 'angry', icon: 'emoticon-angry-outline'},
14 | {title: 'confused', icon: 'emoticon-confused-outline'},
15 | {title: 'excited', icon: 'emoticon-excited-outline'},
16 | {title: 'kiss', icon: 'emoticon-kiss-outline'},
17 | {title: 'devil', icon: 'emoticon-devil-outline'},
18 | {title: 'dead', icon: 'emoticon-dead-outline'},
19 | {title: 'wink', icon: 'emoticon-wink-outline'},
20 | {title: 'sick', icon: 'emoticon-sick-outline'},
21 | {title: 'frown', icon: 'emoticon-frown-outline'},
22 | ];
23 |
24 | return (
25 |
26 |
27 | Demo
28 |
29 | {
32 | console.log(selectedItem, index);
33 | }}
34 | renderButton={(selectedItem, isOpen) => {
35 | return (
36 |
37 | {selectedItem && }
38 |
39 | {(selectedItem && selectedItem.title) || 'Select your mood'}
40 |
41 |
42 |
43 | );
44 | }}
45 | renderItem={(item, index, isSelected) => {
46 | return (
47 |
52 |
53 | {item.title}
54 |
55 | );
56 | }}
57 | showsVerticalScrollIndicator={false}
58 | dropdownStyle={styles.dropdownMenuStyle}
59 | />
60 |
61 | );
62 | };
63 |
64 | export default Demo;
65 |
66 | const styles = StyleSheet.create({
67 | container: {
68 | flex: 1,
69 | paddingVertical: 100,
70 | alignItems: 'center',
71 | justifyContent: 'space-evenly',
72 | paddingTop: 90,
73 | },
74 | header: {
75 | position: 'absolute',
76 | top: 0,
77 | width: '100%',
78 | height: 90,
79 | backgroundColor: '#E9ECEF',
80 | justifyContent: 'flex-end',
81 | alignItems: 'center',
82 | paddingBottom: 16,
83 | },
84 | headerTxt: {
85 | fontSize: 18,
86 | fontWeight: 'bold',
87 | color: '#151E26',
88 | },
89 | dropdownButtonStyle: {
90 | width: 200,
91 | height: 50,
92 | backgroundColor: '#E9ECEF',
93 | borderRadius: 12,
94 | flexDirection: 'row',
95 | justifyContent: 'center',
96 | alignItems: 'center',
97 | paddingHorizontal: 12,
98 | },
99 | dropdownButtonTxtStyle: {
100 | flex: 1,
101 | fontSize: 18,
102 | fontWeight: '500',
103 | color: '#151E26',
104 | },
105 | dropdownButtonArrowStyle: {
106 | fontSize: 28,
107 | },
108 | dropdownButtonIconStyle: {
109 | fontSize: 28,
110 | marginRight: 8,
111 | },
112 | dropdownMenuStyle: {
113 | backgroundColor: '#E9ECEF',
114 | borderRadius: 8,
115 | },
116 | dropdownItemStyle: {
117 | width: '100%',
118 | flexDirection: 'row',
119 | paddingHorizontal: 12,
120 | justifyContent: 'center',
121 | alignItems: 'center',
122 | paddingVertical: 8,
123 | },
124 | dropdownItemTxtStyle: {
125 | flex: 1,
126 | fontSize: 18,
127 | fontWeight: '500',
128 | color: '#151E26',
129 | },
130 | dropdownItemIconStyle: {
131 | fontSize: 28,
132 | marginRight: 8,
133 | },
134 | });
135 |
--------------------------------------------------------------------------------
/examples/demo1.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {StyleSheet, Text, View} from 'react-native';
3 | import SelectDropdown from 'react-native-select-dropdown';
4 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
5 |
6 | const Demo1 = () => {
7 | const emojis = [
8 | 'happy',
9 | 'cool',
10 | 'lol',
11 | 'sad',
12 | 'cry',
13 | 'angry',
14 | 'confused',
15 | 'excited',
16 | 'kiss',
17 | 'devil',
18 | 'dead',
19 | 'wink',
20 | 'sick',
21 | 'frown',
22 | ];
23 | const emojisWithIcons = [
24 | {title: 'happy', icon: 'emoticon-happy-outline'},
25 | {title: 'cool', icon: 'emoticon-cool-outline'},
26 | {title: 'lol', icon: 'emoticon-lol-outline'},
27 | {title: 'sad', icon: 'emoticon-sad-outline'},
28 | {title: 'cry', icon: 'emoticon-cry-outline'},
29 | {title: 'angry', icon: 'emoticon-angry-outline'},
30 | {title: 'confused', icon: 'emoticon-confused-outline'},
31 | {title: 'excited', icon: 'emoticon-excited-outline'},
32 | {title: 'kiss', icon: 'emoticon-kiss-outline'},
33 | {title: 'devil', icon: 'emoticon-devil-outline'},
34 | {title: 'dead', icon: 'emoticon-dead-outline'},
35 | {title: 'wink', icon: 'emoticon-wink-outline'},
36 | {title: 'sick', icon: 'emoticon-sick-outline'},
37 | {title: 'frown', icon: 'emoticon-frown-outline'},
38 | ];
39 |
40 | return (
41 |
42 |
43 | Demo 1
44 |
45 | {
48 | console.log(selectedItem, index);
49 | }}
50 | // defaultValueByIndex={8} // use default value by index or default value
51 | // defaultValue={'kiss'} // use default value by index or default value
52 | renderButton={(selectedItem, isOpen) => {
53 | return (
54 |
55 | {selectedItem || 'Select your mood'}
56 |
57 | );
58 | }}
59 | renderItem={(item, index, isSelected) => {
60 | return (
61 |
66 | {item}
67 |
68 | );
69 | }}
70 | dropdownStyle={styles.dropdownMenuStyle}
71 | />
72 |
73 | {
76 | console.log(selectedItem, index);
77 | }}
78 | // defaultValueByIndex={8} // use default value by index or default value
79 | // defaultValue={{title: 'kiss', icon: 'emoticon-kiss-outline'}} // use default value by index or default value
80 | renderButton={(selectedItem, isOpen) => {
81 | return (
82 |
83 |
84 |
85 | {(selectedItem && selectedItem.title) || 'Select your mood'}
86 |
87 |
88 |
89 | );
90 | }}
91 | renderItem={(item, index, isSelected) => {
92 | return (
93 |
98 |
99 | {item.title}
100 |
101 | );
102 | }}
103 | dropdownStyle={styles.dropdown1MenuStyle}
104 | showsVerticalScrollIndicator={false}
105 | />
106 |
107 | {
110 | console.log(selectedItem, index);
111 | }}
112 | // defaultValueByIndex={8} // use default value by index or default value
113 | // defaultValue={{title: 'kiss', icon: 'emoticon-kiss-outline'}} // use default value by index or default value
114 | renderButton={(selectedItem, isOpen) => {
115 | return (
116 |
117 | {selectedItem && }
118 |
119 | {(selectedItem && selectedItem.title) || 'Select your mood'}
120 |
121 |
122 |
123 | );
124 | }}
125 | renderItem={(item, index, isSelected) => {
126 | return (
127 |
132 |
133 | {item.title}
134 |
135 | );
136 | }}
137 | dropdownStyle={styles.dropdown2MenuStyle}
138 | showsVerticalScrollIndicator={false}
139 | />
140 |
141 | );
142 | };
143 |
144 | export default Demo1;
145 |
146 | const styles = StyleSheet.create({
147 | container: {
148 | flex: 1,
149 | paddingVertical: 50,
150 | alignItems: 'center',
151 | justifyContent: 'space-between',
152 | paddingTop: 116,
153 | },
154 | header: {
155 | position: 'absolute',
156 | top: 0,
157 | width: '100%',
158 | height: 90,
159 | backgroundColor: '#E9ECEF',
160 | justifyContent: 'flex-end',
161 | alignItems: 'center',
162 | paddingBottom: 16,
163 | },
164 | headerTxt: {
165 | fontSize: 18,
166 | fontWeight: 'bold',
167 | color: '#151E26',
168 | },
169 | dropdownButtonStyle: {
170 | width: 200,
171 | height: 50,
172 | backgroundColor: '#E9ECEF',
173 | borderRadius: 12,
174 | flexDirection: 'row',
175 | justifyContent: 'center',
176 | alignItems: 'center',
177 | paddingHorizontal: 12,
178 | },
179 | dropdownButtonTxtStyle: {
180 | flex: 1,
181 | fontSize: 18,
182 | fontWeight: '500',
183 | color: '#151E26',
184 | textAlign: 'center',
185 | },
186 | dropdownMenuStyle: {
187 | backgroundColor: '#E9ECEF',
188 | borderRadius: 8,
189 | height: 150,
190 | },
191 | dropdownItemStyle: {
192 | width: '100%',
193 | flexDirection: 'row',
194 | paddingHorizontal: 12,
195 | justifyContent: 'center',
196 | alignItems: 'center',
197 | paddingVertical: 12,
198 | borderBottomWidth: 1,
199 | borderBottomColor: '#B1BDC8',
200 | },
201 | dropdownItemTxtStyle: {
202 | flex: 1,
203 | fontSize: 18,
204 | fontWeight: '500',
205 | color: '#151E26',
206 | textAlign: 'center',
207 | },
208 | dropdownItemIconStyle: {
209 | fontSize: 28,
210 | marginRight: 8,
211 | },
212 | ////////////// dropdown1
213 | dropdown1ButtonStyle: {
214 | width: '80%',
215 | height: 50,
216 | borderRadius: 12,
217 | flexDirection: 'row',
218 | justifyContent: 'center',
219 | alignItems: 'center',
220 | paddingHorizontal: 12,
221 | backgroundColor: '#444444',
222 | },
223 | dropdown1ButtonTxtStyle: {
224 | flex: 1,
225 | fontSize: 18,
226 | fontWeight: '500',
227 | color: '#FFFFFF',
228 | textAlign: 'center',
229 | },
230 | dropdown1ButtonArrowStyle: {
231 | fontSize: 28,
232 | color: '#FFFFFF',
233 | },
234 | dropdown1ButtonIconStyle: {
235 | fontSize: 28,
236 | marginRight: 8,
237 | color: '#FFFFFF',
238 | },
239 | dropdown1MenuStyle: {
240 | backgroundColor: '#444444',
241 | borderRadius: 8,
242 | },
243 | dropdown1ItemStyle: {
244 | width: '100%',
245 | flexDirection: 'row',
246 | paddingHorizontal: 12,
247 | justifyContent: 'center',
248 | alignItems: 'center',
249 | paddingVertical: 12,
250 | borderBottomWidth: 1,
251 | borderBottomColor: '#B1BDC8',
252 | },
253 | dropdown1ItemTxtStyle: {
254 | flex: 1,
255 | fontSize: 18,
256 | fontWeight: '500',
257 | color: '#FFFFFF',
258 | },
259 | dropdown1ItemIconStyle: {
260 | fontSize: 28,
261 | marginRight: 8,
262 | color: '#FFFFFF',
263 | },
264 | ////////////// dropdown2
265 | dropdown2ButtonStyle: {
266 | width: '80%',
267 | height: 50,
268 | borderRadius: 12,
269 | flexDirection: 'row',
270 | justifyContent: 'center',
271 | alignItems: 'center',
272 | paddingHorizontal: 12,
273 | borderWidth: 1,
274 | borderColor: '#B1BDC8',
275 | },
276 | dropdown2ButtonTxtStyle: {
277 | flex: 1,
278 | fontSize: 18,
279 | fontWeight: '500',
280 | color: '#151E26',
281 | },
282 | dropdown2ButtonArrowStyle: {
283 | fontSize: 28,
284 | },
285 | dropdown2ButtonIconStyle: {
286 | fontSize: 28,
287 | marginRight: 8,
288 | },
289 | dropdown2MenuStyle: {
290 | backgroundColor: '#FFF',
291 | borderRadius: 8,
292 | },
293 | dropdown2ItemStyle: {
294 | width: '100%',
295 | flexDirection: 'row',
296 | paddingHorizontal: 12,
297 | justifyContent: 'center',
298 | alignItems: 'center',
299 | paddingVertical: 12,
300 | borderBottomWidth: 1,
301 | borderBottomColor: '#B1BDC8',
302 | },
303 | dropdown2ItemTxtStyle: {
304 | flex: 1,
305 | fontSize: 18,
306 | fontWeight: '500',
307 | color: '#151E26',
308 | },
309 | dropdown2ItemIconStyle: {
310 | fontSize: 28,
311 | marginRight: 8,
312 | },
313 | });
314 |
--------------------------------------------------------------------------------
/examples/demo2.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {StyleSheet, Text, View} from 'react-native';
3 | import SelectDropdown from 'react-native-select-dropdown';
4 |
5 | const Demo2 = () => {
6 | const [feelings, setFeelings] = useState([]);
7 | const [moods, setMoods] = useState([]);
8 |
9 | const moodDropdownRef = useRef();
10 |
11 | useEffect(() => {
12 | setTimeout(() => {
13 | setFeelings([
14 | {title: 'positive', moods: [{title: 'Happy'}, {title: 'lol'}]},
15 | {title: 'negative', moods: [{title: 'Sad'}, {title: 'Angry'}]},
16 | ]);
17 | }, 1000);
18 | }, []);
19 |
20 | return (
21 |
22 |
23 | Demo 2
24 |
25 |
26 | {
29 | console.log(selectedItem, index);
30 | moodDropdownRef.current.reset();
31 | setMoods([]);
32 | setMoods(selectedItem.moods);
33 | // setTimeout(() => {
34 | // moodDropdownRef.current.selectIndex(1);
35 | // }, 250);
36 | }}
37 | renderButton={(selectedItem, isOpen) => {
38 | return (
39 |
40 | {selectedItem?.title || 'Select a feeling'}
41 |
42 | );
43 | }}
44 | renderItem={(item, index, isSelected) => {
45 | return (
46 |
51 | {item.title}
52 |
53 | );
54 | }}
55 | dropdownStyle={styles.dropdownMenuStyle}
56 | />
57 |
58 | {
62 | console.log(selectedItem, index);
63 | }}
64 | renderButton={(selectedItem, isOpen) => {
65 | return (
66 |
67 | {selectedItem?.title || 'Select a mood'}
68 |
69 | );
70 | }}
71 | renderItem={(item, index, isSelected) => {
72 | return (
73 |
78 | {item.title}
79 |
80 | );
81 | }}
82 | dropdownStyle={styles.dropdownMenuStyle}
83 | />
84 |
85 |
86 | );
87 | };
88 |
89 | export default Demo2;
90 |
91 | const styles = StyleSheet.create({
92 | container: {
93 | flex: 1,
94 | flexDirection: 'row',
95 | alignItems: 'center',
96 | justifyContent: 'space-between',
97 | },
98 | header: {
99 | position: 'absolute',
100 | top: 0,
101 | width: '100%',
102 | height: 90,
103 | backgroundColor: '#E9ECEF',
104 | justifyContent: 'flex-end',
105 | alignItems: 'center',
106 | paddingBottom: 16,
107 | },
108 | headerTxt: {
109 | fontSize: 18,
110 | fontWeight: 'bold',
111 | color: '#151E26',
112 | },
113 | dropdownButtonStyle: {
114 | flex: 1,
115 | height: 50,
116 | backgroundColor: '#E9ECEF',
117 | borderRadius: 12,
118 | flexDirection: 'row',
119 | justifyContent: 'center',
120 | alignItems: 'center',
121 | paddingHorizontal: 12,
122 | },
123 | dropdownButtonTxtStyle: {
124 | flex: 1,
125 | fontSize: 18,
126 | fontWeight: '500',
127 | color: '#151E26',
128 | textAlign: 'center',
129 | },
130 | dropdownMenuStyle: {
131 | backgroundColor: '#E9ECEF',
132 | borderRadius: 8,
133 | height: 100,
134 | },
135 | dropdownItemStyle: {
136 | width: '100%',
137 | height: 50,
138 | flexDirection: 'row',
139 | paddingHorizontal: 12,
140 | justifyContent: 'center',
141 | alignItems: 'center',
142 | borderBottomWidth: 1,
143 | borderBottomColor: '#B1BDC8',
144 | },
145 | dropdownItemTxtStyle: {
146 | flex: 1,
147 | fontSize: 18,
148 | fontWeight: '500',
149 | color: '#151E26',
150 | textAlign: 'center',
151 | },
152 | dropdownItemIconStyle: {
153 | fontSize: 28,
154 | marginRight: 8,
155 | },
156 | });
157 |
--------------------------------------------------------------------------------
/examples/demo3.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {StyleSheet, Text, View} from 'react-native';
3 | import SelectDropdown from 'react-native-select-dropdown';
4 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
5 | import FontAwesome from 'react-native-vector-icons/FontAwesome';
6 |
7 | const Demo3 = () => {
8 | const emojis = [
9 | 'happy',
10 | 'cool',
11 | 'lol',
12 | 'sad',
13 | 'cry',
14 | 'angry',
15 | 'confused',
16 | 'excited',
17 | 'kiss',
18 | 'devil',
19 | 'dead',
20 | 'wink',
21 | 'sick',
22 | 'frown',
23 | ];
24 | const emojisWithIcons = [
25 | {title: 'happy', icon: 'emoticon-happy-outline'},
26 | {title: 'cool', icon: 'emoticon-cool-outline'},
27 | {title: 'lol', icon: 'emoticon-lol-outline'},
28 | {title: 'sad', icon: 'emoticon-sad-outline'},
29 | {title: 'cry', icon: 'emoticon-cry-outline'},
30 | {title: 'angry', icon: 'emoticon-angry-outline'},
31 | {title: 'confused', icon: 'emoticon-confused-outline'},
32 | {title: 'excited', icon: 'emoticon-excited-outline'},
33 | {title: 'kiss', icon: 'emoticon-kiss-outline'},
34 | {title: 'devil', icon: 'emoticon-devil-outline'},
35 | {title: 'dead', icon: 'emoticon-dead-outline'},
36 | {title: 'wink', icon: 'emoticon-wink-outline'},
37 | {title: 'sick', icon: 'emoticon-sick-outline'},
38 | {title: 'frown', icon: 'emoticon-frown-outline'},
39 | ];
40 |
41 | return (
42 |
43 |
44 | Demo 3
45 |
46 | {
51 | console.log(selectedItem, index);
52 | }}
53 | renderButton={(selectedItem, isOpen) => {
54 | return (
55 |
56 | {selectedItem || 'Select your mood'}
57 |
58 | );
59 | }}
60 | renderItem={(item, index, isSelected) => {
61 | return (
62 |
67 | {item}
68 |
69 | );
70 | }}
71 | dropdownStyle={styles.dropdownMenuStyle}
72 | search
73 | searchInputStyle={styles.dropdownSearchInputStyle}
74 | searchInputTxtColor={'#151E26'}
75 | searchPlaceHolder={'Search here'}
76 | searchPlaceHolderColor={'#72808D'}
77 | renderSearchInputLeftIcon={() => {
78 | return ;
79 | }}
80 | />
81 |
82 | {
87 | console.log(selectedItem, index);
88 | }}
89 | renderButton={(selectedItem, isOpen) => {
90 | return (
91 |
92 |
93 |
94 | {(selectedItem && selectedItem.title) || 'Select your mood'}
95 |
96 |
97 |
98 | );
99 | }}
100 | renderItem={(item, index, isSelected) => {
101 | return (
102 |
107 |
108 | {item.title}
109 |
110 | );
111 | }}
112 | dropdownStyle={styles.dropdown1MenuStyle}
113 | showsVerticalScrollIndicator={false}
114 | search
115 | searchInputStyle={styles.dropdown1SearchInputStyle}
116 | searchInputTxtColor={'#FFFFFF'}
117 | searchPlaceHolder={'Search here'}
118 | searchPlaceHolderColor={'#F8F8F8'}
119 | renderSearchInputLeftIcon={() => {
120 | return ;
121 | }}
122 | />
123 |
124 | {
129 | console.log(selectedItem, index);
130 | }}
131 | renderButton={(selectedItem, isOpen) => {
132 | return (
133 |
134 |
135 |
136 | {(selectedItem && selectedItem.title) || 'Select your mood'}
137 |
138 |
139 |
140 | );
141 | }}
142 | renderItem={(item, index, isSelected) => {
143 | return (
144 |
149 |
150 | {item.title}
151 |
152 | );
153 | }}
154 | dropdownStyle={styles.dropdown2MenuStyle}
155 | showsVerticalScrollIndicator={false}
156 | search
157 | searchInputStyle={styles.dropdown2SearchInputStyle}
158 | searchInputTxtColor={'#151E26'}
159 | searchPlaceHolder={'Search here'}
160 | searchPlaceHolderColor={'#72808D'}
161 | renderSearchInputLeftIcon={() => {
162 | return ;
163 | }}
164 | />
165 |
166 | );
167 | };
168 |
169 | export default Demo3;
170 |
171 | const styles = StyleSheet.create({
172 | container: {
173 | flex: 1,
174 | paddingVertical: 50,
175 | alignItems: 'center',
176 | justifyContent: 'space-between',
177 | paddingTop: 116,
178 | },
179 | header: {
180 | position: 'absolute',
181 | top: 0,
182 | width: '100%',
183 | height: 90,
184 | backgroundColor: '#E9ECEF',
185 | justifyContent: 'flex-end',
186 | alignItems: 'center',
187 | paddingBottom: 16,
188 | },
189 | headerTxt: {
190 | fontSize: 18,
191 | fontWeight: 'bold',
192 | color: '#151E26',
193 | },
194 | dropdownButtonStyle: {
195 | width: 350,
196 | height: 50,
197 | backgroundColor: '#E9ECEF',
198 | borderRadius: 12,
199 | flexDirection: 'row',
200 | justifyContent: 'center',
201 | alignItems: 'center',
202 | paddingHorizontal: 12,
203 | },
204 | dropdownButtonTxtStyle: {
205 | flex: 1,
206 | fontSize: 18,
207 | fontWeight: '500',
208 | color: '#151E26',
209 | textAlign: 'center',
210 | },
211 | dropdownMenuStyle: {
212 | backgroundColor: '#E9ECEF',
213 | borderRadius: 8,
214 | },
215 | dropdownSearchInputStyle: {
216 | backgroundColor: '#E9ECEF',
217 | borderRadius: 8,
218 | borderBottomWidth: 1,
219 | borderBottomColor: '#B1BDC8',
220 | },
221 | dropdownItemStyle: {
222 | width: '100%',
223 | flexDirection: 'row',
224 | paddingHorizontal: 12,
225 | justifyContent: 'center',
226 | alignItems: 'center',
227 | paddingVertical: 12,
228 | borderBottomWidth: 1,
229 | borderBottomColor: '#B1BDC8',
230 | },
231 | dropdownItemTxtStyle: {
232 | flex: 1,
233 | fontSize: 18,
234 | fontWeight: '500',
235 | color: '#151E26',
236 | textAlign: 'center',
237 | },
238 | dropdownItemIconStyle: {
239 | fontSize: 28,
240 | marginRight: 8,
241 | },
242 | ////////////// dropdown1
243 | dropdown1ButtonStyle: {
244 | width: '80%',
245 | height: 50,
246 | borderRadius: 12,
247 | flexDirection: 'row',
248 | justifyContent: 'center',
249 | alignItems: 'center',
250 | paddingHorizontal: 12,
251 | backgroundColor: '#444444',
252 | },
253 | dropdown1ButtonTxtStyle: {
254 | flex: 1,
255 | fontSize: 18,
256 | fontWeight: '500',
257 | color: '#FFFFFF',
258 | textAlign: 'center',
259 | },
260 | dropdown1ButtonArrowStyle: {
261 | fontSize: 28,
262 | color: '#FFFFFF',
263 | },
264 | dropdown1ButtonIconStyle: {
265 | fontSize: 28,
266 | marginRight: 8,
267 | color: '#FFFFFF',
268 | },
269 | dropdown1MenuStyle: {
270 | backgroundColor: '#444444',
271 | borderRadius: 8,
272 | },
273 | dropdown1SearchInputStyle: {
274 | backgroundColor: '#444444',
275 | borderBottomWidth: 1,
276 | borderBottomColor: '#FFFFFF',
277 | },
278 | dropdown1ItemStyle: {
279 | width: '100%',
280 | flexDirection: 'row',
281 | paddingHorizontal: 12,
282 | justifyContent: 'center',
283 | alignItems: 'center',
284 | paddingVertical: 12,
285 | borderBottomWidth: 1,
286 | borderBottomColor: '#B1BDC8',
287 | },
288 | dropdown1ItemTxtStyle: {
289 | flex: 1,
290 | fontSize: 18,
291 | fontWeight: '500',
292 | color: '#FFFFFF',
293 | },
294 | dropdown1ItemIconStyle: {
295 | fontSize: 28,
296 | marginRight: 8,
297 | color: '#FFFFFF',
298 | },
299 | ////////////// dropdown2
300 | dropdown2ButtonStyle: {
301 | width: '80%',
302 | height: 50,
303 | borderRadius: 12,
304 | flexDirection: 'row',
305 | justifyContent: 'center',
306 | alignItems: 'center',
307 | paddingHorizontal: 12,
308 | borderWidth: 1,
309 | borderColor: '#B1BDC8',
310 | },
311 | dropdown2ButtonTxtStyle: {
312 | flex: 1,
313 | fontSize: 18,
314 | fontWeight: '500',
315 | color: '#151E26',
316 | },
317 | dropdown2ButtonArrowStyle: {
318 | fontSize: 28,
319 | },
320 | dropdown2ButtonIconStyle: {
321 | fontSize: 28,
322 | marginRight: 8,
323 | },
324 | dropdown2MenuStyle: {
325 | backgroundColor: '#FFF',
326 | borderRadius: 8,
327 | },
328 | dropdown2SearchInputStyle: {
329 | backgroundColor: '#FFFFFF',
330 | borderRadius: 8,
331 | borderBottomWidth: 1,
332 | borderBottomColor: '#B1BDC8',
333 | },
334 | dropdown2ItemStyle: {
335 | width: '100%',
336 | flexDirection: 'row',
337 | paddingHorizontal: 12,
338 | justifyContent: 'center',
339 | alignItems: 'center',
340 | paddingVertical: 12,
341 | borderBottomWidth: 1,
342 | borderBottomColor: '#B1BDC8',
343 | },
344 | dropdown2ItemTxtStyle: {
345 | flex: 1,
346 | fontSize: 18,
347 | fontWeight: '500',
348 | color: '#151E26',
349 | },
350 | dropdown2ItemIconStyle: {
351 | fontSize: 28,
352 | marginRight: 8,
353 | },
354 | });
355 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import type * as React from 'react';
2 | import {StyleProp, ViewStyle, TextStyle} from 'react-native';
3 |
4 | declare module 'react-native-select-dropdown' {
5 | export type SelectDropdownProps = {
6 | /**
7 | * array of data that will be represented in dropdown, can be array of objects
8 | */
9 | data: Array;
10 | /**
11 | * function recieves selected item and its index in data array
12 | */
13 | onSelect: (selectedItem: any, index: number) => void;
14 | /**
15 | * function returns React component for the dropdown button
16 | */
17 | renderButton: (selectedItem: any, isOpened: boolean) => React.ReactNode;
18 | /**
19 | * function returns React component for each dropdown item
20 | */
21 | renderItem: (selectedItem: any, index: number, isSelected: boolean) => React.ReactNode;
22 | /**
23 | * default selected item in dropdown
24 | */
25 | defaultValue?: any;
26 | /**
27 | * default selected item index
28 | */
29 | defaultValueByIndex?: number;
30 | /**
31 | * disable dropdown
32 | */
33 | disabled?: boolean;
34 | /**
35 | * array of disabled items index
36 | */
37 | disabledIndexes?: number[];
38 | /**
39 | * disable auto scroll to selected value
40 | */
41 | disableAutoScroll?: boolean;
42 | /**
43 | * dropdown menu testID
44 | */
45 | testID?: string;
46 | /**
47 | * function fires when dropdown is opened
48 | */
49 | onFocus?: () => void;
50 | /**
51 | * function fires when dropdown is closed
52 | */
53 | onBlur?: () => void;
54 | /**
55 | * function fires when dropdown reaches the end
56 | */
57 | onScrollEndReached?: () => void;
58 | /**
59 | * required to set true when statusbar is translucent (android only)
60 | */
61 | statusBarTranslucent?: boolean;
62 | /**
63 | * style object for dropdown view
64 | */
65 | dropdownStyle?: StyleProp;
66 | /**
67 | * backdrop color when dropdown is opened
68 | */
69 | dropdownOverlayColor?: string;
70 | /**
71 | * When true, shows a vertical scroll indicator in the dropdown.
72 | */
73 | showsVerticalScrollIndicator?: boolean;
74 | /**
75 | * enable search functionality
76 | */
77 | search?: boolean;
78 | /**
79 | * style object for search input
80 | */
81 | searchInputStyle?: StyleProp;
82 | /**
83 | * text color for search input
84 | */
85 | searchInputTxtColor?: string;
86 | /**
87 | * text style for search input
88 | */
89 | searchInputTxtStyle?: StyleProp;
90 | /**
91 | * placeholder text for search input
92 | */
93 | searchPlaceHolder?: string;
94 | /**
95 | * text color for search input placeholder
96 | */
97 | searchPlaceHolderColor?: string;
98 | /**
99 | * function callback when the search input text changes, this will automatically disable the dropdown's internal search to be implemented manually outside the component
100 | */
101 | onChangeSearchInputText?: (searchText: string) => void;
102 | /**
103 | * function returns React component for search input icon
104 | */
105 | renderSearchInputLeftIcon?: (selectedItem: any, index: number) => React.ReactNode;
106 | /**
107 | * function returns React component for search input icon
108 | */
109 | renderSearchInputRightIcon?: (selectedItem: any, index: number) => React.ReactNode;
110 | };
111 |
112 | export default class SelectDropdown extends React.Component {
113 | /**
114 | * Remove selection & reset it
115 | */
116 | reset(): void;
117 | /**
118 | * Open the dropdown.
119 | */
120 | openDropdown(): void;
121 | /**
122 | * Close the dropdown.
123 | */
124 | closeDropdown(): void;
125 | /**
126 | * Select index.
127 | */
128 | selectIndex(index: number): void;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import SelectDropdown from './src/SelectDropdown';
2 |
3 | export default SelectDropdown;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-select-dropdown",
3 | "version": "4.0.1",
4 | "description": "react-native-select-dropdown is a highly customized dropdown | select | picker | menu for react native that works for andriod and iOS platforms.",
5 | "main": "index.js",
6 | "typings": "index.d.ts",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [
11 | "react-native",
12 | "android",
13 | "ios",
14 | "dropdown",
15 | "select",
16 | "option",
17 | "selector",
18 | "picker",
19 | "menu",
20 | "modal"
21 | ],
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/AdelRedaa97/react-native-select-dropdown"
25 | },
26 | "author": "Adel Reda (AdelRedaa97)",
27 | "license": "MIT"
28 | }
--------------------------------------------------------------------------------
/src/SelectDropdown.js:
--------------------------------------------------------------------------------
1 | import React, {forwardRef, useImperativeHandle, useCallback} from 'react';
2 | import {View, TouchableOpacity, FlatList} from 'react-native';
3 | import {isExist} from './helpers/isExist';
4 | import Input from './components/Input';
5 | import DropdownOverlay from './components/DropdownOverlay';
6 | import DropdownModal from './components/DropdownModal';
7 | import DropdownWindow from './components/DropdownWindow';
8 | import {useSelectDropdown} from './hooks/useSelectDropdown';
9 | import {useLayoutDropdown} from './hooks/useLayoutDropdown';
10 | import {useRefs} from './hooks/useRefs';
11 | import {findIndexInArr} from './helpers/findIndexInArr';
12 |
13 | const SelectDropdown = (
14 | {
15 | data /* array */,
16 | onSelect /* function */,
17 | renderButton /* function returns React component for the dropdown button */,
18 | renderItem /* function returns React component for each dropdown Item */,
19 | defaultValue /* any */,
20 | defaultValueByIndex /* integer */,
21 | disabled /* boolean */,
22 | disabledIndexes /* array of disabled items index */,
23 | disableAutoScroll /* boolean */,
24 | testID /* dropdown menu testID */,
25 | onFocus /* function */,
26 | onBlur /* function */,
27 | onScrollEndReached /* function */,
28 | /////////////////////////////
29 | statusBarTranslucent /* boolean */,
30 | dropdownStyle /* style object for search input */,
31 | dropdownOverlayColor /* string */,
32 | showsVerticalScrollIndicator /* boolean */,
33 | /////////////////////////////
34 | search /* boolean */,
35 | searchInputStyle /* style object for search input */,
36 | searchInputTxtColor /* text color for search input */,
37 | searchInputTxtStyle /* text style for search input */,
38 | searchPlaceHolder /* placeholder text for search input */,
39 | searchPlaceHolderColor /* text color for search input placeholder */,
40 | renderSearchInputLeftIcon /* function returns React component for search input icon */,
41 | renderSearchInputRightIcon /* function returns React component for search input icon */,
42 | onChangeSearchInputText /* function callback when the search input text changes, this will automatically disable the dropdown's interna search to be implemented manually outside the component */,
43 | },
44 | ref,
45 | ) => {
46 | const disabledInternalSearch = !!onChangeSearchInputText;
47 | /* ******************* hooks ******************* */
48 | const {dropdownButtonRef, dropDownFlatlistRef} = useRefs();
49 | const {
50 | dataArr, //
51 | selectedItem,
52 | selectItem,
53 | reset,
54 | searchTxt,
55 | setSearchTxt,
56 | } = useSelectDropdown(data, defaultValueByIndex, defaultValue, disabledInternalSearch);
57 | const {
58 | isVisible, //
59 | setIsVisible,
60 | buttonLayout,
61 | onDropdownButtonLayout,
62 | dropdownWindowStyle,
63 | onRequestClose,
64 | } = useLayoutDropdown(data, dropdownStyle);
65 | useImperativeHandle(ref, () => ({
66 | reset: () => {
67 | reset();
68 | },
69 | openDropdown: () => {
70 | openDropdown();
71 | },
72 | closeDropdown: () => {
73 | closeDropdown();
74 | },
75 | selectIndex: index => {
76 | selectItem(index);
77 | },
78 | }));
79 | /* ******************* Methods ******************* */
80 | const openDropdown = () => {
81 | dropdownButtonRef.current.measure((fx, fy, w, h, px, py) => {
82 | onDropdownButtonLayout(w, h, px, py);
83 | setIsVisible(true);
84 | onFocus && onFocus();
85 | scrollToSelectedItem();
86 | });
87 | };
88 | const closeDropdown = () => {
89 | setIsVisible(false);
90 | setSearchTxt('');
91 | onBlur && onBlur();
92 | };
93 | const scrollToSelectedItem = () => {
94 | const indexInCurrArr = findIndexInArr(selectedItem, dataArr);
95 | setTimeout(() => {
96 | if (disableAutoScroll) {
97 | return;
98 | }
99 | if (indexInCurrArr > 1) {
100 | dropDownFlatlistRef?.current?.scrollToIndex({
101 | index: search ? indexInCurrArr - 1 : indexInCurrArr,
102 | animated: true,
103 | });
104 | }
105 | }, 200);
106 | };
107 | const onSelectItem = (item, index) => {
108 | const indexInOriginalArr = findIndexInArr(item, data);
109 | closeDropdown();
110 | onSelect && onSelect(item, indexInOriginalArr);
111 | selectItem(indexInOriginalArr);
112 | };
113 | const onScrollToIndexFailed = error => {
114 | dropDownFlatlistRef.current.scrollToOffset({
115 | offset: error.averageItemLength * error.index,
116 | animated: true,
117 | });
118 | setTimeout(() => {
119 | if (dataArr.length !== 0 && dropDownFlatlistRef) {
120 | dropDownFlatlistRef.current.scrollToIndex({index: error.index, animated: true});
121 | }
122 | }, 100);
123 | };
124 | /* ******************** Render Methods ******************** */
125 | const renderSearchView = () => {
126 | return (
127 | search && (
128 | {
135 | setSearchTxt(txt);
136 | disabledInternalSearch && onChangeSearchInputText(txt);
137 | }}
138 | inputStyle={searchInputStyle}
139 | inputTextStyle={searchInputTxtStyle}
140 | renderLeft={renderSearchInputLeftIcon}
141 | renderRight={renderSearchInputRightIcon}
142 | />
143 | )
144 | );
145 | };
146 | const renderFlatlistItem = ({item, index}) => {
147 | const indexInCurrArr = findIndexInArr(selectedItem, dataArr);
148 | const isSelected = index == indexInCurrArr;
149 |
150 | let clonedElement = renderItem ? renderItem(item, index, isSelected) : ;
151 | let props = {...clonedElement.props};
152 | return (
153 | isExist(item) && (
154 | onSelectItem(item, index)}>
159 | {props?.children}
160 |
161 | )
162 | );
163 | };
164 | const renderDropdown = () => {
165 | return (
166 | isVisible && (
167 |
168 |
169 |
170 | index.toString()}
174 | ref={dropDownFlatlistRef}
175 | renderItem={renderFlatlistItem}
176 | ListHeaderComponent={renderSearchView()}
177 | stickyHeaderIndices={search && [0]}
178 | keyboardShouldPersistTaps="always"
179 | onEndReached={() => onScrollEndReached && onScrollEndReached()}
180 | onEndReachedThreshold={0.5}
181 | showsVerticalScrollIndicator={showsVerticalScrollIndicator}
182 | onScrollToIndexFailed={onScrollToIndexFailed}
183 | />
184 |
185 |
186 | )
187 | );
188 | };
189 | ///////////////////////////////////////////////////////
190 | let clonedElement = renderButton ? renderButton(selectedItem, isVisible) : ;
191 | let props = {...clonedElement.props};
192 | return (
193 |
194 | {renderDropdown()}
195 | {props?.children}
196 |
197 | );
198 | };
199 |
200 | export default forwardRef((props, ref) => SelectDropdown(props, ref));
201 |
--------------------------------------------------------------------------------
/src/components/DropdownModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Modal} from 'react-native';
3 |
4 | const DropdownModal = ({visible, statusBarTranslucent, onRequestClose, children}) => {
5 | const defaults = {
6 | statusBarTranslucent: statusBarTranslucent || false,
7 | };
8 | return (
9 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | export default DropdownModal;
22 |
--------------------------------------------------------------------------------
/src/components/DropdownOverlay.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet, TouchableOpacity} from 'react-native';
2 | import React from 'react';
3 |
4 | const DropdownOverlay = ({onPress, backgroundColor}) => {
5 | const defaults = {
6 | backgroundColor: backgroundColor || 'rgba(0,0,0,0.4)',
7 | };
8 | return (
9 |
19 | );
20 | };
21 |
22 | export default DropdownOverlay;
23 |
24 | const styles = StyleSheet.create({
25 | dropdownOverlay: {
26 | width: '100%',
27 | height: '100%',
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/src/components/DropdownWindow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, StyleSheet} from 'react-native';
3 |
4 | const DropdownWindow = ({layoutStyle, children}) => {
5 | return {children};
6 | };
7 |
8 | export default DropdownWindow;
9 |
10 | const styles = StyleSheet.create({
11 | dropdownOverlayView: {
12 | backgroundColor: '#EFEFEF',
13 | },
14 | shadow: {
15 | shadowColor: '#000',
16 | shadowOffset: {width: 0, height: 6},
17 | shadowOpacity: 0.1,
18 | shadowRadius: 10,
19 | elevation: 10,
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/src/components/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {forwardRef} from 'react';
3 | import {View, TextInput, StyleSheet, I18nManager} from 'react-native';
4 |
5 | const voidFunction = () => {};
6 |
7 | const Input = (
8 | {
9 | searchViewWidth,
10 | inputStyle,
11 | inputTextStyle,
12 | value,
13 | valueColor,
14 | placeholder,
15 | placeholderTextColor,
16 | textAlign,
17 | onChangeText,
18 | onEndEditing,
19 | onSubmitEditing,
20 | renderLeft,
21 | renderRight,
22 | testID,
23 | },
24 | ref,
25 | ) => {
26 | const defaults = {
27 | inputStyle: inputStyle,
28 | inputTextStyle: inputTextStyle,
29 | value: value ?? '',
30 | valueColor: valueColor ?? '#000000',
31 | placeholder: placeholder ?? '',
32 | placeholderTextColor: placeholderTextColor ?? '#CACACA',
33 | textAlign: textAlign || (I18nManager.isRTL ? 'right' : 'left'),
34 | onChangeText: onChangeText ?? voidFunction,
35 | onEndEditing: onEndEditing ?? voidFunction,
36 | onSubmitEditing: onSubmitEditing ?? voidFunction,
37 | renderLeft: renderLeft,
38 | renderRight: renderRight,
39 | testID: testID,
40 | };
41 |
42 | const onChangeTextValidator = txt => {
43 | if (txt.length == 1 && txt == ' ') {
44 | return;
45 | }
46 | if (txt.length > 1 && txt.slice(-2) == ' ') {
47 | return;
48 | }
49 | defaults.onChangeText(txt);
50 | };
51 |
52 | return (
53 |
54 |
59 | {defaults.renderLeft && {defaults.renderLeft()}}
60 |
80 | {defaults.renderRight && {defaults.renderRight()}}
81 |
82 |
83 | );
84 | };
85 |
86 | export default forwardRef((props, ref) => Input(props, ref));
87 |
88 | const styles = StyleSheet.create({
89 | searchViewStyle: {
90 | height: 50,
91 | paddingHorizontal: 0,
92 | },
93 | defaultInputStyle: {
94 | width: '100%',
95 | height: '100%',
96 | backgroundColor: '#FFFFFF',
97 | flexDirection: 'row',
98 | justifyContent: 'center',
99 | paddingHorizontal: '4%',
100 | },
101 | inputField: {
102 | flex: 1,
103 | height: '100%',
104 | backgroundColor: '#0000',
105 | textAlignVertical: 'center',
106 | paddingVertical: 0,
107 | },
108 | pressableLeft: {
109 | height: '100%',
110 | marginRight: '4%',
111 | justifyContent: 'center',
112 | },
113 | pressableRight: {
114 | height: '100%',
115 | marginLeft: '4%',
116 | justifyContent: 'center',
117 | },
118 | });
119 |
--------------------------------------------------------------------------------
/src/helpers/deepSearchInArr.js:
--------------------------------------------------------------------------------
1 | const contains = (item, searchTxt) => {
2 | // item is an object
3 | if (typeof item == 'object' && item != null) {
4 | for (let key in item) {
5 | const value = item[key];
6 | if (contains(value, searchTxt)) {
7 | return true;
8 | }
9 | }
10 | }
11 | // string, number or boolean
12 | if (typeof item != 'object' && item != null && item != undefined) {
13 | const itemStringfied = item.toString().toLowerCase();
14 | const searchTxtStringfied = searchTxt.toString().toLowerCase();
15 | if (itemStringfied.includes(searchTxtStringfied)) {
16 | return true;
17 | }
18 | }
19 | return false;
20 | };
21 |
22 | export const deepSearchInArr = (query, arr) => {
23 | let array = [];
24 | for (let i = 0; i <= arr.length - 1; i++) {
25 | if (contains(arr[i], query)) {
26 | array.push(arr[i]);
27 | }
28 | if (i == arr.length - 1) {
29 | return array;
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/helpers/findIndexInArr.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export const findIndexInArr = (obj, arr) => {
4 | var defaultValueIndex = -1;
5 | if (typeof obj == 'object') {
6 | for (let index = 0; index < arr.length; index++) {
7 | const element = arr[index];
8 | if (_.isEqual(element, obj)) {
9 | defaultValueIndex = index;
10 | }
11 | if (index == arr.length - 1) {
12 | return defaultValueIndex;
13 | }
14 | }
15 | } else {
16 | for (let index = 0; index < arr.length; index++) {
17 | const element = arr[index];
18 | if (element == obj) {
19 | defaultValueIndex = index;
20 | }
21 | if (index == arr.length - 1) {
22 | if (index == arr.length - 1) {
23 | return defaultValueIndex;
24 | }
25 | }
26 | }
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/helpers/getDropdownHeight.js:
--------------------------------------------------------------------------------
1 | export const getDropdownHeight = (dropdownStyle, dataLength) => {
2 | if (dropdownStyle && dropdownStyle.height) {
3 | return dropdownStyle.height;
4 | } else {
5 | if (dataLength == 0) {
6 | return 150;
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/src/helpers/isExist.js:
--------------------------------------------------------------------------------
1 | export const isExist = value => {
2 | if (value != undefined && value != null) {
3 | return true;
4 | }
5 | return false;
6 | };
7 |
--------------------------------------------------------------------------------
/src/hooks/useKeyboardHeight.js:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from 'react';
2 | import {Keyboard} from 'react-native';
3 |
4 | export const useKeyboardHeight = () => {
5 | const [keyboardHeight, setKeyboardHeight] = useState(0);
6 |
7 | useEffect(() => {
8 | const showSubscription = Keyboard.addListener('keyboardDidShow', e => {
9 | setKeyboardHeight(e.endCoordinates.height);
10 | });
11 | const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
12 | setKeyboardHeight(0);
13 | });
14 | return () => {
15 | showSubscription.remove();
16 | hideSubscription.remove();
17 | };
18 | }, [setKeyboardHeight]);
19 |
20 | return {keyboardHeight};
21 | };
22 |
--------------------------------------------------------------------------------
/src/hooks/useLayoutDropdown.js:
--------------------------------------------------------------------------------
1 | import {useEffect, useState, useMemo} from 'react';
2 | import {I18nManager, Dimensions} from 'react-native';
3 | import {getDropdownHeight} from '../helpers/getDropdownHeight';
4 | import {useKeyboardHeight} from './useKeyboardHeight';
5 | const {height} = Dimensions.get('window');
6 | const DROPDOWN_MAX_HEIGHT = height * 0.4;
7 |
8 | export const useLayoutDropdown = (data, dropdownStyle) => {
9 | const [isVisible, setIsVisible] = useState(false); // dropdown visible ?
10 | const [buttonLayout, setButtonLayout] = useState(null);
11 | const [dropdownCalculatedStyle, setDropdownCalculatedStyle] = useState({});
12 |
13 | const [dropdownHEIGHT, setDropdownHEIGHT] = useState(() => {
14 | return getDropdownHeight(dropdownStyle, data?.length || 0);
15 | }); // dropdown height
16 |
17 | const {keyboardHeight} = useKeyboardHeight();
18 |
19 | useEffect(() => {
20 | setDropdownHEIGHT(getDropdownHeight(dropdownStyle, data?.length || 0));
21 | }, [JSON.stringify(dropdownStyle), JSON.stringify(data)]);
22 |
23 | const onDropdownButtonLayout = (w, h, px, py) => {
24 | setButtonLayout({w, h, px, py});
25 |
26 | const remainingHeight = dropdownStyle?.height || height / 4;
27 |
28 | if (py + h > height - remainingHeight) {
29 | return setDropdownCalculatedStyle({
30 | bottom: height - (py + h) + h,
31 | width: dropdownStyle?.width || w,
32 | ...(I18nManager.isRTL ? {right: dropdownStyle?.right || px} : {left: dropdownStyle?.left || px}),
33 | });
34 | }
35 |
36 | return setDropdownCalculatedStyle({
37 | top: py + h + 2,
38 | width: dropdownStyle?.width || w,
39 | ...(I18nManager.isRTL ? {right: dropdownStyle?.right || px} : {left: dropdownStyle?.left || px}),
40 | });
41 | };
42 |
43 | const dropdownWindowStyle = useMemo(() => {
44 | // minimum dropdownheight to show while keyboard is opened
45 | const minDropdownHeight = 200;
46 | const getPositionIfKeyboardIsOpened = () => {
47 | if (keyboardHeight) {
48 | if (dropdownCalculatedStyle.top && height - dropdownCalculatedStyle.top < keyboardHeight + minDropdownHeight) {
49 | return {top: height - (keyboardHeight + minDropdownHeight), minHeight: minDropdownHeight};
50 | }
51 | if (dropdownCalculatedStyle.bottom && dropdownCalculatedStyle.bottom < keyboardHeight - minDropdownHeight) {
52 | return {top: height - (keyboardHeight + minDropdownHeight), bottom: undefined, minHeight: minDropdownHeight};
53 | }
54 | return {minHeight: minDropdownHeight};
55 | }
56 | return {};
57 | };
58 |
59 | return {
60 | ...{
61 | borderTopWidth: 0,
62 | overflow: 'hidden',
63 | },
64 | ...dropdownStyle,
65 | ...dropdownCalculatedStyle,
66 | ...{
67 | position: 'absolute',
68 | height: dropdownHEIGHT,
69 | maxHeight: DROPDOWN_MAX_HEIGHT,
70 | },
71 | ...getPositionIfKeyboardIsOpened(),
72 | };
73 | }, [JSON.stringify(dropdownStyle), JSON.stringify(dropdownCalculatedStyle), keyboardHeight, dropdownHEIGHT]);
74 |
75 | const onRequestClose = () => {
76 | setIsVisible(false);
77 | };
78 |
79 | return {
80 | isVisible,
81 | setIsVisible,
82 | buttonLayout,
83 | onDropdownButtonLayout,
84 | dropdownWindowStyle,
85 | onRequestClose,
86 | };
87 | };
88 |
--------------------------------------------------------------------------------
/src/hooks/useRefs.js:
--------------------------------------------------------------------------------
1 | import {useRef} from 'react';
2 |
3 | export const useRefs = () => {
4 | const dropdownButtonRef = useRef(); // button ref to get positions
5 | const dropDownFlatlistRef = useRef(null); // ref to the drop down flatlist
6 |
7 | return {
8 | dropdownButtonRef,
9 | dropDownFlatlistRef,
10 | };
11 | };
12 |
--------------------------------------------------------------------------------
/src/hooks/useSelectDropdown.js:
--------------------------------------------------------------------------------
1 | import {useState, useEffect, useMemo} from 'react';
2 | import {deepSearchInArr} from '../helpers/deepSearchInArr';
3 | import {findIndexInArr} from '../helpers/findIndexInArr';
4 | import {isExist} from '../helpers/isExist';
5 |
6 | export const useSelectDropdown = (data, defaultValueByIndex, defaultValue, disabledInternalSearch) => {
7 | const [selectedItem, setSelectedItem] = useState(null); // selected item from dropdown
8 | const [selectedIndex, setSelectedIndex] = useState(-1); // index of selected item from dropdown
9 | const [searchTxt, setSearchTxt] = useState('');
10 |
11 | // data array changes
12 | useEffect(() => {
13 | if (!data || data.length == 0) {
14 | reset();
15 | }
16 | }, [JSON.stringify(data)]);
17 |
18 | // default value by index added or changed
19 | useEffect(() => {
20 | // defaultValueByIndex may be equals zero
21 | if (isExist(defaultValueByIndex)) {
22 | if (data && isExist(data[defaultValueByIndex])) {
23 | selectItem(defaultValueByIndex);
24 | }
25 | }
26 | }, [JSON.stringify(defaultValueByIndex)]);
27 | // default value added or changed
28 | useEffect(() => {
29 | // defaultValue may be equals zero
30 | if (isExist(defaultValue)) {
31 | if (data && findIndexInArr(defaultValue, data) >= 0) {
32 | selectItem(findIndexInArr(defaultValue, data));
33 | }
34 | }
35 | }, [JSON.stringify(defaultValue)]);
36 |
37 | const dataArr = useMemo(() => {
38 | if (disabledInternalSearch) {
39 | return data;
40 | }
41 | return searchTxt ? deepSearchInArr(searchTxt, data) : data;
42 | }, [JSON.stringify(data), searchTxt]);
43 |
44 | const selectItem = index => {
45 | setSelectedItem(data[index]);
46 | setSelectedIndex(index);
47 | };
48 |
49 | const reset = () => {
50 | setSelectedItem(null);
51 | setSelectedIndex(-1);
52 | };
53 |
54 | return {
55 | dataArr,
56 | selectedItem,
57 | selectedIndex,
58 | selectItem,
59 | reset,
60 | searchTxt,
61 | setSearchTxt,
62 | };
63 | };
64 |
--------------------------------------------------------------------------------