├── .commitlintrc.json ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .markdownlint.json ├── .npmignore ├── .vscode └── settings.json ├── .watchmanconfig ├── .yarn └── releases │ └── yarn-3.6.4.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── README^2.md ├── README^3.md ├── babel.config.js ├── example ├── .gitignore ├── App.tsx ├── Gemfile ├── Gemfile.lock ├── README.md ├── __tests__ │ └── App.test.tsx ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── autocompletedropdownplayground │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── babel.config.js ├── components │ ├── CustomRightIconExample.tsx │ ├── LocalDataSetExample.tsx │ ├── LocalDataSetExample2.tsx │ ├── ModalExample.tsx │ ├── RemoteDataSetExample.tsx │ ├── RemoteDataSetExample2.tsx │ └── RemoteDataSetExample3.tsx ├── helpers.ts ├── index.js ├── ios │ ├── .xcode.env │ ├── AutocompleteDropdownPlayground.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── AutocompleteDropdownPlayground.xcscheme │ ├── AutocompleteDropdownPlayground.xcworkspace │ │ └── contents.xcworkspacedata │ ├── AutocompleteDropdownPlayground │ │ ├── AppDelegate.swift │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ └── PrivacyInfo.xcprivacy │ ├── Podfile │ └── Podfile.lock ├── jest.config.js ├── metro.config.js ├── package.json └── tsconfig.json ├── lefthook.yml ├── package.json ├── screens ├── Example.png ├── android.gif ├── expo-example-qr-v4.png └── ios.gif ├── src ├── AutocompleteDropdownContext.tsx ├── Dropdown.tsx ├── HOC │ └── withFadeAnimation.tsx ├── NothingFound.tsx ├── RightButton.tsx ├── ScrollViewListItem.tsx ├── diacriticless.ts ├── helpers.ts ├── index.tsx ├── theme.tsx ├── types │ ├── global.d.ts │ └── index.ts └── useKeyboardHeight.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": { 4 | "type-enum": [ 5 | 2, 6 | "always", 7 | [ 8 | "build", 9 | "chore", 10 | "ci", 11 | "docs", 12 | "feat", 13 | "fix", 14 | "perf", 15 | "refactor", 16 | "revert", 17 | "style", 18 | "test", 19 | "release" 20 | ] 21 | ], 22 | "subject-case": [2, "never", ["sentence-case", "start-case", "pascal-case", "upper-case"]] 23 | } 24 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['plugin:prettier/recommended', '@react-native'], 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['react-native', 'prettier', '@typescript-eslint', 'unused-imports', 'simple-import-sort', 'import'], 6 | env: { 7 | 'react-native/react-native': true, 8 | }, 9 | parserOptions: { 10 | ecmaFeatures: { 11 | jsx: true, 12 | }, 13 | }, 14 | settings: { 15 | 'react-native/style-sheet-object-names': ['ScaledSheet'], 16 | }, 17 | rules: { 18 | 'react-native/no-unused-styles': 2, 19 | '@typescript-eslint/no-unused-vars': 'off', // or "no-unused-vars": "off", 20 | 'unused-imports/no-unused-imports': 'error', 21 | 'unused-imports/no-unused-vars': 0, 22 | semi: ['warn', 'never'], 23 | 'prettier/prettier': [ 24 | 'error', 25 | { 26 | singleQuote: true, 27 | semi: false, 28 | arrowParens: 'avoid', 29 | bracketSameLine: true, 30 | bracketSpacing: true, 31 | trailingComma: 'all', 32 | printWidth: 120, 33 | }, 34 | ], 35 | 'comma-dangle': 0, 36 | 'simple-import-sort/imports': 'off', 37 | 'simple-import-sort/exports': 'error', 38 | 'import/order': [ 39 | 'error', 40 | { 41 | pathGroups: [ 42 | { 43 | pattern: 'react', 44 | group: 'builtin', 45 | position: 'before', 46 | }, 47 | ], 48 | pathGroupsExcludedImportTypes: ['react'], 49 | }, 50 | ], 51 | 'import/first': 'error', 52 | 'import/newline-after-import': 'off', 53 | 'import/no-duplicates': 'error', 54 | 'react/display-name': 'off', 55 | '@typescript-eslint/consistent-type-imports': 'error', 56 | }, 57 | overrides: [ 58 | { 59 | files: ['*.ts', '*.tsx'], 60 | rules: { 61 | '@typescript-eslint/no-shadow': ['error'], 62 | 'no-shadow': 'off', 63 | 'no-undef': 'off', 64 | 'react-hooks/exhaustive-deps': 'warn', 65 | 'react-native/no-unused-styles': 'warn', 66 | }, 67 | }, 68 | ], 69 | } 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | # Android/IJ 33 | # 34 | .classpath 35 | .cxx 36 | .gradle 37 | .idea 38 | .project 39 | .settings 40 | local.properties 41 | android.iml 42 | 43 | # Cocoapods 44 | # 45 | example/ios/Pods 46 | 47 | # Ruby 48 | example/vendor/ 49 | 50 | # node.js 51 | # 52 | node_modules/ 53 | npm-debug.log 54 | yarn-debug.log 55 | yarn-error.log 56 | 57 | # BUCK 58 | buck-out/ 59 | \.buckd/ 60 | android/app/libs 61 | android/keystores/debug.keystore 62 | 63 | # Yarn 64 | .yarn/* 65 | !.yarn/patches 66 | !.yarn/plugins 67 | !.yarn/releases 68 | !.yarn/sdks 69 | !.yarn/versions 70 | 71 | # Expo 72 | .expo/ 73 | 74 | # Turborepo 75 | .turbo/ 76 | 77 | # generated by bob 78 | lib/ 79 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD007": { "indent": 8 }, 4 | "MD045": false, 5 | "MD013": {"line_length": 800}, 6 | "MD033": false, 7 | "no-hard-tabs": false 8 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | screens/ 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ], 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit" 9 | }, 10 | "eslint.lintTask.enable": true, 11 | "eslint.probe": [ 12 | "javascript", 13 | "javascriptreact", 14 | "typescript", 15 | "typescriptreact", 16 | "html", 17 | "markdown", 18 | "react", 19 | "tsx", 20 | "ts" 21 | ], 22 | "jest.runMode": "deferred", 23 | "prettier.requireConfig": true, 24 | "prettier.semi": false, 25 | "prettier.trailingComma": "none", 26 | "typescript.format.enable": false, 27 | "typescript.preferences.includePackageJsonAutoImports": "on" 28 | } -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | nmHoistingLimits: workspaces 3 | 4 | yarnPath: .yarn/releases/yarn-3.6.4.cjs 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alexandr Kozhevnikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-autocomplete-dropdown 2 | 3 | Dropdown Item picker with search and autocomplete (typeahead) functionality for react native 4 | 5 | [![license](https://img.shields.io/github/license/onmotion/react-native-autocomplete-dropdown)](https://img.shields.io/github/license/onmotion/react-native-autocomplete-dropdown) 6 | [![npm](https://img.shields.io/npm/v/react-native-autocomplete-dropdown.svg)](https://npmjs.com/package/react-native-autocomplete-dropdown) 7 | [![npm](https://img.shields.io/npm/dm/react-native-autocomplete-dropdown.svg)](https://npmjs.com/package/react-native-autocomplete-dropdown) 8 | 9 | > This is documentation for version 4.x, if you are looking docs for version 3.x, you can find it [here](https://github.com/onmotion/react-native-autocomplete-dropdown/blob/main/README%5E3.md) 10 | 11 |

