├── .editorconfig
├── .gitattributes
├── .github
├── actions
│ └── setup
│ │ └── action.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .nvmrc
├── .watchmanconfig
├── .yarnrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.js
├── example
├── App.js
├── app.json
├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
├── babel.config.js
├── metro.config.js
├── package.json
├── src
│ ├── App.tsx
│ └── dropdown
│ │ ├── CountrySelect1.tsx
│ │ ├── CountrySelect2.tsx
│ │ ├── Dropdown1.tsx
│ │ ├── Dropdown2.tsx
│ │ ├── DropdownLazyLoad.tsx
│ │ ├── DropdownWithConfirm.tsx
│ │ ├── Menu.tsx
│ │ ├── MultiSelectAll.tsx
│ │ └── MultiSelectWithConfirm.tsx
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
├── lefthook.yml
├── package.json
├── scripts
└── bootstrap.js
├── src
├── __tests__
│ └── index.test.tsx
├── assets
│ ├── close.png
│ └── down.png
├── components
│ ├── Dropdown
│ │ ├── index.tsx
│ │ ├── model.ts
│ │ └── styles.ts
│ ├── MultiSelect
│ │ ├── index.tsx
│ │ ├── model.ts
│ │ └── styles.ts
│ ├── SelectCountry
│ │ ├── index.tsx
│ │ ├── model.ts
│ │ └── styles.ts
│ └── TextInput
│ │ ├── index.tsx
│ │ ├── model.ts
│ │ └── styles.ts
├── index.tsx
├── toolkits
│ ├── index.ts
│ └── model.ts
└── useDeviceOrientation.ts
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | description: Setup Node.js and install dependencies
3 |
4 | runs:
5 | using: composite
6 | steps:
7 | - name: Setup Node.js
8 | uses: actions/setup-node@v3
9 | with:
10 | node-version-file: .nvmrc
11 |
12 | - name: Cache dependencies
13 | id: yarn-cache
14 | uses: actions/cache@v3
15 | with:
16 | path: |
17 | **/node_modules
18 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
19 | restore-keys: |
20 | ${{ runner.os }}-yarn-
21 |
22 | - name: Install dependencies
23 | if: steps.yarn-cache.outputs.cache-hit != 'true'
24 | run: |
25 | yarn install --cwd example --frozen-lockfile
26 | yarn install --frozen-lockfile
27 | shell: bash
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | lint:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3
16 |
17 | - name: Setup
18 | uses: ./.github/actions/setup
19 |
20 | - name: Lint files
21 | run: yarn lint
22 |
23 | - name: Typecheck files
24 | run: yarn typecheck
25 |
26 | test:
27 | runs-on: ubuntu-latest
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@v3
31 |
32 | - name: Setup
33 | uses: ./.github/actions/setup
34 |
35 | - name: Run unit tests
36 | run: yarn test --maxWorkers=2 --coverage
37 |
38 | build:
39 | runs-on: ubuntu-latest
40 | steps:
41 | - name: Checkout
42 | uses: actions/checkout@v3
43 |
44 | - name: Setup
45 | uses: ./.github/actions/setup
46 |
47 | - name: Build package
48 | run: yarn prepack
49 |
--------------------------------------------------------------------------------
/.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 | # Expo
64 | .expo/
65 |
66 | # Turborepo
67 | .turbo/
68 |
69 | # generated by bob
70 | lib/
71 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.18.1
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | # Override Yarn command so we can automatically setup the repo on running `yarn`
2 |
3 | yarn-path "scripts/bootstrap.js"
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, caste, color, religion, or sexual
11 | identity and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the overall
27 | community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or advances of
32 | any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email address,
36 | without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | [INSERT CONTACT METHOD].
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series of
87 | actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or permanent
94 | ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within the
114 | community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.1, available at
120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121 |
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127 | [https://www.contributor-covenant.org/translations][translations].
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome, no matter how large or small!
4 |
5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
6 |
7 | ## Development workflow
8 |
9 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
10 |
11 | ```sh
12 | yarn
13 | ```
14 |
15 | > While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development.
16 |
17 | While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.
18 |
19 | To start the packager:
20 |
21 | ```sh
22 | yarn example start
23 | ```
24 |
25 | To run the example app on Android:
26 |
27 | ```sh
28 | yarn example android
29 | ```
30 |
31 | To run the example app on iOS:
32 |
33 | ```sh
34 | yarn example ios
35 | ```
36 |
37 | To run the example app on Web:
38 |
39 | ```sh
40 | yarn example web
41 | ```
42 |
43 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
44 |
45 | ```sh
46 | yarn typecheck
47 | yarn lint
48 | ```
49 |
50 | To fix formatting errors, run the following:
51 |
52 | ```sh
53 | yarn lint --fix
54 | ```
55 |
56 | Remember to add tests for your change if possible. Run the unit tests by:
57 |
58 | ```sh
59 | yarn test
60 | ```
61 |
62 |
63 | ### Commit message convention
64 |
65 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
66 |
67 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
68 | - `feat`: new features, e.g. add new method to the module.
69 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
70 | - `docs`: changes into documentation, e.g. add usage example for the module..
71 | - `test`: adding or updating tests, e.g. add integration tests using detox.
72 | - `chore`: tooling changes, e.g. change CI config.
73 |
74 | Our pre-commit hooks verify that your commit message matches this format when committing.
75 |
76 | ### Linting and tests
77 |
78 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
79 |
80 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
81 |
82 | Our pre-commit hooks verify that the linter and tests pass when committing.
83 |
84 | ### Publishing to npm
85 |
86 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
87 |
88 | To publish new versions, run the following:
89 |
90 | ```sh
91 | yarn release
92 | ```
93 |
94 | ### Scripts
95 |
96 | The `package.json` file contains various scripts for common tasks:
97 |
98 | - `yarn bootstrap`: setup project by installing all dependencies and pods.
99 | - `yarn typecheck`: type-check files with TypeScript.
100 | - `yarn lint`: lint files with ESLint.
101 | - `yarn test`: run unit tests with Jest.
102 | - `yarn example start`: start the Metro server for the example app.
103 | - `yarn example android`: run the example app on Android.
104 | - `yarn example ios`: run the example app on iOS.
105 |
106 | ### Sending a pull request
107 |
108 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
109 |
110 | When you're sending a pull request:
111 |
112 | - Prefer small pull requests focused on one change.
113 | - Verify that linters and tests are passing.
114 | - Review the documentation to make sure it looks good.
115 | - Follow the pull request template when opening a pull request.
116 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
117 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Hoa Phan
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [
](https://github.com/hoaphantn7604/file-upload/blob/master/document/dropdown/react-native-element-dropdown-demo.png)
2 |
3 | # react-native-element-dropdown
4 | React Native Element Dropdown is a library that provides a customizable dropdown component for React Native applications. This library simplifies the process of creating dropdown menus and provides a variety of options to customize the dropdown to match the design and functionality of your application.
5 |
6 | ## Features
7 | * Dropdown and Multiselect in one package
8 | * Easy to use
9 | * Consistent look and feel on iOS and Android
10 | * Customizable font size, colors and animation duration
11 | * Implemented with typescript
12 |
13 | ```js
14 | If you love this library, give us a star, you will be a ray of sunshine in our lives :)
15 | ```
16 |
17 | ### Free React Native Boilerplate
18 | [React Native Template](https://github.com/hoaphantn7604/react-native-template-components) with a beautiful UI.
19 |
20 | ## Getting started
21 | ```js
22 | npm install react-native-element-dropdown --save
23 | ```
24 | or
25 |
26 | ```js
27 | yarn add react-native-element-dropdown
28 | ```
29 | ### Demo
30 |
31 | [
](https://youtu.be/FhTDR_Ad_14)
32 |
33 |
34 |
35 |
36 | 
37 | 
38 |
39 | ### Dropdown Props
40 | | Props | Params | isRequire | Description |
41 | | ------------------ | ----------------------------------------------- | --------- | ------------------------------------------------------------------- |
42 | | mode | 'default' or 'modal' of 'auto' | No | Mode 'modal' is show the dropdown in the middle of the screen. |
43 | | data | Array | Yes | Data is a plain array |
44 | | labelField | String | Yes | Extract the label from the data item |
45 | | valueField | String | Yes | Extract the primary key from the data item |
46 | | searchField | String | No | Specify the field of data list you want to search |
47 | | onChange | (item: object) => void | Yes | Selection callback |
48 | | onChangeText | (search: string) => void | No | Callback that is called when the text input's text changes |
49 | | value | Item | No | Set default value |
50 | | placeholder | String | No | The string that will be rendered before dropdown has been selected |
51 | | placeholderStyle | TextStyle | No | Styling for text placeholder |
52 | | selectedTextStyle | TextStyle | No | Styling for selected text |
53 | | selectedTextProps | TextProps | No | Text Props for selected text. Ex: numberOfLines={1} |
54 | | style | ViewStyle | No | Styling for view container |
55 | | containerStyle | ViewStyle | No | Styling for list container |
56 | | maxHeight | Number | No | Customize max height for list container |
57 | | minHeight | Number | No | Customize min height for list container |
58 | | fontFamily | String | No | Customize font style |
59 | | iconStyle | ImageStyle | No | Styling for icon |
60 | | iconColor | String | No | Color of icons |
61 | | itemContainerStyle | TextStyle | No | Styling for item container in list |
62 | | itemTextStyle | TextStyle | No | Styling for text item in list |
63 | | activeColor | String | No | Background color for item selected in list container |
64 | | search | Boolean | No | Show or hide input search |
65 | | searchQuery | (keyword: string, labelValue: string) => Boolean| No | Callback used to filter the list of items |
66 | | inputSearchStyle | ViewStyle | No | Styling for input search |
67 | | searchPlaceholder | String | No | The string that will be rendered before text input has been entered |
68 | | searchPlaceholderTextColor | String | No | The text color of the placeholder string |
69 | | renderInputSearch | (onSearch: (text:string) => void) => JSX.Element| No | Customize TextInput search |
70 | | disable | Boolean | No | Specifies the disabled state of the Dropdown |
71 | | dropdownPosition | 'auto' or 'top' or 'bottom' | No | Dropdown list position. Default is 'auto' |
72 | | autoScroll | Boolean | No | Auto scroll to index item selected, default is true |
73 | | showsVerticalScrollIndicator | Boolean | No | When true, shows a vertical scroll indicator, default is true |
74 | | renderLeftIcon | (visible?: boolean) => JSX.Element | No | Customize left icon for dropdown |
75 | | renderRightIcon | (visible?: boolean) => JSX.Element | No | Customize right icon for dropdown |
76 | | renderItem | (item: object, selected: Boolean) => JSX.Element| No | Takes an item from data and renders it into the list |
77 | | flatListProps | FlatListProps | No | Customize FlatList element |
78 | | inverted | Boolean | No | Reverses the direction of scroll on top position mode. Default is true|
79 | | onFocus | () => void | No | Callback that is called when the dropdown is focused |
80 | | onBlur | () => void | No | Callback that is called when the dropdown is blurred |
81 | | keyboardAvoiding | Boolean | No | keyboardAvoiding default is true |
82 | | backgroundColor | String | No | Set background color |
83 | | confirmSelectItem | Boolean | No | Turn On confirm select item. Refer example/src/dropdown/example3 |
84 | | onConfirmSelectItem | (item: object) => void | No | Selection callback. Refer example/src/dropdown/example3 |
85 | | testID | String | No | Used to locate this view in end-to-end tests |
86 | | itemTestIDField | String | No | Add this field to the input data. Ex: DATA = [{itemTestIDField: '', label: '', value:: ''}]|
87 | | accessibilityLabel | String | No | Set an accessibilityLabel on the view, so that people who use VoiceOver know what element they have selected |
88 | | itemAccessibilityLabelField | String | No | Add this field to the input data. Ex: DATA = [{itemAccessibilityLabelField: '', label: '', value:: ''}]|
89 | | closeModalWhenSelectedItem | Boolean | No | By default, closeModalWhenSelectedItem is set to true. When closeModalWhenSelectedItem is set to false, the Modal won't close when an item is selected. |
90 | | excludeItems | Item[] | No | The array containing the items to be excluded. |
91 | | excludeSearchItems | Item[] | No | The array containing the items to be excluded. |
92 |
93 |
94 |
95 | ### MultiSelect Props
96 | | Props | Params | isRequire | Description |
97 | | ------------------ | -----------------------------------------------------| --------- | ------------------------------------------------------------------- |
98 | | mode | 'default' or 'modal' of 'auto' | No | Mode 'modal' is show the dropdown in the middle of the screen. |
99 | | data | Array | Yes | Data is a plain array |
100 | | labelField | String | Yes | Extract the label from the data item |
101 | | valueField | String | Yes | Extract the primary key from the data item |
102 | | searchField | String | No | Specify the field of data list you want to search |
103 | | onChange | (value[]) => void | Yes | Selection callback. A array containing the "valueField". |
104 | | onChangeText | (search: string) => void | No | Callback that is called when the text input's text changes |
105 | | value | Item[] | No | Set default value. A array containing the "valueField". |
106 | | placeholder | String | No | The string that will be rendered before dropdown has been selected |
107 | | placeholderStyle | TextStyle | No | Styling for text placeholder |
108 | | style | ViewStyle | No | Styling for view container |
109 | | containerStyle | ViewStyle | No | Styling for list container |
110 | | maxHeight | Number | No | Customize max height for list container |
111 | | minHeight | Number | No | Customize min height for list container |
112 | | maxSelect | Number | No | maximum number of items that can be selected |
113 | | fontFamily | String | No | Customize font style |
114 | | iconStyle | ImageStyle | No | Styling for icon |
115 | | iconColor | String | No | Color of icons |
116 | | activeColor | String | No | Background color for item selected in list container |
117 | | itemContainerStyle | TextStyle | No | Styling for item container in list |
118 | | itemTextStyle | TextStyle | No | Styling for text item in list |
119 | | selectedStyle | ViewStyle | No | Styling for selected view |
120 | | selectedTextStyle | TextStyle | No | Styling for selected text |
121 | | selectedTextProps | TextProps | No | Text Props for selected text. Ex: numberOfLines={1} |
122 | | renderSelectedItem | (item: object, unSelect?: () => void) => JSX.Element | No | Takes an item from data and renders it into the list selected |
123 | | alwaysRenderSelectedItem | Boolean | No | Always show the list of selected items |
124 | | visibleSelectedItem | Boolean | No | Option to hide selected item list, Ẽx: visibleSelectedItem={false} |
125 | | search | Boolean | No | Show or hide input search |
126 | | searchQuery | (keyword: string, labelValue: string) => Boolean | No | Callback used to filter the list of items |
127 | | inputSearchStyle | ViewStyle | No | Styling for input search |
128 | | searchPlaceholder | String | No | The string that will be rendered before text input has been entered |
129 | | searchPlaceholderTextColor | String | No | The text color of the placeholder string |
130 | | renderInputSearch | (onSearch: (text:string) => void) => JSX.Element | No | Customize TextInput search |
131 | | disable | Boolean | No | Specifies the disabled state of the Dropdown |
132 | | dropdownPosition | 'auto' or 'top' or 'bottom' | No | Dropdown list position. Default is 'auto' |
133 | | showsVerticalScrollIndicator | Boolean | No | When true, shows a vertical scroll indicator, default is true |
134 | | renderLeftIcon | (visible?: boolean) => JSX.Element | No | Customize left icon for dropdown |
135 | | renderRightIcon | (visible?: boolean) => JSX.Element | No | Customize right icon for dropdown |
136 | | renderItem | (item: object, selected: Boolean) => JSX.Element | No | Takes an item from data and renders it into the list |
137 | | flatListProps | FlatListProps | No | Customize FlatList element |
138 | | inverted | Boolean | No | Reverses the direction of scroll on top position mode. Default is true|
139 | | onFocus | () => void | No | Callback that is called when the dropdown is focused |
140 | | onBlur | () => void | No | Callback that is called when the dropdown is blurred |
141 | | keyboardAvoiding | Boolean | No | keyboardAvoiding default is true |
142 | | inside | Boolean | No | inside default is false |
143 | | backgroundColor | String | No | Set background color |
144 | | confirmSelectItem | Boolean | No | Turn On confirm select item. Refer example/src/dropdown/example7 |
145 | | confirmUnSelectItem | Boolean | No | Turn On confirm un-select item. Refer example/src/dropdown/example7 |
146 | | onConfirmSelectItem | (item: any) => void | No | Selection callback. Refer example/src/dropdown/example7 |
147 | | testID | String | No | Used to locate this view in end-to-end tests |
148 | | itemTestIDField | String | No | Add this field to the input data. Ex: DATA = [{itemTestIDField: '', label: '', value:: ''}]|
149 | | accessibilityLabel | String | No | Set an accessibilityLabel on the view, so that people who use VoiceOver know what element they have selected |
150 | | itemAccessibilityLabelField | String | No | Add this field to the input data. Ex: DATA = [{itemAccessibilityLabelField: '', label: '', value:: ''}]|
151 | | excludeItems | Item[] | No | The array containing the items to be excluded. |
152 | | excludeSearchItems | Item[] | No | The array containing the items to be excluded. |
153 |
154 |
155 |
156 | ### SelectCountry extends Dropdown
157 | | Props | Params | isRequire | Description |
158 | | ------------------ | -------------------- | --------- | ------------------------------------ |
159 | | imageField | String | Yes | Extract the image from the data item |
160 | | imageStyle | ImageStyle | No | Styling for image |
161 |
162 |
163 | ### Method
164 | | API | Params | Description |
165 | | ----------- | ------------| ---------------------|
166 | | open | () => void | Open dropdown list |
167 | | close | () => void | Close dropdown list |
168 |
169 |
170 | ### Dropdown example
171 | 
172 | ```js
173 | import React, { useState } from 'react';
174 | import { StyleSheet, Text, View } from 'react-native';
175 | import { Dropdown } from 'react-native-element-dropdown';
176 | import AntDesign from '@expo/vector-icons/AntDesign';
177 |
178 | const data = [
179 | { label: 'Item 1', value: '1' },
180 | { label: 'Item 2', value: '2' },
181 | { label: 'Item 3', value: '3' },
182 | { label: 'Item 4', value: '4' },
183 | { label: 'Item 5', value: '5' },
184 | { label: 'Item 6', value: '6' },
185 | { label: 'Item 7', value: '7' },
186 | { label: 'Item 8', value: '8' },
187 | ];
188 |
189 | const DropdownComponent = () => {
190 | const [value, setValue] = useState(null);
191 | const [isFocus, setIsFocus] = useState(false);
192 |
193 | const renderLabel = () => {
194 | if (value || isFocus) {
195 | return (
196 |
197 | Dropdown label
198 |
199 | );
200 | }
201 | return null;
202 | };
203 |
204 | return (
205 |
206 | {renderLabel()}
207 | setIsFocus(true)}
222 | onBlur={() => setIsFocus(false)}
223 | onChange={item => {
224 | setValue(item.value);
225 | setIsFocus(false);
226 | }}
227 | renderLeftIcon={() => (
228 |
234 | )}
235 | />
236 |
237 | );
238 | };
239 |
240 | export default DropdownComponent;
241 |
242 | const styles = StyleSheet.create({
243 | container: {
244 | backgroundColor: 'white',
245 | padding: 16,
246 | },
247 | dropdown: {
248 | height: 50,
249 | borderColor: 'gray',
250 | borderWidth: 0.5,
251 | borderRadius: 8,
252 | paddingHorizontal: 8,
253 | },
254 | icon: {
255 | marginRight: 5,
256 | },
257 | label: {
258 | position: 'absolute',
259 | backgroundColor: 'white',
260 | left: 22,
261 | top: 8,
262 | zIndex: 999,
263 | paddingHorizontal: 8,
264 | fontSize: 14,
265 | },
266 | placeholderStyle: {
267 | fontSize: 16,
268 | },
269 | selectedTextStyle: {
270 | fontSize: 16,
271 | },
272 | iconStyle: {
273 | width: 20,
274 | height: 20,
275 | },
276 | inputSearchStyle: {
277 | height: 40,
278 | fontSize: 16,
279 | },
280 | });
281 | ```
282 |
283 | ### Dropdown example 1
284 | 
285 | ```javascript
286 | import React, { useState } from 'react';
287 | import { StyleSheet } from 'react-native';
288 | import { Dropdown } from 'react-native-element-dropdown';
289 | import AntDesign from '@expo/vector-icons/AntDesign';
290 |
291 | const data = [
292 | { label: 'Item 1', value: '1' },
293 | { label: 'Item 2', value: '2' },
294 | { label: 'Item 3', value: '3' },
295 | { label: 'Item 4', value: '4' },
296 | { label: 'Item 5', value: '5' },
297 | { label: 'Item 6', value: '6' },
298 | { label: 'Item 7', value: '7' },
299 | { label: 'Item 8', value: '8' },
300 | ];
301 |
302 | const DropdownComponent = () => {
303 | const [value, setValue] = useState(null);
304 |
305 | return (
306 | {
321 | setValue(item.value);
322 | }}
323 | renderLeftIcon={() => (
324 |
325 | )}
326 | />
327 | );
328 | };
329 |
330 | export default DropdownComponent;
331 |
332 | const styles = StyleSheet.create({
333 | dropdown: {
334 | margin: 16,
335 | height: 50,
336 | borderBottomColor: 'gray',
337 | borderBottomWidth: 0.5,
338 | },
339 | icon: {
340 | marginRight: 5,
341 | },
342 | placeholderStyle: {
343 | fontSize: 16,
344 | },
345 | selectedTextStyle: {
346 | fontSize: 16,
347 | },
348 | iconStyle: {
349 | width: 20,
350 | height: 20,
351 | },
352 | inputSearchStyle: {
353 | height: 40,
354 | fontSize: 16,
355 | },
356 | });
357 | ```
358 |
359 | ### Dropdown example 2
360 | 
361 | ```javascript
362 | import React, { useState } from 'react';
363 | import { StyleSheet, View, Text } from 'react-native';
364 | import { Dropdown } from 'react-native-element-dropdown';
365 | import AntDesign from '@expo/vector-icons/AntDesign';
366 |
367 | const data = [
368 | { label: 'Item 1', value: '1' },
369 | { label: 'Item 2', value: '2' },
370 | { label: 'Item 3', value: '3' },
371 | { label: 'Item 4', value: '4' },
372 | { label: 'Item 5', value: '5' },
373 | { label: 'Item 6', value: '6' },
374 | { label: 'Item 7', value: '7' },
375 | { label: 'Item 8', value: '8' },
376 | ];
377 |
378 | const DropdownComponent = () => {
379 | const [value, setValue] = useState(null);
380 |
381 | const renderItem = item => {
382 | return (
383 |
384 | {item.label}
385 | {item.value === value && (
386 |
392 | )}
393 |
394 | );
395 | };
396 |
397 | return (
398 | {
413 | setValue(item.value);
414 | }}
415 | renderLeftIcon={() => (
416 |
417 | )}
418 | renderItem={renderItem}
419 | />
420 | );
421 | };
422 |
423 | export default DropdownComponent;
424 |
425 | const styles = StyleSheet.create({
426 | dropdown: {
427 | margin: 16,
428 | height: 50,
429 | backgroundColor: 'white',
430 | borderRadius: 12,
431 | padding: 12,
432 | shadowColor: '#000',
433 | shadowOffset: {
434 | width: 0,
435 | height: 1,
436 | },
437 | shadowOpacity: 0.2,
438 | shadowRadius: 1.41,
439 |
440 | elevation: 2,
441 | },
442 | icon: {
443 | marginRight: 5,
444 | },
445 | item: {
446 | padding: 17,
447 | flexDirection: 'row',
448 | justifyContent: 'space-between',
449 | alignItems: 'center',
450 | },
451 | textItem: {
452 | flex: 1,
453 | fontSize: 16,
454 | },
455 | placeholderStyle: {
456 | fontSize: 16,
457 | },
458 | selectedTextStyle: {
459 | fontSize: 16,
460 | },
461 | iconStyle: {
462 | width: 20,
463 | height: 20,
464 | },
465 | inputSearchStyle: {
466 | height: 40,
467 | fontSize: 16,
468 | },
469 | });
470 | ```
471 |
472 | ### MultiSelect example 1
473 | 
474 | ```js
475 | import React, { useState } from 'react';
476 | import { StyleSheet, View } from 'react-native';
477 | import { MultiSelect } from 'react-native-element-dropdown';
478 | import AntDesign from '@expo/vector-icons/AntDesign';
479 |
480 | const data = [
481 | { label: 'Item 1', value: '1' },
482 | { label: 'Item 2', value: '2' },
483 | { label: 'Item 3', value: '3' },
484 | { label: 'Item 4', value: '4' },
485 | { label: 'Item 5', value: '5' },
486 | { label: 'Item 6', value: '6' },
487 | { label: 'Item 7', value: '7' },
488 | { label: 'Item 8', value: '8' },
489 | ];
490 |
491 | const MultiSelectComponent = () => {
492 | const [selected, setSelected] = useState([]);
493 |
494 | return (
495 |
496 | {
510 | setSelected(item);
511 | }}
512 | renderLeftIcon={() => (
513 |
519 | )}
520 | selectedStyle={styles.selectedStyle}
521 | />
522 |
523 | );
524 | };
525 |
526 | export default MultiSelectComponent;
527 |
528 | const styles = StyleSheet.create({
529 | container: { padding: 16 },
530 | dropdown: {
531 | height: 50,
532 | backgroundColor: 'transparent',
533 | borderBottomColor: 'gray',
534 | borderBottomWidth: 0.5,
535 | },
536 | placeholderStyle: {
537 | fontSize: 16,
538 | },
539 | selectedTextStyle: {
540 | fontSize: 14,
541 | },
542 | iconStyle: {
543 | width: 20,
544 | height: 20,
545 | },
546 | inputSearchStyle: {
547 | height: 40,
548 | fontSize: 16,
549 | },
550 | icon: {
551 | marginRight: 5,
552 | },
553 | selectedStyle: {
554 | borderRadius: 12,
555 | },
556 | });
557 | ```
558 |
559 | ### MultiSelect example 2
560 | 
561 | ```js
562 | import React, { useState } from 'react';
563 | import { StyleSheet, View, TouchableOpacity, Text } from 'react-native';
564 | import { MultiSelect } from 'react-native-element-dropdown';
565 | import AntDesign from '@expo/vector-icons/AntDesign';
566 |
567 | const data = [
568 | { label: 'Item 1', value: '1' },
569 | { label: 'Item 2', value: '2' },
570 | { label: 'Item 3', value: '3' },
571 | { label: 'Item 4', value: '4' },
572 | { label: 'Item 5', value: '5' },
573 | { label: 'Item 6', value: '6' },
574 | { label: 'Item 7', value: '7' },
575 | { label: 'Item 8', value: '8' },
576 | ];
577 |
578 | const MultiSelectComponent = () => {
579 | const [selected, setSelected] = useState([]);
580 |
581 | const renderItem = item => {
582 | return (
583 |
584 | {item.label}
585 |
586 |
587 | );
588 | };
589 |
590 | return (
591 |
592 | {
606 | setSelected(item);
607 | }}
608 | renderLeftIcon={() => (
609 |
615 | )}
616 | renderItem={renderItem}
617 | renderSelectedItem={(item, unSelect) => (
618 | unSelect && unSelect(item)}>
619 |
620 | {item.label}
621 |
622 |
623 |
624 | )}
625 | />
626 |
627 | );
628 | };
629 |
630 | export default MultiSelectComponent;
631 |
632 | const styles = StyleSheet.create({
633 | container: { padding: 16 },
634 | dropdown: {
635 | height: 50,
636 | backgroundColor: 'white',
637 | borderRadius: 12,
638 | padding: 12,
639 | shadowColor: '#000',
640 | shadowOffset: {
641 | width: 0,
642 | height: 1,
643 | },
644 | shadowOpacity: 0.2,
645 | shadowRadius: 1.41,
646 |
647 | elevation: 2,
648 | },
649 | placeholderStyle: {
650 | fontSize: 16,
651 | },
652 | selectedTextStyle: {
653 | fontSize: 14,
654 | },
655 | iconStyle: {
656 | width: 20,
657 | height: 20,
658 | },
659 | inputSearchStyle: {
660 | height: 40,
661 | fontSize: 16,
662 | },
663 | icon: {
664 | marginRight: 5,
665 | },
666 | item: {
667 | padding: 17,
668 | flexDirection: 'row',
669 | justifyContent: 'space-between',
670 | alignItems: 'center',
671 | },
672 | selectedStyle: {
673 | flexDirection: 'row',
674 | justifyContent: 'center',
675 | alignItems: 'center',
676 | borderRadius: 14,
677 | backgroundColor: 'white',
678 | shadowColor: '#000',
679 | marginTop: 8,
680 | marginRight: 12,
681 | paddingHorizontal: 12,
682 | paddingVertical: 8,
683 | shadowOffset: {
684 | width: 0,
685 | height: 1,
686 | },
687 | shadowOpacity: 0.2,
688 | shadowRadius: 1.41,
689 |
690 | elevation: 2,
691 | },
692 | textSelectedStyle: {
693 | marginRight: 5,
694 | fontSize: 16,
695 | },
696 | });
697 | ```
698 |
699 | ### SelectCountry example 1
700 | 
701 | ```js
702 | import React, { useState } from 'react';
703 | import { StyleSheet } from 'react-native';
704 | import { SelectCountry } from 'react-native-element-dropdown';
705 |
706 | const local_data = [
707 | {
708 | value: '1',
709 | lable: 'Country 1',
710 | image: {
711 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
712 | },
713 | },
714 | {
715 | value: '2',
716 | lable: 'Country 2',
717 | image: {
718 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
719 | },
720 | },
721 | {
722 | value: '3',
723 | lable: 'Country 3',
724 | image: {
725 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
726 | },
727 | },
728 | {
729 | value: '4',
730 | lable: 'Country 4',
731 | image: {
732 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
733 | },
734 | },
735 | {
736 | value: '5',
737 | lable: 'Country 5',
738 | image: {
739 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
740 | },
741 | },
742 | ];
743 |
744 | const SelectCountryScreen = _props => {
745 | const [country, setCountry] = useState('1');
746 |
747 | return (
748 | {
765 | setCountry(e.value);
766 | }}
767 | />
768 | );
769 | };
770 |
771 | export default SelectCountryScreen;
772 |
773 | const styles = StyleSheet.create({
774 | dropdown: {
775 | margin: 16,
776 | height: 50,
777 | borderBottomColor: 'gray',
778 | borderBottomWidth: 0.5,
779 | },
780 | imageStyle: {
781 | width: 24,
782 | height: 24,
783 | },
784 | placeholderStyle: {
785 | fontSize: 16,
786 | },
787 | selectedTextStyle: {
788 | fontSize: 16,
789 | marginLeft: 8,
790 | },
791 | iconStyle: {
792 | width: 20,
793 | height: 20,
794 | },
795 | inputSearchStyle: {
796 | height: 40,
797 | fontSize: 16,
798 | },
799 | });
800 | ```
801 |
802 | ### SelectCountry example 2
803 | 
804 | ```js
805 | import React, { useState } from 'react';
806 | import { StyleSheet } from 'react-native';
807 | import { SelectCountry } from 'react-native-element-dropdown';
808 |
809 | const local_data = [
810 | {
811 | value: '1',
812 | lable: 'Country 1',
813 | image: {
814 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
815 | },
816 | },
817 | {
818 | value: '2',
819 | lable: 'Country 2',
820 | image: {
821 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
822 | },
823 | },
824 | {
825 | value: '3',
826 | lable: 'Country 3',
827 | image: {
828 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
829 | },
830 | },
831 | {
832 | value: '4',
833 | lable: 'Country 4',
834 | image: {
835 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
836 | },
837 | },
838 | {
839 | value: '5',
840 | lable: 'Country 5',
841 | image: {
842 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
843 | },
844 | },
845 | ];
846 |
847 | const SelectCountryScreen = _props => {
848 | const [country, setCountry] = useState('1');
849 |
850 | return (
851 | {
866 | setCountry(e.value);
867 | }}
868 | />
869 | );
870 | };
871 |
872 | export default SelectCountryScreen;
873 |
874 | const styles = StyleSheet.create({
875 | dropdown: {
876 | margin: 16,
877 | height: 50,
878 | width: 150,
879 | backgroundColor: '#EEEEEE',
880 | borderRadius: 22,
881 | paddingHorizontal: 8,
882 | },
883 | imageStyle: {
884 | width: 24,
885 | height: 24,
886 | borderRadius: 12,
887 | },
888 | placeholderStyle: {
889 | fontSize: 16,
890 | },
891 | selectedTextStyle: {
892 | fontSize: 16,
893 | marginLeft: 8,
894 | },
895 | iconStyle: {
896 | width: 20,
897 | height: 20,
898 | },
899 | });
900 | ```
901 |
902 |
903 |
904 | [
](https://github.com/hoaphantn7604)
905 |
906 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | export { default } from './src/App';
2 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "example",
4 | "slug": "example",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true
19 | },
20 | "android": {
21 | "adaptiveIcon": {
22 | "foregroundImage": "./assets/adaptive-icon.png",
23 | "backgroundColor": "#ffffff"
24 | }
25 | },
26 | "web": {
27 | "favicon": "./assets/favicon.png"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoaphantn7604/react-native-element-dropdown/5787e1f33f7ca890322457b1ee2dcd2e68e78ed1/example/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/example/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoaphantn7604/react-native-element-dropdown/5787e1f33f7ca890322457b1ee2dcd2e68e78ed1/example/assets/favicon.png
--------------------------------------------------------------------------------
/example/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoaphantn7604/react-native-element-dropdown/5787e1f33f7ca890322457b1ee2dcd2e68e78ed1/example/assets/icon.png
--------------------------------------------------------------------------------
/example/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoaphantn7604/react-native-element-dropdown/5787e1f33f7ca890322457b1ee2dcd2e68e78ed1/example/assets/splash.png
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pak = require('../package.json');
3 |
4 | module.exports = function (api) {
5 | api.cache(true);
6 |
7 | return {
8 | presets: ['babel-preset-expo'],
9 | plugins: [
10 | [
11 | 'module-resolver',
12 | {
13 | extensions: ['.tsx', '.ts', '.js', '.json'],
14 | alias: {
15 | // For development, we want to alias the library to the source
16 | [pak.name]: path.join(__dirname, '..', pak.source),
17 | },
18 | },
19 | ],
20 | ],
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const escape = require('escape-string-regexp');
3 | const { getDefaultConfig } = require('@expo/metro-config');
4 | const exclusionList = require('metro-config/src/defaults/exclusionList');
5 | const pak = require('../package.json');
6 |
7 | const root = path.resolve(__dirname, '..');
8 |
9 | const modules = Object.keys({
10 | ...pak.peerDependencies,
11 | });
12 |
13 | const defaultConfig = getDefaultConfig(__dirname);
14 |
15 | module.exports = {
16 | ...defaultConfig,
17 |
18 | projectRoot: __dirname,
19 | watchFolders: [root],
20 |
21 | // We need to make sure that only one version is loaded for peerDependencies
22 | // So we block them at the root, and alias them to the versions in example's node_modules
23 | resolver: {
24 | ...defaultConfig.resolver,
25 |
26 | blacklistRE: exclusionList(
27 | modules.map(
28 | (m) =>
29 | new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)
30 | )
31 | ),
32 |
33 | extraNodeModules: modules.reduce((acc, name) => {
34 | acc[name] = path.join(__dirname, 'node_modules', name);
35 | return acc;
36 | }, {}),
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "@expo/vector-icons": "^13.0.0",
13 | "expo": "~48.0.6",
14 | "expo-status-bar": "~1.4.4",
15 | "react": "18.2.0",
16 | "react-dom": "18.2.0",
17 | "react-native": "0.71.3",
18 | "react-native-web": "~0.18.10"
19 | },
20 | "devDependencies": {
21 | "@babel/core": "^7.20.0",
22 | "@expo/webpack-config": "^0.17.2",
23 | "babel-loader": "^8.1.0",
24 | "babel-plugin-module-resolver": "^4.1.0"
25 | },
26 | "private": true
27 | }
28 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, View, ScrollView } from 'react-native';
3 | import DropdownExample1 from './dropdown/Dropdown1';
4 | import DropdownExample2 from './dropdown/Dropdown2';
5 | import Menu from './dropdown/Menu';
6 | import DropdownWithConfirm from './dropdown/DropdownWithConfirm';
7 | import CountrySelect1 from './dropdown/CountrySelect1';
8 | import CountrySelect2 from './dropdown/CountrySelect2';
9 | import MultiSelectAll from './dropdown/MultiSelectAll';
10 | import MultiSelectWithConfirm from './dropdown/MultiSelectWithConfirm';
11 | import DropdownLazyLoad from './dropdown/DropdownLazyLoad';
12 |
13 | const DropdownScreen = (_props: any) => {
14 | return (
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default DropdownScreen;
35 |
36 | const styles = StyleSheet.create({
37 | container: {
38 | flex: 1,
39 | backgroundColor: 'white',
40 | paddingVertical: 50,
41 | },
42 | });
43 |
--------------------------------------------------------------------------------
/example/src/dropdown/CountrySelect1.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { StyleSheet } from 'react-native';
3 | import { SelectCountry } from 'react-native-element-dropdown';
4 |
5 | const local_data = [
6 | {
7 | value: '1',
8 | lable: 'Country 1',
9 | image: {
10 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
11 | },
12 | },
13 | {
14 | value: '2',
15 | lable: 'Country 2',
16 | image: {
17 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
18 | },
19 | },
20 | {
21 | value: '3',
22 | lable: 'Country 3',
23 | image: {
24 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
25 | },
26 | },
27 | {
28 | value: '4',
29 | lable: 'Country 4',
30 | image: {
31 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
32 | },
33 | },
34 | {
35 | value: '5',
36 | lable: 'Country 5',
37 | image: {
38 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
39 | },
40 | },
41 | ];
42 |
43 | const SelectCountryScreen = () => {
44 | const [country, setCountry] = useState();
45 |
46 | return (
47 | {
64 | setCountry(e.value);
65 | }}
66 | />
67 | );
68 | };
69 |
70 | export default SelectCountryScreen;
71 |
72 | const styles = StyleSheet.create({
73 | dropdown: {
74 | margin: 16,
75 | height: 50,
76 | borderBottomColor: 'gray',
77 | borderBottomWidth: 0.5,
78 | },
79 | imageStyle: {
80 | width: 24,
81 | height: 24,
82 | },
83 | placeholderStyle: {
84 | fontSize: 16,
85 | },
86 | selectedTextStyle: {
87 | fontSize: 16,
88 | marginLeft: 8,
89 | },
90 | iconStyle: {
91 | width: 20,
92 | height: 20,
93 | },
94 | inputSearchStyle: {
95 | height: 40,
96 | fontSize: 16,
97 | },
98 | });
99 |
--------------------------------------------------------------------------------
/example/src/dropdown/CountrySelect2.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { StyleSheet } from 'react-native';
3 | import { SelectCountry } from 'react-native-element-dropdown';
4 |
5 | const local_data = [
6 | {
7 | value: '1',
8 | lable: 'Country 1',
9 | image: {
10 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
11 | },
12 | },
13 | {
14 | value: '2',
15 | lable: 'Country 2',
16 | image: {
17 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
18 | },
19 | },
20 | {
21 | value: '3',
22 | lable: 'Country 3',
23 | image: {
24 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
25 | },
26 | },
27 | {
28 | value: '4',
29 | lable: 'Country 4',
30 | image: {
31 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
32 | },
33 | },
34 | {
35 | value: '5',
36 | lable: 'Country 5',
37 | image: {
38 | uri: 'https://www.vigcenter.com/public/all/images/default-image.jpg',
39 | },
40 | },
41 | ];
42 |
43 | const SelectCountryScreen = () => {
44 | const [country, setCountry] = useState();
45 |
46 | return (
47 | {
63 | setCountry(e.value);
64 | }}
65 | />
66 | );
67 | };
68 |
69 | export default SelectCountryScreen;
70 |
71 | const styles = StyleSheet.create({
72 | dropdown: {
73 | margin: 16,
74 | height: 50,
75 | width: 160,
76 | backgroundColor: '#EEEEEE',
77 | borderRadius: 22,
78 | paddingHorizontal: 8,
79 | },
80 | imageStyle: {
81 | width: 24,
82 | height: 24,
83 | borderRadius: 12,
84 | },
85 | placeholderStyle: {
86 | fontSize: 16,
87 | },
88 | selectedTextStyle: {
89 | fontSize: 16,
90 | marginLeft: 8,
91 | },
92 | iconStyle: {
93 | width: 20,
94 | height: 20,
95 | },
96 | });
97 |
--------------------------------------------------------------------------------
/example/src/dropdown/Dropdown1.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-native/no-inline-styles */
2 | import React, { useState } from 'react';
3 | import { StyleSheet, Text, View } from 'react-native';
4 | import { Dropdown } from 'react-native-element-dropdown';
5 | import AntDesign from '@expo/vector-icons/AntDesign';
6 |
7 | const data = [
8 | { label: 'Item 1', value: '1', search: 'Item 1' },
9 | { label: 'Item 2', value: '2', search: 'Item 2' },
10 | { label: 'Item 3', value: '3', search: 'Item 3' },
11 | { label: 'Item 4', value: '4', search: 'Item 4' },
12 | { label: 'Item 5', value: '5', search: 'Item 5' },
13 | { label: 'Item 6', value: '6', search: 'Item 6' },
14 | { label: 'Item 7', value: '7', search: 'Item 7' },
15 | { label: 'Item 8', value: '8', search: 'Item 8' },
16 | ];
17 |
18 | const excludeItem = [
19 | { label: 'Item 7', value: '7', search: 'Item 7' },
20 | { label: 'Item 8', value: '8', search: 'Item 8' },
21 | ];
22 |
23 | const DropdownComponent = () => {
24 | const [value, setValue] = useState();
25 | const [isFocus, setIsFocus] = useState(false);
26 |
27 | const renderLabel = () => {
28 | if (value || isFocus) {
29 | return (
30 |
31 | Dropdown label
32 |
33 | );
34 | }
35 | return null;
36 | };
37 |
38 | return (
39 |
40 | {renderLabel()}
41 | setIsFocus(true)}
60 | onBlur={() => setIsFocus(false)}
61 | onChange={(item) => {
62 | setValue(item.value);
63 | setIsFocus(false);
64 | }}
65 | renderLeftIcon={() => (
66 |
72 | )}
73 | />
74 |
75 | );
76 | };
77 |
78 | export default DropdownComponent;
79 |
80 | const styles = StyleSheet.create({
81 | container: {
82 | backgroundColor: 'white',
83 | padding: 16,
84 | },
85 | dropdown: {
86 | height: 50,
87 | borderColor: 'gray',
88 | borderWidth: 0.5,
89 | borderRadius: 8,
90 | paddingHorizontal: 8,
91 | },
92 | icon: {
93 | marginRight: 5,
94 | },
95 | label: {
96 | position: 'absolute',
97 | backgroundColor: 'white',
98 | left: 22,
99 | top: 8,
100 | zIndex: 999,
101 | paddingHorizontal: 8,
102 | fontSize: 14,
103 | },
104 | placeholderStyle: {
105 | fontSize: 16,
106 | },
107 | selectedTextStyle: {
108 | fontSize: 16,
109 | },
110 | iconStyle: {
111 | width: 20,
112 | height: 20,
113 | },
114 | inputSearchStyle: {
115 | height: 40,
116 | fontSize: 16,
117 | },
118 | });
119 |
--------------------------------------------------------------------------------
/example/src/dropdown/Dropdown2.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 | import { Button, StyleSheet, View } from 'react-native';
3 | import { Dropdown, IDropdownRef } from 'react-native-element-dropdown';
4 |
5 | const data = [
6 | { label: 'Item 1', value: '1' },
7 | { label: 'Item 2', value: '2' },
8 | { label: 'Item 3', value: '3' },
9 | { label: 'Item 4', value: '4' },
10 | { label: 'Item 5', value: '5' },
11 | { label: 'Item 6', value: '6' },
12 | { label: 'Item 7', value: '7' },
13 | { label: 'Item 8', value: '8' },
14 | ];
15 |
16 | const DropdownComponent = () => {
17 | const [value, setValue] = useState();
18 | const ref = useRef(null);
19 |
20 | return (
21 |
22 | {
39 | setValue(item.value);
40 | }}
41 | onChangeText={() => {}} // Keep search keyword
42 | />
43 |
44 |
51 |
52 | );
53 | };
54 |
55 | export default DropdownComponent;
56 |
57 | const styles = StyleSheet.create({
58 | row: {
59 | flexDirection: 'row',
60 | alignItems: 'center',
61 | },
62 | dropdown: {
63 | flex: 1,
64 | margin: 16,
65 | height: 50,
66 | borderBottomColor: 'gray',
67 | borderBottomWidth: 0.5,
68 | },
69 | placeholderStyle: {
70 | fontSize: 16,
71 | },
72 | selectedTextStyle: {
73 | fontSize: 16,
74 | },
75 | iconStyle: {
76 | width: 20,
77 | height: 20,
78 | },
79 | inputSearchStyle: {
80 | height: 40,
81 | fontSize: 16,
82 | },
83 | button: {
84 | marginHorizontal: 16,
85 | },
86 | });
87 |
--------------------------------------------------------------------------------
/example/src/dropdown/DropdownLazyLoad.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject, useEffect, useRef, useState } from 'react';
2 | import {
3 | View,
4 | StyleSheet,
5 | Text,
6 | RefreshControl,
7 | ActivityIndicator,
8 | } from 'react-native';
9 | import { Dropdown } from 'react-native-element-dropdown';
10 | import AntDesign from '@expo/vector-icons/AntDesign';
11 |
12 | const RenderEmpty = () => {
13 | return (
14 |
15 | List Empty!
16 |
17 | );
18 | };
19 |
20 | const RenderFooter = ({ isLoading }: { isLoading: boolean }) => {
21 | if (!isLoading) {
22 | return null;
23 | }
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | const DropdownComponent = () => {
32 | const [data, setData] = useState([]);
33 | const [nextPage, setNextPage] = useState('');
34 | const [isLoading, setIsLoading] = useState(false);
35 | const [value, setValue] = useState(null);
36 | const [isSearch, setIsSearch] = useState(false);
37 | const ref: RefObject = useRef(null);
38 |
39 | const fetchApi = async (url: string, isRefresh: boolean = false) => {
40 | try {
41 | const response = await fetch(url);
42 | const json = await response.json();
43 |
44 | if (json) {
45 | if (isRefresh) {
46 | setData([]);
47 | }
48 |
49 | setNextPage(json?.next);
50 | setData((pre: any) => [...pre, ...json?.results]);
51 |
52 | setIsLoading(false);
53 | }
54 | } catch (error) {
55 | console.log('Error: ', error);
56 | }
57 | };
58 |
59 | const onRefresh = () => {
60 | fetchApi('https://pokeapi.co/api/v2/pokemon?limit=20&offset=0', true);
61 | };
62 |
63 | const onLoadMore = () => {
64 | if (!isSearch) {
65 | setIsLoading(true);
66 | fetchApi(nextPage);
67 | }
68 | };
69 |
70 | useEffect(() => {
71 | fetchApi('https://pokeapi.co/api/v2/pokemon?limit=20&offset=0');
72 | }, []);
73 |
74 | return (
75 | {
90 | setValue(item);
91 | setIsSearch(false);
92 | }}
93 | onChangeText={(keyword) => {
94 | setIsSearch(keyword.length > 0);
95 | }}
96 | flatListProps={{
97 | ListEmptyComponent: ,
98 | ListFooterComponent: ,
99 | refreshControl: (
100 |
101 | ),
102 | onEndReachedThreshold: 0.5,
103 | onEndReached: onLoadMore,
104 | }}
105 | renderLeftIcon={() => (
106 |
107 | )}
108 | />
109 | );
110 | };
111 |
112 | const styles = StyleSheet.create({
113 | dropdown: {
114 | backgroundColor: '#DDDDDD',
115 | margin: 16,
116 | paddingHorizontal: 16,
117 | paddingVertical: 16,
118 | borderRadius: 8,
119 | },
120 | icon: {
121 | marginRight: 5,
122 | },
123 | container: {
124 | marginTop: 4,
125 | },
126 | emptyContainer: {
127 | padding: 16,
128 | alignItems: 'center',
129 | },
130 | footerContainer: {
131 | padding: 16,
132 | alignItems: 'center',
133 | },
134 | });
135 |
136 | export default DropdownComponent;
137 |
--------------------------------------------------------------------------------
/example/src/dropdown/DropdownWithConfirm.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject, useRef, useState } from 'react';
2 | import { StyleSheet, View, Text, Alert } from 'react-native';
3 | import { Dropdown } from 'react-native-element-dropdown';
4 | import AntDesign from '@expo/vector-icons/AntDesign';
5 |
6 | const data = [
7 | { label: 'Item 1', value: '1' },
8 | { label: 'Item 2', value: '2' },
9 | { label: 'Item 3', value: '3' },
10 | { label: 'Item 4', value: '4' },
11 | { label: 'Item 5', value: '5' },
12 | { label: 'Item 6', value: '6' },
13 | { label: 'Item 7', value: '7' },
14 | { label: 'Item 8', value: '8' },
15 | ];
16 |
17 | const DropdownComponent = () => {
18 | const [value, setValue] = useState();
19 | const ref: RefObject = useRef(null);
20 |
21 | const renderItem = (item: any) => {
22 | return (
23 |
24 |
25 | {item.label}
26 |
27 | );
28 | };
29 |
30 | return (
31 | {
47 | setValue(item.value);
48 | }}
49 | renderItem={renderItem}
50 | confirmSelectItem
51 | onConfirmSelectItem={(item) => {
52 | Alert.alert('Confirm', 'Message confirm', [
53 | {
54 | text: 'Cancel',
55 | onPress: () => {},
56 | },
57 | {
58 | text: 'Confirm',
59 | onPress: () => {
60 | setValue(item.value);
61 | ref.current.close();
62 | },
63 | },
64 | ]);
65 | }}
66 | renderLeftIcon={() => (
67 |
68 | )}
69 | />
70 | );
71 | };
72 |
73 | export default DropdownComponent;
74 |
75 | const styles = StyleSheet.create({
76 | dropdown: {
77 | margin: 16,
78 | height: 50,
79 | backgroundColor: 'white',
80 | borderRadius: 12,
81 | padding: 12,
82 | shadowColor: '#000',
83 | shadowOffset: {
84 | width: 0,
85 | height: 1,
86 | },
87 | shadowOpacity: 0.2,
88 | shadowRadius: 1.41,
89 |
90 | elevation: 2,
91 | },
92 | icon: {
93 | marginRight: 5,
94 | },
95 | item: {
96 | padding: 17,
97 | flexDirection: 'row',
98 | justifyContent: 'space-between',
99 | alignItems: 'center',
100 | },
101 | textItem: {
102 | flex: 1,
103 | fontSize: 16,
104 | },
105 | placeholderStyle: {
106 | fontSize: 16,
107 | },
108 | selectedTextStyle: {
109 | fontSize: 16,
110 | },
111 | iconStyle: {
112 | width: 20,
113 | height: 20,
114 | },
115 | inputSearchStyle: {
116 | height: 40,
117 | fontSize: 16,
118 | },
119 | });
120 |
--------------------------------------------------------------------------------
/example/src/dropdown/Menu.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 | import { StyleSheet, Dimensions, View } from 'react-native';
3 | import { Dropdown, IDropdownRef } from 'react-native-element-dropdown';
4 | import AntDesign from '@expo/vector-icons/AntDesign';
5 |
6 | const data = [
7 | { label: 'Item 1', value: '1' },
8 | { label: 'Item 2', value: '2' },
9 | { label: 'Item 3', value: '3' },
10 | { label: 'Item 4', value: '4' },
11 | { label: 'Item 5', value: '5' },
12 | { label: 'Item 6', value: '6' },
13 | { label: 'Item 7', value: '7' },
14 | { label: 'Item 8', value: '8' },
15 | ];
16 |
17 | const { width } = Dimensions.get('window');
18 |
19 | const DropdownComponent = () => {
20 | const [value, setValue] = useState();
21 | const ref = useRef(null);
22 |
23 | const renderIcon = () => {
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | return (
32 | {
43 | setValue(item.value);
44 | }}
45 | onChangeText={() => {}} // Keep search keyword
46 | renderRightIcon={renderIcon}
47 | />
48 | );
49 | };
50 |
51 | export default DropdownComponent;
52 |
53 | const styles = StyleSheet.create({
54 | dropdown: {
55 | margin: 16,
56 | width: 50,
57 | marginLeft: width - 80,
58 | height: 50,
59 | borderColor: 'gray',
60 | borderWidth: 0.5,
61 | borderRadius: 8,
62 | paddingRight: 14,
63 | },
64 | containerStyle: {
65 | width: 200,
66 | marginLeft: -150,
67 | marginTop: 5,
68 | },
69 | iconStyle: {
70 | width: 50,
71 | height: 50,
72 | alignItems: 'center',
73 | justifyContent: 'center',
74 | },
75 | });
76 |
--------------------------------------------------------------------------------
/example/src/dropdown/MultiSelectAll.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 | import { Button, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3 | import { IMultiSelectRef, MultiSelect } from 'react-native-element-dropdown';
4 |
5 | const data = [
6 | { label: 'Item 1', value: '1' },
7 | { label: 'Item 2', value: '2' },
8 | { label: 'Item 3', value: '3' },
9 | { label: 'Item 4', value: '4' },
10 | { label: 'Item 5', value: '5' },
11 | { label: 'Item 6', value: '6' },
12 | { label: 'Item 7', value: '7' },
13 | { label: 'Item 8', value: '8' },
14 | ];
15 |
16 | const excludeItem = [
17 | { label: 'Item 7', value: '7', search: 'Item 7' },
18 | { label: 'Item 8', value: '8', search: 'Item 8' },
19 | ];
20 |
21 | const MultiSelectComponent = () => {
22 | const [selected, setSelected] = useState([]);
23 | const ref = useRef(null);
24 |
25 | const onSelectAll = (isSelectAll = true) => {
26 | const selectItem: string[] = [];
27 | if (isSelectAll) {
28 | data.map((item) => {
29 | selectItem.push(item.value);
30 | });
31 | }
32 | setSelected(selectItem);
33 | };
34 |
35 | const renderSelectAllIcon = () => {
36 | const isSelectAll = selected.length === data.length;
37 | return (
38 | onSelectAll(!isSelectAll)}
41 | >
42 |
43 | {isSelectAll ? `UnSelect All` : 'Select All'}
44 |
45 |
46 | );
47 | };
48 |
49 | return (
50 |
51 | {
69 | setSelected(item);
70 | }}
71 | selectedStyle={styles.selectedStyle}
72 | flatListProps={{ ListHeaderComponent: renderSelectAllIcon }}
73 | />
74 |
75 |
82 |
83 | );
84 | };
85 |
86 | export default MultiSelectComponent;
87 |
88 | const styles = StyleSheet.create({
89 | container: { padding: 16, flexDirection: 'row' },
90 | dropdown: {
91 | flex: 1,
92 | backgroundColor: 'transparent',
93 | borderBottomColor: 'gray',
94 | borderBottomWidth: 0.5,
95 | },
96 | placeholderStyle: {
97 | fontSize: 16,
98 | },
99 | selectedTextStyle: {
100 | fontSize: 14,
101 | },
102 | iconStyle: {
103 | width: 20,
104 | height: 20,
105 | },
106 | inputSearchStyle: {
107 | height: 40,
108 | fontSize: 16,
109 | },
110 | selectedStyle: {
111 | borderRadius: 12,
112 | },
113 | wrapSelectAll: {
114 | alignItems: 'flex-end',
115 | marginHorizontal: 16,
116 | marginVertical: 8,
117 | },
118 | txtSelectAll: {
119 | color: 'blue',
120 | },
121 | button: {
122 | marginHorizontal: 16,
123 | },
124 | });
125 |
--------------------------------------------------------------------------------
/example/src/dropdown/MultiSelectWithConfirm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3 | import { MultiSelect } from 'react-native-element-dropdown';
4 | import AntDesign from '@expo/vector-icons/AntDesign';
5 |
6 | const data = [
7 | { label: 'Item 1', value: '1' },
8 | { label: 'Item 2', value: '2' },
9 | { label: 'Item 3', value: '3' },
10 | { label: 'Item 4', value: '4' },
11 | { label: 'Item 5', value: '5' },
12 | { label: 'Item 6', value: '6' },
13 | { label: 'Item 7', value: '7' },
14 | { label: 'Item 8', value: '8' },
15 | ];
16 |
17 | const MultiSelectComponent = () => {
18 | const [selected, setSelected] = useState([]);
19 |
20 | const renderItem = (item: any) => {
21 | return (
22 |
23 | {item.label}
24 |
25 |
26 | );
27 | };
28 |
29 | return (
30 |
31 | {
46 | setSelected(item);
47 | }}
48 | renderItem={renderItem}
49 | renderSelectedItem={(item, unSelect) => (
50 | unSelect && unSelect(item)}>
51 |
52 | {item.label}
53 |
54 |
55 |
56 | )}
57 | confirmSelectItem
58 | confirmUnSelectItem
59 | onConfirmSelectItem={(item) => {
60 | Alert.alert('Confirm', 'Message confirm', [
61 | {
62 | text: 'Cancel',
63 | onPress: () => {},
64 | },
65 | {
66 | text: 'Confirm',
67 | onPress: () => {
68 | setSelected(item);
69 | },
70 | },
71 | ]);
72 | }}
73 | renderLeftIcon={() => (
74 |
75 | )}
76 | />
77 |
78 | );
79 | };
80 |
81 | export default MultiSelectComponent;
82 |
83 | const styles = StyleSheet.create({
84 | container: { padding: 16 },
85 | dropdown: {
86 | height: 50,
87 | backgroundColor: 'white',
88 | borderRadius: 12,
89 | padding: 12,
90 | shadowColor: '#000',
91 | shadowOffset: {
92 | width: 0,
93 | height: 1,
94 | },
95 | shadowOpacity: 0.2,
96 | shadowRadius: 1.41,
97 |
98 | elevation: 2,
99 | },
100 | icon: {
101 | marginRight: 5,
102 | },
103 | placeholderStyle: {
104 | fontSize: 16,
105 | },
106 | selectedTextStyle: {
107 | fontSize: 14,
108 | },
109 | iconStyle: {
110 | width: 20,
111 | height: 20,
112 | },
113 | inputSearchStyle: {
114 | height: 40,
115 | fontSize: 16,
116 | },
117 | item: {
118 | padding: 17,
119 | flexDirection: 'row',
120 | justifyContent: 'space-between',
121 | alignItems: 'center',
122 | },
123 | selectedStyle: {
124 | flexDirection: 'row',
125 | justifyContent: 'center',
126 | alignItems: 'center',
127 | borderRadius: 14,
128 | backgroundColor: 'white',
129 | shadowColor: '#000',
130 | marginTop: 8,
131 | marginRight: 12,
132 | paddingHorizontal: 12,
133 | paddingVertical: 8,
134 | shadowOffset: {
135 | width: 0,
136 | height: 1,
137 | },
138 | shadowOpacity: 0.2,
139 | shadowRadius: 1.41,
140 |
141 | elevation: 2,
142 | },
143 | textSelectedStyle: {
144 | marginRight: 5,
145 | fontSize: 16,
146 | },
147 | });
148 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig",
3 | "compilerOptions": {
4 | // Avoid expo-cli auto-generating a tsconfig
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const createExpoWebpackConfigAsync = require('@expo/webpack-config');
3 | const { resolver } = require('./metro.config');
4 |
5 | const root = path.resolve(__dirname, '..');
6 | const node_modules = path.join(__dirname, 'node_modules');
7 |
8 | module.exports = async function (env, argv) {
9 | const config = await createExpoWebpackConfigAsync(env, argv);
10 |
11 | config.module.rules.push({
12 | test: /\.(js|jsx|ts|tsx)$/,
13 | include: path.resolve(root, 'src'),
14 | use: 'babel-loader',
15 | });
16 |
17 | // We need to make sure that only one version is loaded for peerDependencies
18 | // So we alias them to the versions in example's node_modules
19 | Object.assign(config.resolve.alias, {
20 | ...resolver.extraNodeModules,
21 | 'react-native-web': path.join(node_modules, 'react-native-web'),
22 | });
23 |
24 | return config;
25 | };
26 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | parallel: true
3 | commands:
4 | lint:
5 | files: git diff --name-only @{push}
6 | glob: "*.{js,ts,jsx,tsx}"
7 | run: npx eslint {files}
8 | types:
9 | files: git diff --name-only @{push}
10 | glob: "*.{js,ts, jsx, tsx}"
11 | run: npx tsc --noEmit
12 | commit-msg:
13 | parallel: true
14 | commands:
15 | commitlint:
16 | run: npx commitlint --edit
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-element-dropdown",
3 | "version": "2.12.4",
4 | "description": "React Native Element Dropdown is a library that provides a customizable dropdown component for React Native applications.",
5 | "main": "lib/commonjs/index",
6 | "module": "lib/module/index",
7 | "types": "lib/typescript/index.d.ts",
8 | "react-native": "src/index",
9 | "source": "src/index",
10 | "files": [
11 | "src",
12 | "lib",
13 | "android",
14 | "ios",
15 | "cpp",
16 | "*.podspec",
17 | "!lib/typescript/example",
18 | "!ios/build",
19 | "!android/build",
20 | "!android/gradle",
21 | "!android/gradlew",
22 | "!android/gradlew.bat",
23 | "!android/local.properties",
24 | "!**/__tests__",
25 | "!**/__fixtures__",
26 | "!**/__mocks__",
27 | "!**/.*"
28 | ],
29 | "scripts": {
30 | "test": "jest",
31 | "typecheck": "tsc --noEmit",
32 | "lint": "eslint \"**/*.{js,ts,tsx}\"",
33 | "prepack": "bob build",
34 | "release": "release-it",
35 | "example": "yarn --cwd example",
36 | "bootstrap": "yarn example && yarn install"
37 | },
38 | "keywords": [
39 | "react-native",
40 | "elements",
41 | "components",
42 | "material",
43 | "dropdown",
44 | "lazy loading",
45 | "load more",
46 | "menu",
47 | "multiselect",
48 | "picker",
49 | "select",
50 | "select country"
51 | ],
52 | "repository": "https://github.com/hoaphantn7604/react-native-element-dropdown",
53 | "author": "Hoa Phan (https://github.com/hoaphantn7604)",
54 | "license": "MIT",
55 | "bugs": {
56 | "url": "https://github.com/hoaphantn7604/react-native-element-dropdown/issues"
57 | },
58 | "homepage": "https://github.com/hoaphantn7604/react-native-element-dropdown#readme",
59 | "publishConfig": {
60 | "registry": "https://registry.npmjs.org/"
61 | },
62 | "dependencies": {
63 | "lodash": "^4.17.21"
64 | },
65 | "devDependencies": {
66 | "@commitlint/config-conventional": "^17.0.2",
67 | "@evilmartians/lefthook": "^1.2.2",
68 | "@react-native-community/eslint-config": "^3.0.2",
69 | "@release-it/conventional-changelog": "^5.0.0",
70 | "@types/jest": "^28.1.2",
71 | "@types/lodash": "^4.14.191",
72 | "@types/react": "~17.0.21",
73 | "@types/react-native": "0.70.0",
74 | "@types/react-test-renderer": "^18.0.0",
75 | "commitlint": "^17.0.2",
76 | "del-cli": "^5.0.0",
77 | "eslint": "^8.4.1",
78 | "eslint-config-prettier": "^8.5.0",
79 | "eslint-plugin-prettier": "^4.0.0",
80 | "jest": "^28.1.1",
81 | "pod-install": "^0.1.0",
82 | "prettier": "^2.0.5",
83 | "react": "18.2.0",
84 | "react-native": "0.71.3",
85 | "react-native-builder-bob": "^0.20.0",
86 | "react-test-renderer": "^18.2.0",
87 | "release-it": "^15.0.0",
88 | "typescript": "^4.5.2"
89 | },
90 | "resolutions": {
91 | "@types/react": "17.0.21"
92 | },
93 | "peerDependencies": {
94 | "react": "*",
95 | "react-native": "*"
96 | },
97 | "engines": {
98 | "node": ">= 16.0.0"
99 | },
100 | "jest": {
101 | "preset": "react-native",
102 | "modulePathIgnorePatterns": [
103 | "/example/node_modules",
104 | "/lib/"
105 | ]
106 | },
107 | "commitlint": {
108 | "extends": [
109 | "@commitlint/config-conventional"
110 | ]
111 | },
112 | "release-it": {
113 | "git": {
114 | "commitMessage": "chore: release ${version}",
115 | "tagName": "v${version}"
116 | },
117 | "npm": {
118 | "publish": true
119 | },
120 | "github": {
121 | "release": true
122 | },
123 | "plugins": {
124 | "@release-it/conventional-changelog": {
125 | "preset": "angular"
126 | }
127 | }
128 | },
129 | "eslintConfig": {
130 | "root": true,
131 | "extends": [
132 | "@react-native-community",
133 | "prettier"
134 | ],
135 | "rules": {
136 | "prettier/prettier": [
137 | "error",
138 | {
139 | "quoteProps": "consistent",
140 | "singleQuote": true,
141 | "tabWidth": 2,
142 | "trailingComma": "es5",
143 | "useTabs": false
144 | }
145 | ]
146 | }
147 | },
148 | "eslintIgnore": [
149 | "node_modules/",
150 | "lib/"
151 | ],
152 | "prettier": {
153 | "quoteProps": "consistent",
154 | "singleQuote": true,
155 | "tabWidth": 2,
156 | "trailingComma": "es5",
157 | "useTabs": false
158 | },
159 | "react-native-builder-bob": {
160 | "source": "src",
161 | "output": "lib",
162 | "targets": [
163 | "commonjs",
164 | "module",
165 | [
166 | "typescript",
167 | {
168 | "project": "tsconfig.build.json"
169 | }
170 | ]
171 | ]
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/scripts/bootstrap.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const path = require('path');
3 | const child_process = require('child_process');
4 |
5 | const root = path.resolve(__dirname, '..');
6 | const args = process.argv.slice(2);
7 | const options = {
8 | cwd: process.cwd(),
9 | env: process.env,
10 | stdio: 'inherit',
11 | encoding: 'utf-8',
12 | };
13 |
14 | if (os.type() === 'Windows_NT') {
15 | options.shell = true;
16 | }
17 |
18 | let result;
19 |
20 | if (process.cwd() !== root || args.length) {
21 | // We're not in the root of the project, or additional arguments were passed
22 | // In this case, forward the command to `yarn`
23 | result = child_process.spawnSync('yarn', args, options);
24 | } else {
25 | // If `yarn` is run without arguments, perform bootstrap
26 | result = child_process.spawnSync('yarn', ['bootstrap'], options);
27 | }
28 |
29 | process.exitCode = result.status;
30 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | it.todo('write a test');
2 |
--------------------------------------------------------------------------------
/src/assets/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoaphantn7604/react-native-element-dropdown/5787e1f33f7ca890322457b1ee2dcd2e68e78ed1/src/assets/close.png
--------------------------------------------------------------------------------
/src/assets/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoaphantn7604/react-native-element-dropdown/5787e1f33f7ca890322457b1ee2dcd2e68e78ed1/src/assets/down.png
--------------------------------------------------------------------------------
/src/components/Dropdown/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-shadow */
2 | import _assign from 'lodash/assign';
3 | import _differenceWith from 'lodash/differenceWith';
4 | import _findIndex from 'lodash/findIndex';
5 | import _get from 'lodash/get';
6 | import _isEqual from 'lodash/isEqual';
7 |
8 | import { debounce } from 'lodash';
9 | import React, {
10 | useCallback,
11 | useEffect,
12 | useImperativeHandle,
13 | useMemo,
14 | useRef,
15 | useState,
16 | } from 'react';
17 | import {
18 | Dimensions,
19 | FlatList,
20 | I18nManager,
21 | Image,
22 | Keyboard,
23 | KeyboardEvent,
24 | Modal,
25 | StatusBar,
26 | StyleSheet,
27 | Text,
28 | TouchableHighlight,
29 | TouchableWithoutFeedback,
30 | View,
31 | ViewStyle,
32 | } from 'react-native';
33 | import { useDetectDevice } from '../../toolkits';
34 | import { useDeviceOrientation } from '../../useDeviceOrientation';
35 | import CInput from '../TextInput';
36 | import { DropdownProps, IDropdownRef } from './model';
37 | import { styles } from './styles';
38 |
39 | const { isTablet } = useDetectDevice;
40 | const ic_down = require('../../assets/down.png');
41 |
42 | const statusBarHeight: number = StatusBar.currentHeight || 0;
43 |
44 | const DropdownComponent = React.forwardRef>(
45 | (props, currentRef) => {
46 | const orientation = useDeviceOrientation();
47 | const {
48 | testID,
49 | itemTestIDField,
50 | onChange,
51 | style = {},
52 | containerStyle,
53 | placeholderStyle,
54 | selectedTextStyle,
55 | itemContainerStyle,
56 | itemTextStyle,
57 | inputSearchStyle,
58 | iconStyle,
59 | selectedTextProps = {},
60 | data = [],
61 | labelField,
62 | valueField,
63 | searchField,
64 | value,
65 | activeColor = '#F6F7F8',
66 | fontFamily,
67 | iconColor = 'gray',
68 | searchPlaceholder,
69 | searchPlaceholderTextColor = 'gray',
70 | placeholder = 'Select item',
71 | search = false,
72 | maxHeight = 340,
73 | minHeight = 0,
74 | disable = false,
75 | keyboardAvoiding = true,
76 | inverted = true,
77 | renderLeftIcon,
78 | renderRightIcon,
79 | renderItem,
80 | renderInputSearch,
81 | onFocus,
82 | onBlur,
83 | autoScroll = true,
84 | showsVerticalScrollIndicator = true,
85 | dropdownPosition = 'auto',
86 | flatListProps,
87 | searchQuery,
88 | backgroundColor,
89 | onChangeText,
90 | confirmSelectItem,
91 | onConfirmSelectItem,
92 | accessibilityLabel,
93 | itemAccessibilityLabelField,
94 | mode = 'default',
95 | closeModalWhenSelectedItem = true,
96 | excludeItems = [],
97 | excludeSearchItems = [],
98 | } = props;
99 |
100 | const ref = useRef(null);
101 | const refList = useRef(null);
102 | const [visible, setVisible] = useState(false);
103 | const [currentValue, setCurrentValue] = useState(null);
104 | const [listData, setListData] = useState(data);
105 | const [position, setPosition] = useState();
106 | const [keyboardHeight, setKeyboardHeight] = useState(0);
107 | const [searchText, setSearchText] = useState('');
108 |
109 | const { width: W, height: H } = Dimensions.get('window');
110 | const styleContainerVertical: ViewStyle = useMemo(() => {
111 | return {
112 | backgroundColor: 'rgba(0,0,0,0.1)',
113 | alignItems: 'center',
114 | };
115 | }, []);
116 | const styleHorizontal: ViewStyle = useMemo(() => {
117 | return {
118 | width: orientation === 'LANDSCAPE' ? W / 2 : '100%',
119 | alignSelf: 'center',
120 | };
121 | }, [W, orientation]);
122 |
123 | useImperativeHandle(currentRef, () => {
124 | return { open: eventOpen, close: eventClose };
125 | });
126 |
127 | useEffect(() => {
128 | return eventClose;
129 | // eslint-disable-next-line react-hooks/exhaustive-deps
130 | }, []);
131 |
132 | const excludeData = useCallback(
133 | (data: any[]) => {
134 | if (excludeItems.length > 0) {
135 | const getData = _differenceWith(
136 | data,
137 | excludeItems,
138 | (obj1, obj2) => _get(obj1, valueField) === _get(obj2, valueField)
139 | );
140 | return getData || [];
141 | } else {
142 | return data || [];
143 | }
144 | },
145 | [excludeItems, valueField]
146 | );
147 |
148 | useEffect(() => {
149 | if (data && searchText.length === 0) {
150 | const filterData = excludeData(data);
151 | setListData([...filterData]);
152 | }
153 |
154 | if (searchText) {
155 | onSearch(searchText);
156 | }
157 | // eslint-disable-next-line react-hooks/exhaustive-deps
158 | }, [data, searchText]);
159 |
160 | const eventOpen = () => {
161 | if (!disable) {
162 | _measure();
163 | setVisible(true);
164 | if (onFocus) {
165 | onFocus();
166 | }
167 |
168 | if (searchText.length > 0) {
169 | onSearch(searchText);
170 | }
171 | }
172 | };
173 |
174 | const eventClose = useCallback(() => {
175 | if (!disable) {
176 | setVisible(false);
177 | if (onBlur) {
178 | onBlur();
179 | }
180 | }
181 | }, [disable, onBlur]);
182 |
183 | const font = useCallback(() => {
184 | if (fontFamily) {
185 | return {
186 | fontFamily: fontFamily,
187 | };
188 | } else {
189 | return {};
190 | }
191 | }, [fontFamily]);
192 |
193 | const _measure = useCallback(() => {
194 | if (ref && ref?.current) {
195 | ref.current.measureInWindow((pageX, pageY, width, height) => {
196 | let isFull = isTablet
197 | ? false
198 | : mode === 'modal' || orientation === 'LANDSCAPE';
199 |
200 | if (mode === 'auto') {
201 | isFull = false;
202 | }
203 |
204 | const top = isFull ? 20 : height + pageY + 2;
205 | const bottom = H - top + height;
206 | const left = I18nManager.isRTL ? W - width - pageX : pageX;
207 |
208 | setPosition({
209 | isFull,
210 | width: Math.floor(width),
211 | top: Math.floor(top + statusBarHeight),
212 | bottom: Math.floor(bottom - statusBarHeight),
213 | left: Math.floor(left),
214 | height: Math.floor(height),
215 | });
216 | });
217 | }
218 | }, [H, W, orientation, mode]);
219 |
220 | const onKeyboardDidShow = useCallback(
221 | (e: KeyboardEvent) => {
222 | _measure();
223 | setKeyboardHeight(e.endCoordinates.height);
224 | },
225 | [_measure]
226 | );
227 |
228 | const onKeyboardDidHide = useCallback(() => {
229 | setKeyboardHeight(0);
230 | _measure();
231 | }, [_measure]);
232 |
233 | useEffect(() => {
234 | const susbcriptionKeyboardDidShow = Keyboard.addListener(
235 | 'keyboardDidShow',
236 | onKeyboardDidShow
237 | );
238 | const susbcriptionKeyboardDidHide = Keyboard.addListener(
239 | 'keyboardDidHide',
240 | onKeyboardDidHide
241 | );
242 |
243 | return () => {
244 | if (typeof susbcriptionKeyboardDidShow?.remove === 'function') {
245 | susbcriptionKeyboardDidShow.remove();
246 | }
247 |
248 | if (typeof susbcriptionKeyboardDidHide?.remove === 'function') {
249 | susbcriptionKeyboardDidHide.remove();
250 | }
251 | };
252 | }, [onKeyboardDidHide, onKeyboardDidShow]);
253 |
254 | const getValue = useCallback(() => {
255 | const defaultValue =
256 | typeof value === 'object' ? _get(value, valueField) : value;
257 |
258 | const getItem = data.filter((e) =>
259 | _isEqual(defaultValue, _get(e, valueField))
260 | );
261 |
262 | if (getItem.length > 0) {
263 | setCurrentValue(getItem[0]);
264 | } else {
265 | setCurrentValue(null);
266 | }
267 | }, [data, value, valueField]);
268 |
269 | useEffect(() => {
270 | getValue();
271 | }, [value, data, getValue]);
272 |
273 | const scrollIndex = debounce(
274 | useCallback(() => {
275 | if (
276 | autoScroll &&
277 | data?.length > 0 &&
278 | listData?.length === data?.length
279 | ) {
280 | if (refList && refList?.current) {
281 | const defaultValue =
282 | typeof value === 'object' ? _get(value, valueField) : value;
283 |
284 | const index = _findIndex(listData, (e) =>
285 | _isEqual(defaultValue, _get(e, valueField))
286 | );
287 | if (
288 | listData?.length > 0 &&
289 | index > -1 &&
290 | index <= listData?.length - 1
291 | ) {
292 | try {
293 | refList.current.scrollToIndex({
294 | index: index,
295 | animated: false,
296 | });
297 | } catch (error) {
298 | console.warn(`scrollToIndex error: ${error}`);
299 | }
300 | }
301 | }
302 | }
303 | }, [autoScroll, data.length, listData, value, valueField]),
304 | 200
305 | );
306 |
307 | const showOrClose = useCallback(() => {
308 | if (!disable) {
309 | const visibleStatus = !visible;
310 |
311 | if (keyboardHeight > 0 && !visibleStatus) {
312 | return Keyboard.dismiss();
313 | }
314 |
315 | if (!visibleStatus) {
316 | if (onChangeText) {
317 | onChangeText('');
318 | }
319 | setSearchText('');
320 | onSearch('');
321 | }
322 |
323 | _measure();
324 | setVisible(visibleStatus);
325 |
326 | if (data) {
327 | const filterData = excludeData(data);
328 | setListData(filterData);
329 | }
330 |
331 | if (visibleStatus) {
332 | if (onFocus) {
333 | onFocus();
334 | }
335 | } else {
336 | if (onBlur) {
337 | onBlur();
338 | }
339 | }
340 |
341 | if (searchText.length > 0) {
342 | onSearch(searchText);
343 | }
344 | }
345 | // eslint-disable-next-line react-hooks/exhaustive-deps
346 | }, [
347 | disable,
348 | keyboardHeight,
349 | visible,
350 | _measure,
351 | data,
352 | searchText,
353 | onFocus,
354 | onBlur,
355 | ]);
356 |
357 | const onSearch = useCallback(
358 | (text: string) => {
359 | if (text.length > 0) {
360 | const defaultFilterFunction = (e: any) => {
361 | const item = _get(e, searchField || labelField)
362 | ?.toLowerCase()
363 | .replace(/\s/g, '')
364 | .normalize('NFD')
365 | .replace(/[\u0300-\u036f]/g, '');
366 | const key = text
367 | .toLowerCase()
368 | .replace(/\s/g, '')
369 | .normalize('NFD')
370 | .replace(/[\u0300-\u036f]/g, '');
371 |
372 | return item.indexOf(key) >= 0;
373 | };
374 |
375 | const propSearchFunction = (e: any) => {
376 | const labelText = _get(e, searchField || labelField);
377 |
378 | return searchQuery?.(text, labelText);
379 | };
380 |
381 | const dataSearch = data.filter(
382 | searchQuery ? propSearchFunction : defaultFilterFunction
383 | );
384 |
385 | if (excludeSearchItems.length > 0 || excludeItems.length > 0) {
386 | const excludeSearchData = _differenceWith(
387 | dataSearch,
388 | excludeSearchItems,
389 | (obj1, obj2) => _get(obj1, valueField) === _get(obj2, valueField)
390 | );
391 |
392 | const filterData = excludeData(excludeSearchData);
393 | setListData(filterData);
394 | } else {
395 | setListData(dataSearch);
396 | }
397 | } else {
398 | const filterData = excludeData(data);
399 | setListData(filterData);
400 | }
401 | },
402 | [
403 | data,
404 | searchQuery,
405 | excludeSearchItems,
406 | excludeItems,
407 | searchField,
408 | labelField,
409 | valueField,
410 | excludeData,
411 | ]
412 | );
413 |
414 | const onSelect = useCallback(
415 | (item: any) => {
416 | if (confirmSelectItem && onConfirmSelectItem) {
417 | return onConfirmSelectItem(item);
418 | }
419 |
420 | setCurrentValue(item);
421 | onChange(item);
422 |
423 | if (closeModalWhenSelectedItem) {
424 | if (onChangeText) {
425 | onChangeText('');
426 | }
427 | setSearchText('');
428 | onSearch('');
429 | eventClose();
430 | }
431 | },
432 | [
433 | confirmSelectItem,
434 | eventClose,
435 | onChange,
436 | onChangeText,
437 | onConfirmSelectItem,
438 | onSearch,
439 | closeModalWhenSelectedItem,
440 | ]
441 | );
442 |
443 | const _renderDropdown = () => {
444 | const isSelected = currentValue && _get(currentValue, valueField);
445 | return (
446 |
452 |
453 | {renderLeftIcon?.(visible)}
454 |
462 | {isSelected !== null
463 | ? _get(currentValue, labelField)
464 | : placeholder}
465 |
466 | {renderRightIcon ? (
467 | renderRightIcon(visible)
468 | ) : (
469 |
477 | )}
478 |
479 |
480 | );
481 | };
482 |
483 | const _renderItem = useCallback(
484 | ({ item, index }: { item: any; index: number }) => {
485 | const isSelected = currentValue && _get(currentValue, valueField);
486 | const selected = _isEqual(_get(item, valueField), isSelected);
487 | _assign(item, { _index: index });
488 | return (
489 | onSelect(item)}
499 | >
500 |
508 | {renderItem ? (
509 | renderItem(item, selected)
510 | ) : (
511 |
512 |
519 | {_get(item, labelField)}
520 |
521 |
522 | )}
523 |
524 |
525 | );
526 | },
527 | [
528 | accessibilityLabel,
529 | activeColor,
530 | currentValue,
531 | font,
532 | itemAccessibilityLabelField,
533 | itemContainerStyle,
534 | itemTestIDField,
535 | itemTextStyle,
536 | labelField,
537 | onSelect,
538 | renderItem,
539 | valueField,
540 | ]
541 | );
542 |
543 | const renderSearch = useCallback(() => {
544 | if (search) {
545 | if (renderInputSearch) {
546 | return renderInputSearch((text) => {
547 | if (onChangeText) {
548 | setSearchText(text);
549 | onChangeText(text);
550 | }
551 | onSearch(text);
552 | });
553 | } else {
554 | return (
555 | {
564 | if (onChangeText) {
565 | setSearchText(e);
566 | onChangeText(e);
567 | }
568 | onSearch(e);
569 | }}
570 | placeholderTextColor={searchPlaceholderTextColor}
571 | showIcon
572 | iconStyle={[{ tintColor: iconColor }, iconStyle]}
573 | />
574 | );
575 | }
576 | }
577 | return null;
578 | }, [
579 | accessibilityLabel,
580 | font,
581 | iconColor,
582 | iconStyle,
583 | inputSearchStyle,
584 | onChangeText,
585 | onSearch,
586 | renderInputSearch,
587 | search,
588 | searchPlaceholder,
589 | searchPlaceholderTextColor,
590 | testID,
591 | searchText,
592 | ]);
593 |
594 | const _renderList = useCallback(
595 | (isTopPosition: boolean) => {
596 | const isInverted = !inverted ? false : isTopPosition;
597 |
598 | const _renderListHelper = () => {
599 | return (
600 | index.toString()}
612 | showsVerticalScrollIndicator={showsVerticalScrollIndicator}
613 | />
614 | );
615 | };
616 |
617 | return (
618 |
619 |
620 | {isInverted && _renderListHelper()}
621 | {renderSearch()}
622 | {!isInverted && _renderListHelper()}
623 |
624 |
625 | );
626 | },
627 | [
628 | _renderItem,
629 | accessibilityLabel,
630 | flatListProps,
631 | listData,
632 | inverted,
633 | renderSearch,
634 | scrollIndex,
635 | showsVerticalScrollIndicator,
636 | testID,
637 | ]
638 | );
639 |
640 | const _renderModal = useCallback(() => {
641 | if (visible && position) {
642 | const { isFull, width, height, top, bottom, left } = position;
643 |
644 | const onAutoPosition = () => {
645 | if (keyboardHeight > 0) {
646 | return bottom < keyboardHeight + height;
647 | }
648 |
649 | return bottom < (search ? 150 : 100);
650 | };
651 |
652 | if (width && top && bottom) {
653 | const styleVertical: ViewStyle = {
654 | left: left,
655 | maxHeight: maxHeight,
656 | minHeight: minHeight,
657 | };
658 | const isTopPosition =
659 | dropdownPosition === 'auto'
660 | ? onAutoPosition()
661 | : dropdownPosition === 'top';
662 |
663 | let keyboardStyle: ViewStyle = {};
664 |
665 | let extendHeight = !isTopPosition ? top : bottom;
666 | if (
667 | keyboardAvoiding &&
668 | keyboardHeight > 0 &&
669 | isTopPosition &&
670 | dropdownPosition === 'auto'
671 | ) {
672 | extendHeight = keyboardHeight;
673 | }
674 |
675 | return (
676 |
683 |
684 |
692 |
704 |
714 | {_renderList(isTopPosition)}
715 |
716 |
717 |
718 |
719 |
720 | );
721 | }
722 | return null;
723 | }
724 | return null;
725 | }, [
726 | visible,
727 | search,
728 | position,
729 | keyboardHeight,
730 | maxHeight,
731 | minHeight,
732 | dropdownPosition,
733 | keyboardAvoiding,
734 | showOrClose,
735 | styleContainerVertical,
736 | backgroundColor,
737 | containerStyle,
738 | styleHorizontal,
739 | _renderList,
740 | ]);
741 |
742 | return (
743 |
748 | {_renderDropdown()}
749 | {_renderModal()}
750 |
751 | );
752 | }
753 | );
754 |
755 | export default DropdownComponent;
756 |
--------------------------------------------------------------------------------
/src/components/Dropdown/model.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react';
2 | import type {
3 | StyleProp,
4 | TextStyle,
5 | ViewStyle,
6 | TextProps,
7 | ImageStyle,
8 | FlatListProps,
9 | } from 'react-native';
10 |
11 | export type IDropdownRef = {
12 | open: () => void;
13 | close: () => void;
14 | };
15 |
16 | export interface DropdownProps {
17 | ref?:
18 | | React.RefObject
19 | | React.MutableRefObject
20 | | null
21 | | undefined;
22 | testID?: string;
23 | itemTestIDField?: string;
24 | style?: StyleProp;
25 | containerStyle?: StyleProp;
26 | placeholderStyle?: StyleProp;
27 | selectedTextStyle?: StyleProp;
28 | selectedTextProps?: TextProps;
29 | itemContainerStyle?: StyleProp;
30 | itemTextStyle?: StyleProp;
31 | inputSearchStyle?: StyleProp;
32 | iconStyle?: StyleProp;
33 | maxHeight?: number;
34 | minHeight?: number;
35 | fontFamily?: string;
36 | iconColor?: string;
37 | activeColor?: string;
38 | data: T[];
39 | value?: T | string | null | undefined;
40 | placeholder?: string;
41 | labelField: keyof T;
42 | valueField: keyof T;
43 | searchField?: keyof T;
44 | search?: boolean;
45 | searchPlaceholder?: string;
46 | searchPlaceholderTextColor?: string;
47 | disable?: boolean;
48 | autoScroll?: boolean;
49 | showsVerticalScrollIndicator?: boolean;
50 | dropdownPosition?: 'auto' | 'top' | 'bottom';
51 | flatListProps?: Omit, 'renderItem' | 'data'>;
52 | keyboardAvoiding?: boolean;
53 | backgroundColor?: string;
54 | confirmSelectItem?: boolean;
55 | accessibilityLabel?: string;
56 | itemAccessibilityLabelField?: string;
57 | inverted?: boolean;
58 | mode?: 'default' | 'modal' | 'auto';
59 | closeModalWhenSelectedItem?: boolean;
60 | excludeItems?: T[];
61 | excludeSearchItems?: T[];
62 | onChange: (item: T) => void;
63 | renderLeftIcon?: (visible?: boolean) => React.ReactElement | null;
64 | renderRightIcon?: (visible?: boolean) => React.ReactElement | null;
65 | renderItem?: (item: T, selected?: boolean) => React.ReactElement | null;
66 | renderInputSearch?: (
67 | onSearch: (text: string) => void
68 | ) => React.ReactElement | null;
69 | onFocus?: () => void;
70 | onBlur?: () => void;
71 | searchQuery?: (keyword: string, labelValue: string) => boolean;
72 | onChangeText?: (search: string) => void;
73 | onConfirmSelectItem?: (item: T) => void;
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/Dropdown/styles.ts:
--------------------------------------------------------------------------------
1 | import { I18nManager, StyleSheet } from 'react-native';
2 |
3 | export const styles = StyleSheet.create({
4 | mainWrap: {
5 | justifyContent: 'center',
6 | },
7 | container: {
8 | flexShrink: 1,
9 | borderWidth: 0.5,
10 | borderColor: '#EEEEEE',
11 | backgroundColor: 'white',
12 | shadowColor: '#000',
13 | shadowOffset: {
14 | width: 0,
15 | height: 1,
16 | },
17 | shadowOpacity: 0.2,
18 | shadowRadius: 1.41,
19 | elevation: 2,
20 | },
21 | flex1: {
22 | flex: 1,
23 | },
24 | flexShrink: {
25 | flexShrink: 1,
26 | },
27 | wrapTop: {
28 | justifyContent: 'flex-end',
29 | },
30 | dropdown: {
31 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
32 | justifyContent: 'space-between',
33 | alignItems: 'center',
34 | },
35 | title: {
36 | marginVertical: 5,
37 | fontSize: 16,
38 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
39 | },
40 | item: {
41 | padding: 17,
42 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
43 | justifyContent: 'space-between',
44 | alignItems: 'center',
45 | },
46 | textItem: {
47 | flex: 1,
48 | fontSize: 16,
49 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
50 | },
51 | icon: {
52 | width: 20,
53 | height: 20,
54 | },
55 | input: {
56 | borderWidth: 0.5,
57 | borderColor: '#DDDDDD',
58 | paddingHorizontal: 8,
59 | marginBottom: 8,
60 | margin: 6,
61 | height: 45,
62 | },
63 | fullScreen: {
64 | alignItems: 'center',
65 | justifyContent: 'center',
66 | },
67 | });
68 |
--------------------------------------------------------------------------------
/src/components/MultiSelect/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-shadow */
2 | import _assign from 'lodash/assign';
3 | import _differenceWith from 'lodash/differenceWith';
4 | import _get from 'lodash/get';
5 | import React, {
6 | useCallback,
7 | useEffect,
8 | useImperativeHandle,
9 | useMemo,
10 | useRef,
11 | useState,
12 | } from 'react';
13 | import {
14 | Dimensions,
15 | FlatList,
16 | I18nManager,
17 | Image,
18 | Keyboard,
19 | KeyboardEvent,
20 | Modal,
21 | StyleSheet,
22 | Text,
23 | TouchableHighlight,
24 | TouchableWithoutFeedback,
25 | View,
26 | ViewStyle,
27 | StatusBar,
28 | } from 'react-native';
29 | import { useDetectDevice } from '../../toolkits';
30 | import { useDeviceOrientation } from '../../useDeviceOrientation';
31 | import CInput from '../TextInput';
32 | import { IMultiSelectRef, MultiSelectProps } from './model';
33 | import { styles } from './styles';
34 |
35 | const { isTablet } = useDetectDevice;
36 | const ic_down = require('../../assets/down.png');
37 | const statusBarHeight: number = StatusBar.currentHeight || 0;
38 |
39 | const MultiSelectComponent = React.forwardRef<
40 | IMultiSelectRef,
41 | MultiSelectProps
42 | >((props, currentRef) => {
43 | const orientation = useDeviceOrientation();
44 | const {
45 | testID,
46 | itemTestIDField,
47 | onChange,
48 | data = [],
49 | value,
50 | style = {},
51 | labelField,
52 | valueField,
53 | searchField,
54 | selectedStyle,
55 | selectedTextStyle,
56 | itemContainerStyle,
57 | itemTextStyle,
58 | iconStyle,
59 | selectedTextProps = {},
60 | activeColor = '#F6F7F8',
61 | containerStyle,
62 | fontFamily,
63 | placeholderStyle,
64 | iconColor = 'gray',
65 | inputSearchStyle,
66 | searchPlaceholder,
67 | searchPlaceholderTextColor = 'gray',
68 | placeholder = 'Select item',
69 | search = false,
70 | maxHeight = 340,
71 | minHeight = 0,
72 | maxSelect,
73 | disable = false,
74 | keyboardAvoiding = true,
75 | inside = false,
76 | inverted = true,
77 | renderItem,
78 | renderLeftIcon,
79 | renderRightIcon,
80 | renderSelectedItem,
81 | renderInputSearch,
82 | onFocus,
83 | onBlur,
84 | showsVerticalScrollIndicator = true,
85 | dropdownPosition = 'auto',
86 | flatListProps,
87 | alwaysRenderSelectedItem = false,
88 | searchQuery,
89 | backgroundColor,
90 | onChangeText,
91 | confirmSelectItem,
92 | confirmUnSelectItem,
93 | onConfirmSelectItem,
94 | accessibilityLabel,
95 | itemAccessibilityLabelField,
96 | visibleSelectedItem = true,
97 | mode = 'default',
98 | excludeItems = [],
99 | excludeSearchItems = [],
100 | } = props;
101 |
102 | const ref = useRef(null);
103 | const [visible, setVisible] = useState(false);
104 | const [currentValue, setCurrentValue] = useState([]);
105 | const [listData, setListData] = useState(data);
106 | const [, setKey] = useState(Math.random());
107 | const [position, setPosition] = useState();
108 | const [keyboardHeight, setKeyboardHeight] = useState(0);
109 | const [searchText, setSearchText] = useState('');
110 |
111 | const { width: W, height: H } = Dimensions.get('window');
112 | const styleContainerVertical: ViewStyle = useMemo(() => {
113 | return {
114 | backgroundColor: 'rgba(0,0,0,0.1)',
115 | alignItems: 'center',
116 | };
117 | }, []);
118 | const styleHorizontal: ViewStyle = useMemo(() => {
119 | return {
120 | width: orientation === 'LANDSCAPE' ? W / 2 : '100%',
121 | alignSelf: 'center',
122 | };
123 | }, [W, orientation]);
124 |
125 | useImperativeHandle(currentRef, () => {
126 | return { open: eventOpen, close: eventClose };
127 | });
128 |
129 | useEffect(() => {
130 | return eventClose;
131 | // eslint-disable-next-line react-hooks/exhaustive-deps
132 | }, []);
133 |
134 | const excludeData = useCallback(
135 | (data: any[]) => {
136 | if (excludeItems.length > 0) {
137 | const getData = _differenceWith(
138 | data,
139 | excludeItems,
140 | (obj1, obj2) => _get(obj1, valueField) === _get(obj2, valueField)
141 | );
142 | return getData || [];
143 | } else {
144 | return data || [];
145 | }
146 | },
147 | [excludeItems, valueField]
148 | );
149 |
150 | useEffect(() => {
151 | if (data && searchText.length === 0) {
152 | const filterData = excludeData(data);
153 | setListData([...filterData]);
154 | }
155 |
156 | if (searchText) {
157 | onSearch(searchText);
158 | }
159 | // eslint-disable-next-line react-hooks/exhaustive-deps
160 | }, [data, searchText]);
161 |
162 | const eventOpen = () => {
163 | if (!disable) {
164 | _measure();
165 | setVisible(true);
166 | if (onFocus) {
167 | onFocus();
168 | }
169 |
170 | if (searchText.length > 0) {
171 | onSearch(searchText);
172 | }
173 | }
174 | };
175 |
176 | const eventClose = () => {
177 | if (!disable) {
178 | setVisible(false);
179 | if (onBlur) {
180 | onBlur();
181 | }
182 | }
183 | };
184 |
185 | const font = useCallback(() => {
186 | if (fontFamily) {
187 | return {
188 | fontFamily: fontFamily,
189 | };
190 | } else {
191 | return {};
192 | }
193 | }, [fontFamily]);
194 |
195 | const getValue = useCallback(() => {
196 | setCurrentValue(value ? [...value] : []);
197 | }, [value]);
198 |
199 | const _measure = useCallback(() => {
200 | if (ref && ref?.current) {
201 | ref.current.measureInWindow((pageX, pageY, width, height) => {
202 | let isFull = isTablet
203 | ? false
204 | : mode === 'modal' || orientation === 'LANDSCAPE';
205 |
206 | if (mode === 'auto') {
207 | isFull = false;
208 | }
209 |
210 | const top = isFull ? 20 : height + pageY + 2;
211 | const bottom = H - top + height;
212 | const left = I18nManager.isRTL ? W - width - pageX : pageX;
213 |
214 | setPosition({
215 | isFull,
216 | width: Math.floor(width),
217 | top: Math.floor(top + statusBarHeight),
218 | bottom: Math.floor(bottom - statusBarHeight),
219 | left: Math.floor(left),
220 | height: Math.floor(height),
221 | });
222 | });
223 | }
224 | }, [H, W, orientation, mode]);
225 |
226 | const onKeyboardDidShow = useCallback(
227 | (e: KeyboardEvent) => {
228 | _measure();
229 | setKeyboardHeight(e.endCoordinates.height);
230 | },
231 | [_measure]
232 | );
233 |
234 | const onKeyboardDidHide = useCallback(() => {
235 | setKeyboardHeight(0);
236 | _measure();
237 | }, [_measure]);
238 |
239 | useEffect(() => {
240 | const susbcriptionKeyboardDidShow = Keyboard.addListener(
241 | 'keyboardDidShow',
242 | onKeyboardDidShow
243 | );
244 | const susbcriptionKeyboardDidHide = Keyboard.addListener(
245 | 'keyboardDidHide',
246 | onKeyboardDidHide
247 | );
248 |
249 | return () => {
250 | if (typeof susbcriptionKeyboardDidShow?.remove === 'function') {
251 | susbcriptionKeyboardDidShow.remove();
252 | }
253 |
254 | if (typeof susbcriptionKeyboardDidHide?.remove === 'function') {
255 | susbcriptionKeyboardDidHide.remove();
256 | }
257 | };
258 | }, [onKeyboardDidHide, onKeyboardDidShow]);
259 |
260 | useEffect(() => {
261 | getValue();
262 | }, [getValue, value]);
263 |
264 | const showOrClose = useCallback(() => {
265 | if (!disable) {
266 | const visibleStatus = !visible;
267 |
268 | if (keyboardHeight > 0 && !visibleStatus) {
269 | return Keyboard.dismiss();
270 | }
271 |
272 | _measure();
273 | setVisible(visibleStatus);
274 |
275 | if (data) {
276 | const filterData = excludeData(data);
277 | setListData(filterData);
278 | }
279 |
280 | if (visibleStatus) {
281 | if (onFocus) {
282 | onFocus();
283 | }
284 | } else {
285 | if (onBlur) {
286 | onBlur();
287 | }
288 | }
289 |
290 | if (searchText.length > 0) {
291 | onSearch(searchText);
292 | }
293 | }
294 | // eslint-disable-next-line react-hooks/exhaustive-deps
295 | }, [
296 | disable,
297 | keyboardHeight,
298 | visible,
299 | _measure,
300 | data,
301 | searchText,
302 | onFocus,
303 | onBlur,
304 | ]);
305 |
306 | const onSearch = useCallback(
307 | (text: string) => {
308 | if (text.length > 0) {
309 | const defaultFilterFunction = (e: any) => {
310 | const item = _get(e, searchField || labelField)
311 | ?.toLowerCase()
312 | .replace(/\s/g, '')
313 | .normalize('NFD')
314 | .replace(/[\u0300-\u036f]/g, '');
315 | const key = text
316 | .toLowerCase()
317 | .replace(/\s/g, '')
318 | .normalize('NFD')
319 | .replace(/[\u0300-\u036f]/g, '');
320 |
321 | return item.indexOf(key) >= 0;
322 | };
323 |
324 | const propSearchFunction = (e: any) => {
325 | const labelText = _get(e, searchField || labelField);
326 |
327 | return searchQuery?.(text, labelText);
328 | };
329 |
330 | const dataSearch = data.filter(
331 | searchQuery ? propSearchFunction : defaultFilterFunction
332 | );
333 |
334 | if (excludeSearchItems.length > 0 || excludeItems.length > 0) {
335 | const excludeSearchData = _differenceWith(
336 | dataSearch,
337 | excludeSearchItems,
338 | (obj1, obj2) => _get(obj1, valueField) === _get(obj2, valueField)
339 | );
340 |
341 | const filterData = excludeData(excludeSearchData);
342 | setListData(filterData);
343 | } else {
344 | setListData(dataSearch);
345 | }
346 | } else {
347 | const filterData = excludeData(data);
348 | setListData(filterData);
349 | }
350 | },
351 | [
352 | data,
353 | searchQuery,
354 | excludeSearchItems,
355 | excludeItems,
356 | searchField,
357 | labelField,
358 | valueField,
359 | excludeData,
360 | ]
361 | );
362 |
363 | const onSelect = useCallback(
364 | (item: any) => {
365 | const newCurrentValue = [...currentValue];
366 | const index = newCurrentValue.findIndex(
367 | (e) => e === _get(item, valueField)
368 | );
369 | if (index > -1) {
370 | newCurrentValue.splice(index, 1);
371 | } else {
372 | if (maxSelect) {
373 | if (newCurrentValue.length < maxSelect) {
374 | newCurrentValue.push(_get(item, valueField));
375 | }
376 | } else {
377 | newCurrentValue.push(_get(item, valueField));
378 | }
379 | }
380 |
381 | if (onConfirmSelectItem) {
382 | if (index > -1) {
383 | if (confirmUnSelectItem) {
384 | onConfirmSelectItem(newCurrentValue);
385 | } else {
386 | onChange(newCurrentValue);
387 | }
388 | } else {
389 | if (confirmSelectItem) {
390 | onConfirmSelectItem(newCurrentValue);
391 | } else {
392 | onChange(newCurrentValue);
393 | }
394 | }
395 | } else {
396 | onChange(newCurrentValue);
397 | }
398 |
399 | setKey(Math.random());
400 | },
401 | [
402 | confirmSelectItem,
403 | confirmUnSelectItem,
404 | currentValue,
405 | maxSelect,
406 | onChange,
407 | onConfirmSelectItem,
408 | valueField,
409 | ]
410 | );
411 |
412 | const _renderDropdown = () => {
413 | return (
414 |
420 |
421 | {renderLeftIcon?.(visible)}
422 |
430 | {placeholder}
431 |
432 | {renderRightIcon ? (
433 | renderRightIcon(visible)
434 | ) : (
435 |
443 | )}
444 |
445 |
446 | );
447 | };
448 |
449 | const checkSelected = useCallback(
450 | (item: any) => {
451 | const index = currentValue.findIndex((e) => e === _get(item, valueField));
452 | return index > -1;
453 | },
454 | [currentValue, valueField]
455 | );
456 |
457 | const _renderItem = useCallback(
458 | ({ item, index }: { item: any; index: number }) => {
459 | const selected = checkSelected(item);
460 | _assign(item, { _index: index });
461 | return (
462 | onSelect(item)}
472 | >
473 |
482 | {renderItem ? (
483 | renderItem(item, selected)
484 | ) : (
485 |
486 |
493 | {_get(item, labelField)}
494 |
495 |
496 | )}
497 |
498 |
499 | );
500 | },
501 | [
502 | accessibilityLabel,
503 | activeColor,
504 | checkSelected,
505 | font,
506 | itemAccessibilityLabelField,
507 | itemContainerStyle,
508 | itemTestIDField,
509 | itemTextStyle,
510 | labelField,
511 | onSelect,
512 | renderItem,
513 | ]
514 | );
515 |
516 | const renderSearch = useCallback(() => {
517 | if (search) {
518 | if (renderInputSearch) {
519 | return renderInputSearch((text) => {
520 | if (onChangeText) {
521 | setSearchText(text);
522 | onChangeText(text);
523 | }
524 | onSearch(text);
525 | });
526 | } else {
527 | return (
528 | {
536 | if (onChangeText) {
537 | setSearchText(e);
538 | onChangeText(e);
539 | }
540 | onSearch(e);
541 | }}
542 | showIcon
543 | placeholderTextColor={searchPlaceholderTextColor}
544 | iconStyle={[{ tintColor: iconColor }, iconStyle]}
545 | />
546 | );
547 | }
548 | }
549 | return null;
550 | }, [
551 | accessibilityLabel,
552 | font,
553 | iconColor,
554 | iconStyle,
555 | inputSearchStyle,
556 | onChangeText,
557 | onSearch,
558 | renderInputSearch,
559 | search,
560 | searchPlaceholder,
561 | searchPlaceholderTextColor,
562 | testID,
563 | ]);
564 |
565 | const _renderList = useCallback(
566 | (isTopPosition: boolean) => {
567 | const isInverted = !inverted ? false : isTopPosition;
568 |
569 | const _renderListHelper = () => {
570 | return (
571 | index.toString()}
580 | showsVerticalScrollIndicator={showsVerticalScrollIndicator}
581 | />
582 | );
583 | };
584 |
585 | return (
586 |
587 |
588 | {isInverted && _renderListHelper()}
589 | {renderSearch()}
590 | {!isInverted && _renderListHelper()}
591 |
592 |
593 | );
594 | },
595 | [
596 | _renderItem,
597 | accessibilityLabel,
598 | flatListProps,
599 | listData,
600 | inverted,
601 | renderSearch,
602 | showsVerticalScrollIndicator,
603 | testID,
604 | ]
605 | );
606 |
607 | const _renderModal = useCallback(() => {
608 | if (visible && position) {
609 | const { isFull, width, height, top, bottom, left } = position;
610 |
611 | const onAutoPosition = () => {
612 | if (keyboardHeight > 0) {
613 | return bottom < keyboardHeight + height;
614 | }
615 |
616 | return bottom < (search ? 150 : 100);
617 | };
618 |
619 | if (width && top && bottom) {
620 | const styleVertical: ViewStyle = {
621 | left: left,
622 | maxHeight: maxHeight,
623 | minHeight: minHeight,
624 | };
625 | const isTopPosition =
626 | dropdownPosition === 'auto'
627 | ? onAutoPosition()
628 | : dropdownPosition === 'top';
629 |
630 | let keyboardStyle: ViewStyle = {};
631 |
632 | let extendHeight = !isTopPosition ? top : bottom;
633 | if (
634 | keyboardAvoiding &&
635 | keyboardHeight > 0 &&
636 | isTopPosition &&
637 | dropdownPosition === 'auto'
638 | ) {
639 | extendHeight = keyboardHeight;
640 | }
641 |
642 | return (
643 |
650 |
651 |
659 |
671 |
681 | {_renderList(isTopPosition)}
682 |
683 |
684 |
685 |
686 |
687 | );
688 | }
689 | return null;
690 | }
691 | return null;
692 | }, [
693 | visible,
694 | search,
695 | position,
696 | keyboardHeight,
697 | maxHeight,
698 | minHeight,
699 | dropdownPosition,
700 | keyboardAvoiding,
701 | showOrClose,
702 | styleContainerVertical,
703 | backgroundColor,
704 | containerStyle,
705 | styleHorizontal,
706 | _renderList,
707 | ]);
708 |
709 | const unSelect = (item: any) => {
710 | if (!disable) {
711 | onSelect(item);
712 | }
713 | };
714 |
715 | const _renderItemSelected = (inside: boolean) => {
716 | const list = data.filter((e: any) => {
717 | const check = value?.indexOf(_get(e, valueField));
718 | if (check !== -1) {
719 | return e;
720 | }
721 | });
722 |
723 | return (
724 |
730 | {list.map((e) => {
731 | if (renderSelectedItem) {
732 | return (
733 | unSelect(e)}
742 | >
743 | {renderSelectedItem(e, () => {
744 | unSelect(e);
745 | })}
746 |
747 | );
748 | } else {
749 | return (
750 | unSelect(e)}
759 | >
760 |
766 |
773 | {_get(e, labelField)}
774 |
775 |
781 | ⓧ
782 |
783 |
784 |
785 | );
786 | }
787 | })}
788 |
789 | );
790 | };
791 |
792 | const _renderInside = () => {
793 | return (
794 |
799 | {_renderDropdownInside()}
800 | {_renderModal()}
801 |
802 | );
803 | };
804 |
805 | const _renderDropdownInside = () => {
806 | return (
807 |
813 |
814 | {renderLeftIcon?.()}
815 | {value && value?.length > 0 ? (
816 | _renderItemSelected(true)
817 | ) : (
818 |
825 | {placeholder}
826 |
827 | )}
828 | {renderRightIcon ? (
829 | renderRightIcon()
830 | ) : (
831 |
839 | )}
840 |
841 |
842 | );
843 | };
844 |
845 | if (inside) {
846 | return _renderInside();
847 | }
848 |
849 | return (
850 | <>
851 |
856 | {_renderDropdown()}
857 | {_renderModal()}
858 |
859 | {(!visible || alwaysRenderSelectedItem) &&
860 | visibleSelectedItem &&
861 | _renderItemSelected(false)}
862 | >
863 | );
864 | });
865 |
866 | export default MultiSelectComponent;
867 |
--------------------------------------------------------------------------------
/src/components/MultiSelect/model.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react';
2 | import type {
3 | FlatListProps,
4 | ImageStyle,
5 | StyleProp,
6 | TextStyle,
7 | ViewStyle,
8 | } from 'react-native';
9 | import { TextProps } from 'react-native';
10 |
11 | export interface IMultiSelectRef {
12 | open: () => void;
13 | close: () => void;
14 | }
15 |
16 | export interface MultiSelectProps {
17 | ref?:
18 | | React.RefObject
19 | | React.MutableRefObject
20 | | null
21 | | undefined;
22 | testID?: string;
23 | itemTestIDField?: string;
24 | style?: StyleProp;
25 | containerStyle?: StyleProp;
26 | placeholderStyle?: StyleProp;
27 | inputSearchStyle?: StyleProp;
28 | selectedStyle?: StyleProp;
29 | selectedTextProps?: TextProps;
30 | selectedTextStyle?: StyleProp;
31 | itemContainerStyle?: StyleProp;
32 | itemTextStyle?: StyleProp;
33 | iconStyle?: StyleProp;
34 | maxHeight?: number;
35 | minHeight?: number;
36 | maxSelect?: number;
37 | fontFamily?: string;
38 | iconColor?: string;
39 | activeColor?: string;
40 | data: T[];
41 | value?: string[] | null | undefined;
42 | placeholder?: string;
43 | labelField: keyof T;
44 | valueField: keyof T;
45 | searchField?: keyof T;
46 | search?: boolean;
47 | disable?: boolean;
48 | showsVerticalScrollIndicator?: boolean;
49 | searchPlaceholder?: string;
50 | searchPlaceholderTextColor?: string;
51 | dropdownPosition?: 'auto' | 'top' | 'bottom';
52 | flatListProps?: Omit, 'renderItem' | 'data'>;
53 | alwaysRenderSelectedItem?: boolean;
54 | visibleSelectedItem?: boolean;
55 | keyboardAvoiding?: boolean;
56 | inside?: boolean;
57 | backgroundColor?: string;
58 | confirmSelectItem?: boolean;
59 | confirmUnSelectItem?: boolean;
60 | accessibilityLabel?: string;
61 | itemAccessibilityLabelField?: string;
62 | inverted?: boolean;
63 | mode?: 'default' | 'modal' | 'auto';
64 | excludeItems?: T[];
65 | excludeSearchItems?: T[];
66 | onChange: (value: string[]) => void;
67 | renderLeftIcon?: (visible?: boolean) => React.ReactElement | null;
68 | renderRightIcon?: (visible?: boolean) => React.ReactElement | null;
69 | renderItem?: (item: T, selected?: boolean) => React.ReactElement | null;
70 | renderSelectedItem?: (
71 | item: T,
72 | unSelect?: (item: T) => void
73 | ) => React.ReactElement | null;
74 | renderInputSearch?: (
75 | onSearch: (text: string) => void
76 | ) => React.ReactElement | null;
77 | onFocus?: () => void;
78 | onBlur?: () => void;
79 | searchQuery?: (keyword: string, labelValue: string) => boolean;
80 | onChangeText?: (search: string) => void;
81 | onConfirmSelectItem?: (item: any) => void;
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/MultiSelect/styles.ts:
--------------------------------------------------------------------------------
1 | import { I18nManager, StyleSheet } from 'react-native';
2 |
3 | export const styles = StyleSheet.create({
4 | mainWrap: {
5 | justifyContent: 'center',
6 | },
7 | container: {
8 | flexShrink: 1,
9 | backgroundColor: 'white',
10 | borderWidth: 0.5,
11 | borderColor: '#EEEEEE',
12 | shadowColor: '#000',
13 | shadowOffset: {
14 | width: 0,
15 | height: 1,
16 | },
17 | shadowOpacity: 0.2,
18 | shadowRadius: 1.41,
19 | elevation: 2,
20 | },
21 | flex1: {
22 | flex: 1,
23 | },
24 | flexShrink: {
25 | flexShrink: 1,
26 | },
27 | wrapTop: {
28 | justifyContent: 'flex-end',
29 | },
30 | dropdown: {
31 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
32 | justifyContent: 'space-between',
33 | alignItems: 'center',
34 | },
35 | dropdownInside: {
36 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
37 | justifyContent: 'space-between',
38 | alignItems: 'center',
39 | minHeight: 35,
40 | },
41 | title: {
42 | marginVertical: 5,
43 | fontSize: 16,
44 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
45 | },
46 | wrapItem: {
47 | marginBottom: 0.5,
48 | },
49 | item: {
50 | padding: 17,
51 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
52 | justifyContent: 'space-between',
53 | alignItems: 'center',
54 | },
55 | textItem: {
56 | flex: 1,
57 | fontSize: 16,
58 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
59 | },
60 | icon: {
61 | width: 20,
62 | height: 20,
63 | },
64 | input: {
65 | borderWidth: 0.5,
66 | borderColor: '#DDDDDD',
67 | paddingHorizontal: 8,
68 | marginBottom: 8,
69 | margin: 6,
70 | height: 45,
71 | },
72 | rowSelectedItem: {
73 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
74 | flexWrap: 'wrap',
75 | },
76 | selectedItem: {
77 | padding: 8,
78 | alignItems: 'center',
79 | justifyContent: 'center',
80 | borderWidth: 0.5,
81 | borderColor: 'gray',
82 | paddingHorizontal: 8,
83 | marginVertical: 6,
84 | marginRight: 8,
85 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
86 | },
87 | selectedTextItem: {
88 | marginLeft: 5,
89 | color: 'gray',
90 | fontSize: 16,
91 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
92 | },
93 | selectedTextLeftItem: {
94 | fontSize: 12,
95 | color: 'gray',
96 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
97 | },
98 | fullScreen: {
99 | alignItems: 'center',
100 | justifyContent: 'center',
101 | },
102 | });
103 |
--------------------------------------------------------------------------------
/src/components/SelectCountry/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useImperativeHandle, useMemo, useRef } from 'react';
2 | import { Image, Text, View } from 'react-native';
3 | import Dropdown from '../Dropdown';
4 | import { ISelectCountryRef, SelectCountryProps } from './model';
5 | import { styles } from './styles';
6 |
7 | const SelectCountryComponent = React.forwardRef<
8 | ISelectCountryRef,
9 | SelectCountryProps
10 | >((props, currentRef) => {
11 | const {
12 | data,
13 | value,
14 | valueField,
15 | labelField,
16 | imageField,
17 | selectedTextStyle,
18 | imageStyle,
19 | } = props;
20 | const ref: any = useRef(null);
21 |
22 | useImperativeHandle(currentRef, () => {
23 | return { open: eventOpen, close: eventClose };
24 | });
25 |
26 | const eventOpen = () => {
27 | ref.current.open();
28 | };
29 |
30 | const eventClose = () => {
31 | ref.current.close();
32 | };
33 |
34 | const _renderItem = (item: any) => {
35 | return (
36 |
37 |
38 |
39 | {item[labelField]}
40 |
41 |
42 | );
43 | };
44 |
45 | const selectItem: any = useMemo(() => {
46 | const index = data.findIndex((e: any) => e[valueField] === value);
47 | return data[index];
48 | }, [data, valueField, value]);
49 |
50 | return (
51 | {
56 | if (selectItem?.image) {
57 | return (
58 |
62 | );
63 | } else {
64 | return null;
65 | }
66 | }}
67 | />
68 | );
69 | });
70 |
71 | export default SelectCountryComponent;
72 |
--------------------------------------------------------------------------------
/src/components/SelectCountry/model.ts:
--------------------------------------------------------------------------------
1 | import type { ImageStyle } from 'react-native';
2 | import type { DropdownProps } from '../Dropdown/model';
3 |
4 | export type ISelectCountryRef = {
5 | open: () => void;
6 | close: () => void;
7 | };
8 |
9 | export interface SelectCountryProps extends DropdownProps {
10 | imageField: string;
11 | imageStyle?: ImageStyle;
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/SelectCountry/styles.ts:
--------------------------------------------------------------------------------
1 | import { I18nManager, StyleSheet } from 'react-native';
2 |
3 | export const styles = StyleSheet.create({
4 | dropdown: {
5 | width: 58,
6 | paddingHorizontal: 6,
7 | height: 26,
8 | },
9 | container: {
10 | width: 60,
11 | },
12 | item: {
13 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
14 | padding: 6,
15 | alignItems: 'center',
16 | },
17 | image: {
18 | width: 20,
19 | height: 20,
20 | marginRight: 3,
21 | marginVertical: 4,
22 | },
23 | selectedTextStyle: {
24 | flex: 1,
25 | fontSize: 12,
26 | writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/src/components/TextInput/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-shadow */
2 | import React, { useEffect, useState } from 'react';
3 | import {
4 | Image,
5 | TextInput,
6 | TouchableOpacity,
7 | View,
8 | TouchableWithoutFeedback,
9 | StyleSheet,
10 | } from 'react-native';
11 | import type { CTextInput } from './model';
12 | import { styles } from './styles';
13 |
14 | const ic_close = require('../../assets/close.png');
15 |
16 | const TextInputComponent: CTextInput = (props) => {
17 | const {
18 | fontFamily,
19 | style,
20 | value,
21 | placeholderTextColor = '#000',
22 | placeholder = '',
23 | showIcon,
24 | inputStyle,
25 | iconStyle,
26 | onChangeText = (_value: string) => {},
27 | renderLeftIcon,
28 | renderRightIcon,
29 | } = props;
30 |
31 | const [text, setText] = useState('');
32 |
33 | useEffect(() => {
34 | if (value) {
35 | setText(value);
36 | }
37 | }, [value]);
38 |
39 | const onChange = (text: string) => {
40 | setText(text);
41 | onChangeText(text);
42 | };
43 |
44 | const _renderRightIcon = () => {
45 | if (showIcon) {
46 | if (renderRightIcon) {
47 | return renderRightIcon();
48 | }
49 | if (text.length > 0) {
50 | return (
51 | onChange('')}>
52 |
56 |
57 | );
58 | }
59 | return null;
60 | }
61 | return null;
62 | };
63 |
64 | const font = () => {
65 | if (fontFamily) {
66 | return {
67 | fontFamily: fontFamily,
68 | };
69 | } else {
70 | return {};
71 | }
72 | };
73 |
74 | return (
75 |
76 |
77 |
78 | {renderLeftIcon?.()}
79 |
87 | {_renderRightIcon()}
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default TextInputComponent;
95 |
--------------------------------------------------------------------------------
/src/components/TextInput/model.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react';
2 | import type {
3 | ImageStyle,
4 | StyleProp,
5 | TextInputProps,
6 | TextStyle,
7 | ViewStyle,
8 | } from 'react-native';
9 |
10 | interface Props extends TextInputProps {
11 | fontFamily?: string;
12 | style?: StyleProp;
13 | inputStyle?: StyleProp;
14 | iconStyle?: StyleProp;
15 | showIcon?: boolean;
16 | renderRightIcon?: () => React.ReactElement | null;
17 | renderLeftIcon?: () => React.ReactElement | null;
18 | }
19 |
20 | export type CTextInput = React.FC;
21 |
--------------------------------------------------------------------------------
/src/components/TextInput/styles.ts:
--------------------------------------------------------------------------------
1 | import { I18nManager, StyleSheet } from 'react-native';
2 |
3 | export const styles = StyleSheet.create({
4 | container: {
5 | backgroundColor: 'white',
6 | borderRadius: 8,
7 | padding: 12,
8 | justifyContent: 'center',
9 | },
10 | textInput: {
11 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
12 | alignItems: 'center',
13 | flex: 1,
14 | },
15 | input: {
16 | fontSize: 16,
17 | flex: 1,
18 | textAlign: I18nManager.isRTL ? 'right' : 'left',
19 | },
20 | label: {
21 | marginBottom: 4,
22 | fontSize: 16,
23 | },
24 | row: {
25 | flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
26 | },
27 | icon: {
28 | width: 20,
29 | height: 20,
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import Dropdown from './components/Dropdown';
2 | import MultiSelect from './components/MultiSelect';
3 | import SelectCountry from './components/SelectCountry';
4 | import { IDropdownRef } from './components/Dropdown/model';
5 | import { IMultiSelectRef } from './components/MultiSelect/model';
6 | import { ISelectCountryRef } from './components/SelectCountry/model';
7 |
8 | export {
9 | Dropdown,
10 | MultiSelect,
11 | SelectCountry,
12 | IDropdownRef,
13 | IMultiSelectRef,
14 | ISelectCountryRef,
15 | };
16 |
--------------------------------------------------------------------------------
/src/toolkits/index.ts:
--------------------------------------------------------------------------------
1 | import { Platform, PixelRatio, Dimensions } from 'react-native';
2 | import type { IUseDetectDevice } from './model';
3 |
4 | const { width, height } = Dimensions.get('window');
5 |
6 | const isTablet = () => {
7 | let pixelDensity = PixelRatio.get();
8 | const adjustedWidth = width * pixelDensity;
9 | const adjustedHeight = height * pixelDensity;
10 | if (pixelDensity < 2 && (adjustedWidth >= 1000 || adjustedHeight >= 1000)) {
11 | return true;
12 | } else {
13 | return (
14 | pixelDensity === 2 && (adjustedWidth >= 1824 || adjustedHeight >= 1824)
15 | );
16 | }
17 | };
18 |
19 | const useDetectDevice: IUseDetectDevice = {
20 | isAndroid: Platform.OS === 'android',
21 | isIOS: Platform.OS === 'ios',
22 | isTablet: isTablet(),
23 | };
24 |
25 | export { useDetectDevice };
26 |
--------------------------------------------------------------------------------
/src/toolkits/model.ts:
--------------------------------------------------------------------------------
1 | export interface IUseDetectDevice {
2 | isAndroid: boolean;
3 | isIOS: boolean;
4 | isTablet: boolean;
5 | }
6 |
--------------------------------------------------------------------------------
/src/useDeviceOrientation.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-shadow */
2 | import { useEffect, useState } from 'react';
3 | import { Dimensions, ScaledSize } from 'react-native';
4 |
5 | const isOrientationPortrait = ({ width, height }: ScaledSize) =>
6 | height >= width;
7 | const isOrientationLandscape = ({ width, height }: ScaledSize) =>
8 | width >= height;
9 |
10 | export function useDeviceOrientation() {
11 | const screen = Dimensions.get('screen');
12 | const initialState = {
13 | portrait: isOrientationPortrait(screen),
14 | landscape: isOrientationLandscape(screen),
15 | };
16 |
17 | const [orientation, setOrientation] = useState(initialState);
18 |
19 | useEffect(() => {
20 | const onChange = ({ screen }: { screen: ScaledSize }) => {
21 | setOrientation({
22 | portrait: isOrientationPortrait(screen),
23 | landscape: isOrientationLandscape(screen),
24 | });
25 | };
26 |
27 | const subscription = Dimensions.addEventListener('change', onChange);
28 |
29 | return () => {
30 | if (typeof subscription?.remove === 'function') {
31 | subscription.remove();
32 | }
33 | };
34 | }, []);
35 |
36 | return orientation.portrait ? 'PORTRAIT' : 'LANDSCAPE';
37 | }
38 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "extends": "./tsconfig",
4 | "exclude": ["example"]
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "react-native-element-dropdown": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "importsNotUsedAsValues": "remove",
11 | "forceConsistentCasingInFileNames": true,
12 | "jsx": "react",
13 | "lib": ["esnext"],
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "noFallthroughCasesInSwitch": true,
17 | "noImplicitReturns": true,
18 | "noImplicitUseStrict": false,
19 | "noStrictGenericChecks": false,
20 | "noUncheckedIndexedAccess": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "resolveJsonModule": true,
24 | "skipLibCheck": true,
25 | "strict": true,
26 | "target": "esnext"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------