├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .prettierrc
├── .travis.yml
├── example
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
│ ├── App.js
│ ├── DemoDropdown.jsx
│ ├── index.css
│ └── index.js
├── package-lock.json
└── src
├── .eslintrc
└── index.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | node_modules/
4 | .snapshots/
5 | *.min.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "standard",
5 | "standard-react",
6 | "plugin:prettier/recommended",
7 | "prettier/standard",
8 | "prettier/react"
9 | ],
10 | "env": {
11 | "node": true
12 | },
13 | "parserOptions": {
14 | "ecmaVersion": 2020,
15 | "ecmaFeatures": {
16 | "legacyDecorators": true,
17 | "jsx": true
18 | }
19 | },
20 | "settings": {
21 | "react": {
22 | "version": "16"
23 | }
24 | },
25 | "rules": {
26 | "space-before-function-paren": 0,
27 | "react/prop-types": 0,
28 | "react/jsx-handler-names": 0,
29 | "react/jsx-fragments": 0,
30 | "react/no-unused-prop-types": 0,
31 | "import/export": 0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 | .rpt2_cache
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "jsxSingleQuote": true,
4 | "semi": false,
5 | "tabWidth": 2,
6 | "bracketSpacing": true,
7 | "jsxBracketSameLine": false,
8 | "arrowParens": "always",
9 | "trailingComma": "none"
10 | }
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 12
4 | - 10
5 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-mui-multiselect-dropdown-example",
3 | "homepage": ".",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
8 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
9 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
10 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
11 | },
12 | "dependencies": {
13 | "react": "file:../node_modules/react",
14 | "@material-ui/core": "^4.11.0",
15 | "@material-ui/icons": "^4.9.1",
16 | "fontsource-roboto": "^2.1.4",
17 | "classnames": "^2.2.6",
18 | "react-dom": "file:../node_modules/react-dom",
19 | "react-scripts": "file:../node_modules/react-scripts",
20 | "react-mui-multiselect-dropdown": "file:.."
21 | },
22 | "devDependencies": {
23 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3"
24 | },
25 | "eslintConfig": {
26 | "extends": "react-app"
27 | },
28 | "browserslist": [
29 | ">0.2%",
30 | "not dead",
31 | "not ie <= 11",
32 | "not op_mini all"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asherb-star/react-mui-multiselect-dropdown/334210c29d91009e7668ba55cbcee42b563ae969/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
16 |
17 |
18 |
27 | react-mui-multiselect-dropdown
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "react-mui-multiselect-dropdown",
3 | "name": "react-mui-multiselect-dropdown",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import DemoDropdown from './DemoDropdown'
4 |
5 | const App = () => {
6 | return
7 | }
8 |
9 | export default App
10 |
--------------------------------------------------------------------------------
/example/src/DemoDropdown.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Dropdown from 'react-mui-multiselect-dropdown'
3 | import { Paper, Grid, Typography, makeStyles, Button } from '@material-ui/core'
4 |
5 | const useStyles = makeStyles((theme) => ({
6 | error: {
7 | color: theme.palette.error.dark,
8 | fontSize: '1em'
9 | },
10 | checkBox: {
11 | color: 'Purple'
12 | }
13 | }))
14 |
15 | function DemoDropdown() {
16 | const [selectedEmployee, setSelectedEmployees] = useState([])
17 | const [selectedSkills, setSelectedSkills] = useState([])
18 | const [selectedCities, setSelectedCities] = useState([])
19 |
20 | const [skills, setSkills] = useState([])
21 | const [employees, setEmployees] = useState([])
22 | const [cities, setCities] = useState([])
23 |
24 | const populateData = () => {
25 | const employeesData = [
26 | { id: 1, name: 'Bhushan' },
27 | { id: 2, name: 'Vishal' },
28 | { id: 3, name: 'Ravindra' }
29 | ]
30 | employees.forEach((v, i) => {
31 | v['path'] = `https://source.unsplash.com/random/${i}`
32 | })
33 |
34 | setEmployees(employeesData)
35 | const skillsData = [
36 | { id: 1, name: 'React Js' },
37 | { id: 2, name: 'Angular' },
38 | { id: 3, name: 'Node JS' }
39 | ]
40 |
41 | setSkills(skillsData)
42 | const SelectedEmp = []
43 | setSelectedEmployees(SelectedEmp)
44 |
45 | const SelectedSkills = []
46 | setSelectedSkills(SelectedSkills)
47 |
48 | const cities = [
49 | { id: 1, city: 'MUMBAI' },
50 | { id: 2, city: 'PUNE' },
51 | { id: 3, city: 'NAGPUR' }
52 | ]
53 |
54 | cities.forEach((v, i) => {
55 | v['path'] = `https://source.unsplash.com/random/${i}`
56 | })
57 |
58 | setCities(cities)
59 | }
60 |
61 | useEffect(() => {
62 | populateData()
63 | }, [])
64 |
65 | const classes = useStyles()
66 |
67 | return (
68 | <>
69 |
70 |
71 |
72 |
73 |
77 | Single Select
78 |
79 | {
97 | setSelectedEmployees(records)
98 | }}
99 | onDeleteItem={(deleted) => {
100 | console.log('deleted', deleted)
101 | }}
102 | />
103 |
107 | Multi Select
108 |
109 | {
129 | setSelectedSkills(records)
130 | }}
131 | onDeleteItem={(deleted) => {
132 | console.log('deleted', deleted)
133 | }}
134 | />
135 |
139 | Select with Image
140 |
141 | {
159 | setSelectedCities(city)
160 | }}
161 | onDeleteItem={(deleted) => {
162 | console.log('deleted', deleted)
163 | }}
164 | />
165 |
166 |
167 |
168 |
169 | >
170 | )
171 | }
172 |
173 | export default DemoDropdown
174 |
--------------------------------------------------------------------------------
/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import './index.css'
2 |
3 | import React from 'react'
4 | import ReactDOM from 'react-dom'
5 | import App from './App'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react'
2 | import {
3 | Input,
4 | InputAdornment,
5 | ButtonBase,
6 | Menu,
7 | MenuItem,
8 | makeStyles,
9 | Chip,
10 | Icon,
11 | FormHelperText,
12 | Avatar,
13 | Typography
14 | } from '@material-ui/core'
15 | import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'
16 | import PropTypes from 'prop-types'
17 | import ClearIcon from '@material-ui/icons/Clear'
18 | import CheckBoxOutlineBlankOutlinedIcon from '@material-ui/icons/CheckBoxOutlineBlankOutlined'
19 | import CheckBoxIcon from '@material-ui/icons/CheckBox'
20 | import classNames from 'classnames'
21 |
22 | const useStyles = makeStyles((theme) => ({
23 | selectedItemsWrapper: {
24 | display: 'flex',
25 | flex: 1,
26 | flexWrap: 'wrap'
27 | },
28 | selectedList: {
29 | listStyle: 'none',
30 | padding: '3px 0'
31 | },
32 | checkBox: {
33 | color: theme.palette.primary.main
34 | },
35 | chip: {
36 | marginRight: '0.5em'
37 | },
38 | Menupaper: {
39 | overflowY: 'hidden !important'
40 | },
41 | artwork: {
42 | width: '1em',
43 | height: '1em',
44 | marginLeft: '3px',
45 | marginRight: '5px'
46 | }
47 | }))
48 |
49 | function Dropdown(Props) {
50 | const [open, setOpen] = useState(false)
51 | const [filteredValues, setFilteredValues] = useState([])
52 | const [selectedItems, setSelectedItems] = useState([])
53 | const [searchKeyword, setSearchKeyword] = useState('')
54 |
55 | const inputRef = useRef()
56 | const wrapperRef = useRef()
57 | const classes = useStyles()
58 |
59 | const {
60 | fullWidth,
61 | title,
62 | searchPlaceHolder,
63 | searchable,
64 | data,
65 | onItemClick,
66 | itemId,
67 | itemLabel,
68 | multiple,
69 | showAllButton,
70 | simpleValue,
71 | itemValue,
72 | onDeleteItem,
73 | searchByValue,
74 | disabled,
75 | error,
76 | errorText,
77 | customStyles,
78 | selectedValues,
79 | showImage,
80 | imageLabel,
81 | allItemValue,
82 | allItemId,
83 | notFoundText
84 | } = Props
85 |
86 | useEffect(() => {
87 | if (data && data.length > 0 && multiple && showAllButton) {
88 | data.unshift({ [allItemId]: allItemValue, [itemLabel]: 'All' })
89 | }
90 | const values =
91 | !multiple && selectedValues.length > 1
92 | ? selectedValues.filter((_, i) => i === 0)
93 | : selectedValues.concat()
94 | setSelectedItems(values)
95 | setFilteredValues(data)
96 | }, [data])
97 |
98 | const handleClose = () => {
99 | setOpen(false)
100 | handleSearchKeyword('')
101 | }
102 |
103 | const onItemSelect = async (item) => {
104 | let selected = selectedItems
105 | let isAllSelected = false
106 | if (multiple) {
107 | if (item[allItemId] === allItemValue) {
108 | item = filteredValues.filter((v) => v[allItemId] !== allItemValue)
109 | isAllSelected = true
110 | }
111 | if (
112 | isAllSelected &&
113 | item.length ===
114 | selectedItems.filter((v) => v[allItemId] !== allItemValue).length
115 | ) {
116 | selected = []
117 | } else if (isItemSelected(item)) {
118 | selected = selected.filter((v) => v[itemId] !== item[itemId])
119 | populateDeletedValue(item)
120 | } else {
121 | selected = isAllSelected ? item : [...selectedItems, item]
122 | }
123 | } else {
124 | selected = [item]
125 | }
126 | setSelectedItems(selected)
127 | populateSelectedValues(selected)
128 | if (!multiple) {
129 | handleClose()
130 | }
131 | }
132 |
133 | const handleRemoveItem = (item) => {
134 | const filteredSelectedItems = selectedItems.filter(
135 | (v) => v[itemId] !== item[itemId]
136 | )
137 | setSelectedItems(filteredSelectedItems)
138 | populateSelectedValues(filteredSelectedItems)
139 | populateDeletedValue(item)
140 | }
141 |
142 | const removeAllSelectedItems = () => {
143 | setSelectedItems([])
144 | populateSelectedValues([])
145 | }
146 |
147 | const isItemSelected = (item) => {
148 | if (
149 | item[allItemId] === allItemValue &&
150 | selectedItems.length ===
151 | data.filter((v) => v[allItemId] !== allItemValue).length
152 | ) {
153 | return true
154 | }
155 | return selectedItems.filter((v) => v[itemId] === item[itemId]).length > 0
156 | }
157 |
158 | const populateSelectedValues = (values) => {
159 | let valuesToPopulate
160 |
161 | if (values && values.length === 0) {
162 | valuesToPopulate = simpleValue ? null : []
163 | } else {
164 | valuesToPopulate = simpleValue ? values.map((v) => v[itemValue]) : values
165 | valuesToPopulate = multiple ? valuesToPopulate : valuesToPopulate[0]
166 | }
167 | onItemClick(valuesToPopulate)
168 | }
169 |
170 | const populateDeletedValue = (item) => {
171 | const deletedValue = simpleValue ? item[itemValue] : item
172 | onDeleteItem(deletedValue)
173 | }
174 |
175 | const handleSearch = (keyword) => {
176 | keyword = keyword ? keyword.toLowerCase() : keyword
177 | let filtredResult = data.filter((v) =>
178 | v[searchByValue].toString().toLowerCase().includes(keyword)
179 | )
180 | const ignoreResult = data.filter((v) => v[allItemId] === allItemValue)
181 | if (JSON.stringify(filtredResult) === JSON.stringify(ignoreResult)) {
182 | filtredResult = []
183 | }
184 | setFilteredValues(filtredResult)
185 | }
186 |
187 | const handleSearchKeyword = (keyword) => {
188 | setSearchKeyword(keyword)
189 | handleSearch(keyword)
190 | }
191 |
192 | const searchSection = () =>
193 | searchable && (
194 |
195 | {
200 | handleSearchKeyword(e.target.value)
201 | }}
202 | style={{
203 | minWidth: wrapperRef.current
204 | ? wrapperRef.current.getBoundingClientRect().width
205 | : undefined,
206 | paddingLeft: '13px'
207 | }}
208 | endAdornment={
209 |
210 | {searchKeyword && (
211 | {
215 | setSearchKeyword('')
216 | handleSearchKeyword('')
217 | }}
218 | >
219 |
220 |
221 | )}
222 |
223 | }
224 | />
225 |
226 | )
227 |
228 | const optionsSection = () => (
229 |
236 | {filteredValues && filteredValues.length > 0 ? (
237 | filteredValues.map((v) => (
238 |
285 | ))
286 | ) : (
287 |
288 | )}
289 |
290 | )
291 |
292 | const selectedOptionSetion = () => (
293 | {
296 | e.stopPropagation()
297 | !disabled && setOpen(true)
298 | }}
299 | style={{
300 | cursor: disabled ? '' : 'pointer',
301 | height: selectedItems && selectedItems.length > 0 ? 'auto' : '1.3rem'
302 | }}
303 | >
304 | {selectedItems &&
305 | selectedItems.length > 0 &&
306 | selectedItems.map((item) => (
307 |
308 | {multiple ? (
309 | : undefined
312 | }
313 | className={classes.chip}
314 | label={item[itemLabel]}
315 | onDelete={() => {
316 | handleRemoveItem(item)
317 | }}
318 | />
319 | ) : (
320 |
321 | {showImage && (
322 |
327 | )}
328 | {item[itemLabel]}
329 |
330 | )}
331 |
332 | ))}
333 |
334 | )
335 |
336 | return (
337 |
338 |
{title}
339 |
349 | {selectedItems && selectedItems.length > 0 && (
350 |
{
354 | removeAllSelectedItems()
355 | }}
356 | >
357 |
358 |
359 | )}
360 |
361 |
{
365 | setOpen(true)
366 | }}
367 | >
368 |
369 |
370 |
371 | }
372 | />
373 | {error && errorText && (
374 |
375 | {errorText}
376 |
377 | )}
378 | {open && (
379 |
400 | )}
401 |
402 | )
403 | }
404 |
405 | Dropdown.propTypes = {
406 | fullWidth: PropTypes.bool,
407 | searchable: PropTypes.bool,
408 | title: PropTypes.string,
409 | searchPlaceHolder: PropTypes.string,
410 | data: PropTypes.array.isRequired,
411 | onItemClick: PropTypes.func.isRequired,
412 | itemLabel: PropTypes.string.isRequired,
413 | itemId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
414 | allItemId: PropTypes.string,
415 | allItemValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
416 | multiple: PropTypes.bool,
417 | showAllButton: PropTypes.bool,
418 | simpleValue: PropTypes.bool,
419 | itemValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
420 | onDeleteItem: PropTypes.func,
421 | searchByValue: PropTypes.string,
422 | disabled: PropTypes.bool,
423 | error: PropTypes.bool,
424 | showImage: PropTypes.bool,
425 | selectedValues: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
426 | imageLabel: PropTypes.string,
427 | errorText: PropTypes.string,
428 | notFoundText: PropTypes.string,
429 | customStyles: PropTypes.shape({
430 | checkBox: PropTypes.string,
431 | error: PropTypes.string
432 | })
433 | }
434 | Dropdown.defaultProps = {
435 | fullWidth: false,
436 | searchable: false,
437 | title: 'Dropdown',
438 | searchPlaceHolder: 'Search result',
439 | itemId: 'id',
440 | allItemId: 'id',
441 | allItemValue: -1,
442 | multiple: false,
443 | showAllButton: true,
444 | simpleValue: false,
445 | itemValue: 'id',
446 | onDeleteItem: (a) => a,
447 | searchByValue: 'name',
448 | disabled: false,
449 | error: false,
450 | errorText: 'Error',
451 | selectedValues: [],
452 | showImage: false,
453 | imageLabel: 'url',
454 | notFoundText: 'Not Found',
455 | customStyles: {
456 | checkBox: '',
457 | error: ''
458 | }
459 | }
460 |
461 | export default Dropdown
462 |
--------------------------------------------------------------------------------