12 | 13 |

14 | 15 | ## Demo 16 | 17 |

18 | 19 | 20 |

21 | 22 | > Run expo snack demo [@onmotion/react-native-autocomplete-dropdown](https://snack.expo.dev/@onmotion/react-native-autocomplete-dropdown-v4) 23 | 24 | or download the [Expo Go](https://expo.dev/go) app and scan the QR code below 25 | 26 | 27 | 28 | ## Nav 29 | 30 | - [react-native-autocomplete-dropdown](#react-native-autocomplete-dropdown) 31 | - [Demo](#demo) 32 | - [Nav](#nav) 33 | - [Installation](#installation) 34 | - [Post-install Steps](#post-install-steps) 35 | - [iOS](#ios) 36 | - [Android](#android) 37 | - [Usage](#usage) 38 | - [Dataset item format](#dataset-item-format) 39 | - [Example with local Dataset](#example-with-local-dataset) 40 | - [Example with remote requested Dataset](#example-with-remote-requested-dataset) 41 | - [Playground](#playground) 42 | - [Options](#options) 43 | - [Usage with a Modal](#usage-with-a-modal) 44 | 45 | ## Installation 46 | 47 | Run: 48 | 49 | or 50 | 51 | ```bash 52 | yarn add react-native-autocomplete-dropdown 53 | ``` 54 | 55 | or for v3.x 56 | 57 | ```bash 58 | yarn add react-native-autocomplete-dropdown@3.1.5 59 | ``` 60 | 61 | ## Post-install Steps 62 | 63 | Make sure **react-native-svg** is installed. Follow the guide 64 | 65 | 66 | ```bash 67 | yarn add react-native-svg 68 | ``` 69 | 70 | ### iOS 71 | 72 | Run: `npx pod-install` for install `react-native-svg` dependency (if not installed yet). 73 | 74 | ### Android 75 | 76 | No additional steps are necessary 77 | 78 | ## Usage 79 | 80 | Wrap your root component in `AutocompleteDropdownContextProvider` from `react-native-autocomplete-dropdown` as you can see in [example](https://github.com/onmotion/react-native-autocomplete-dropdown/blob/main/example/App.tsx) 81 | 82 | ```js 83 | 84 | 85 | 86 | ``` 87 | 88 | The dropdown position is relative to the `AutocompleteDropdownContextProvider`, so put this in the right place, it should cover all the screen/modal. 89 | 90 | If you have a header component, you can pass an offset. For example with react navigation 91 | 92 | ```js 93 | //... 94 | import { useHeaderHeight } from '@react-navigation/elements'; 95 | //... 96 | const headerHeight = useHeaderHeight(); 97 | //... 98 | 99 | 100 | 101 | 102 | ``` 103 | 104 | import the package 105 | 106 | ```js 107 | import { AutocompleteDropdown } from 'react-native-autocomplete-dropdown'; 108 | ``` 109 | 110 | ### Dataset item format 111 | 112 | `dataSet` property must be an **array of objects** or **null**. Object required keys are: 113 | 114 | ```js 115 | { 116 | id: 'some uniq string id', 117 | title: 'list item title' 118 | } 119 | ``` 120 | 121 | ### Example with local Dataset 122 | 123 | ```js 124 | const [selectedItem, setSelectedItem] = useState(null); 125 | 126 | ; 138 | ``` 139 | 140 | ### Example with remote requested Dataset 141 | 142 | ```js 143 | import React, { memo, useCallback, useRef, useState } from 'react' 144 | import { Button, Dimensions, Text, View, Platform } from 'react-native' 145 | import { AutocompleteDropdown } from 'react-native-autocomplete-dropdown' 146 | 147 | export const RemoteDataSetExample2 = memo(() => { 148 | const [loading, setLoading] = useState(false) 149 | const [suggestionsList, setSuggestionsList] = useState(null) 150 | const [selectedItem, setSelectedItem] = useState(null) 151 | const dropdownController = useRef(null) 152 | 153 | const searchRef = useRef(null) 154 | 155 | const getSuggestions = useCallback(async q => { 156 | const filterToken = q.toLowerCase() 157 | console.log('getSuggestions', q) 158 | if (typeof q !== 'string' || q.length < 3) { 159 | setSuggestionsList(null) 160 | return 161 | } 162 | setLoading(true) 163 | const response = await fetch('https://jsonplaceholder.typicode.com/posts') 164 | const items = await response.json() 165 | const suggestions = items 166 | .filter(item => item.title.toLowerCase().includes(filterToken)) 167 | .map(item => ({ 168 | id: item.id, 169 | title: item.title, 170 | })) 171 | setSuggestionsList(suggestions) 172 | setLoading(false) 173 | }, []) 174 | 175 | const onClearPress = useCallback(() => { 176 | setSuggestionsList(null) 177 | }, []) 178 | 179 | const onOpenSuggestionsList = useCallback(isOpened => {}, []) 180 | 181 | return ( 182 | <> 183 | 188 | { 191 | dropdownController.current = controller 192 | }} 193 | // initialValue={'1'} 194 | direction={Platform.select({ ios: 'down' })} 195 | dataSet={suggestionsList} 196 | onChangeText={getSuggestions} 197 | onSelectItem={item => { 198 | item && setSelectedItem(item.id) 199 | }} 200 | debounce={600} 201 | suggestionsListMaxHeight={Dimensions.get('window').height * 0.4} 202 | onClear={onClearPress} 203 | // onSubmit={(e) => onSubmitSearch(e.nativeEvent.text)} 204 | onOpenSuggestionsList={onOpenSuggestionsList} 205 | loading={loading} 206 | useFilter={false} // set false to prevent rerender twice 207 | textInputProps={{ 208 | placeholder: 'Type 3+ letters (dolo...)', 209 | autoCorrect: false, 210 | autoCapitalize: 'none', 211 | style: { 212 | borderRadius: 25, 213 | backgroundColor: '#383b42', 214 | color: '#fff', 215 | paddingLeft: 18, 216 | }, 217 | }} 218 | rightButtonsContainerStyle={{ 219 | right: 8, 220 | height: 30, 221 | 222 | alignSelf: 'center', 223 | }} 224 | inputContainerStyle={{ 225 | backgroundColor: '#383b42', 226 | borderRadius: 25, 227 | }} 228 | suggestionsListContainerStyle={{ 229 | backgroundColor: '#383b42', 230 | }} 231 | containerStyle={{ flexGrow: 1, flexShrink: 1 }} 232 | renderItem={(item, text) => {item.title}} 233 | // ChevronIconComponent={} 234 | // ClearIconComponent={} 235 | inputHeight={50} 236 | showChevron={false} 237 | closeOnBlur={false} 238 | // showClear={false} 239 | /> 240 | 241 |