Skip to content

Commit fc49fee

Browse files
feat(MultiSelect): make MultiSelectDropdown optionally searchable (#885)
1 parent d59fb30 commit fc49fee

File tree

4 files changed

+92
-28
lines changed

4 files changed

+92
-28
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
### 6.3.9 (2022-11-02)
4+
5+
- [885](https://github.com/influxdata/clockface/pull/885): (MultiSelect): make MultiSelectDropdown optionally searchable through `isSearchable` prop
6+
37
### 6.3.8 (2022-10-04)
48

59
- [857](https://github.com/influxdata/clockface/pull/857): (TimeInput): Fix the dropdown button width to accomodate for multi-character units
@@ -39,6 +43,7 @@
3943
### 6.2.0 (2022-08-24)
4044

4145
- [814](https://github.com/influxdata/clockface/pull/814): Added Collapse functionality to the Draggable Resizer
46+
4247
### 6.1.1 (2022-08-17)
4348

4449
- [823](https://github.com/influxdata/clockface/pull/823): Typeahead Dropdown select text on focus works on all browsers
@@ -55,7 +60,6 @@
5560

5661
- [804](https://github.com/influxdata/clockface/pull/804): Add DoubleCaretVertical icon and trailingIcon prop to Dropdown.Button
5762

58-
5963
### 6.0.0 (2022-08-01)
6064

6165
- [812](https://github.com/influxdata/clockface/pull/812): Remove unused MenuDropdown code

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@influxdata/clockface",
3-
"version": "6.3.8",
3+
"version": "6.3.9",
44
"license": "MIT",
55
"main": "dist/index.js",
66
"style": "dist/index.css",

src/Components/Dropdowns/Composed/MultiSelectDropdown.tsx

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Libraries
2-
import React, {MouseEvent, forwardRef} from 'react'
2+
import React, {MouseEvent, forwardRef, useState} from 'react'
33

44
// Components
55
import {Dropdown, DropdownRef} from '../'
@@ -17,6 +17,7 @@ import {
1717
ComponentStatus,
1818
StandardFunctionProps,
1919
} from '../../../Types'
20+
import {Input} from '../../Inputs'
2021

2122
export interface MultiSelectDropdownProps extends StandardFunctionProps {
2223
/** Text to render in button as currently selected option */
@@ -43,6 +44,9 @@ export interface MultiSelectDropdownProps extends StandardFunctionProps {
4344
menuMaxHeight?: number
4445
/** Renders the menu element above the button instead of below */
4546
dropUp?: boolean
47+
/** Enables the search bar in the dropdown menu */
48+
isSearchable?: boolean
49+
searchbarInputPlaceholder?: string
4650
}
4751

4852
export type MultiSelectDropdownRef = DropdownRef
@@ -69,13 +73,17 @@ export const MultiSelectDropdown = forwardRef<
6973
buttonStatus = ComponentStatus.Default,
7074
menuMaxHeight,
7175
selectedOptions,
76+
isSearchable = false,
77+
searchbarInputPlaceholder = 'Search',
7278
},
7379
ref
7480
) => {
7581
const buttonText = selectedOptions.length
7682
? selectedOptions.join(', ')
7783
: emptyText
7884

85+
const [filterString, setFilterString] = useState('')
86+
7987
const button = (
8088
active: boolean,
8189
onClick: (e: MouseEvent<HTMLElement>) => void
@@ -92,32 +100,79 @@ export const MultiSelectDropdown = forwardRef<
92100
</Dropdown.Button>
93101
)
94102

103+
const NoResults = () => (
104+
<Dropdown.Item
105+
key="no-values-in-filter"
106+
testID="nothing-in-filter-typeAhead"
107+
disabled={true}
108+
>
109+
{filterString.length > 0
110+
? `no matches for ${filterString}`
111+
: 'No results'}
112+
</Dropdown.Item>
113+
)
114+
115+
const handleFiltering = (e: any) => {
116+
const filterStr = e.currentTarget.value
117+
setFilterString(filterStr)
118+
}
119+
120+
const clearFilter = () => {
121+
setFilterString('')
122+
}
123+
95124
const menu = () => (
96-
<Dropdown.Menu theme={menuTheme} maxHeight={menuMaxHeight}>
97-
{options.map(o => {
98-
if (o === DROPDOWN_DIVIDER_SHORTCODE) {
99-
return <Dropdown.Divider key={o} />
100-
}
101-
102-
if (o.includes(DROPDOWN_DIVIDER_SHORTCODE)) {
103-
const dividerText = o.replace(DROPDOWN_DIVIDER_SHORTCODE, '')
104-
return <Dropdown.Divider key={o} text={dividerText} />
105-
}
106-
107-
return (
108-
<Dropdown.Item
109-
key={o}
110-
type={indicator}
111-
value={o}
112-
title={o}
113-
selected={selectedOptions.includes(o)}
114-
onClick={onSelect}
115-
>
116-
{o}
117-
</Dropdown.Item>
118-
)
119-
})}
120-
</Dropdown.Menu>
125+
<>
126+
{isSearchable && (
127+
<Dropdown.Menu theme={menuTheme} style={{paddingBottom: 0}}>
128+
<Input
129+
placeholder={searchbarInputPlaceholder}
130+
value={filterString}
131+
onChange={handleFiltering}
132+
onClear={clearFilter}
133+
/>
134+
</Dropdown.Menu>
135+
)}
136+
<Dropdown.Menu theme={menuTheme} maxHeight={menuMaxHeight}>
137+
{options.map(o => {
138+
// case-insensitive search
139+
if (
140+
isSearchable &&
141+
!o.toUpperCase().includes(filterString.toUpperCase())
142+
) {
143+
return
144+
}
145+
146+
if (o === DROPDOWN_DIVIDER_SHORTCODE) {
147+
return <Dropdown.Divider key={o} />
148+
}
149+
150+
if (o.includes(DROPDOWN_DIVIDER_SHORTCODE)) {
151+
const dividerText = o.replace(DROPDOWN_DIVIDER_SHORTCODE, '')
152+
return <Dropdown.Divider key={o} text={dividerText} />
153+
}
154+
155+
return (
156+
<Dropdown.Item
157+
key={o}
158+
type={indicator}
159+
value={o}
160+
title={o}
161+
selected={selectedOptions.includes(o)}
162+
onClick={onSelect}
163+
>
164+
{o}
165+
</Dropdown.Item>
166+
)
167+
})}
168+
169+
{options.filter(option =>
170+
option.toUpperCase().includes(filterString.toUpperCase())
171+
).length === 0 ? (
172+
<NoResults />
173+
) : null}
174+
</Dropdown.Menu>
175+
</>
121176
)
122177

123178
return (

src/Components/Dropdowns/Documentation/Dropdowns.stories.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,11 @@ dropdownComposedStories.add(
906906
emptyText={text('emptyText', 'None selected')}
907907
selectedOptions={selectedOptions}
908908
options={array('options', defaultMultiSelectOptions)}
909+
isSearchable={boolean('isSearchable', true)}
910+
searchbarInputPlaceholder={text(
911+
'searchbarInputPlaceholder',
912+
'Search'
913+
)}
909914
/>
910915
<div className="story--test-buttons">
911916
<button onClick={logRef}>Log Ref</button>

0 commit comments

Comments
 (0)