-
Notifications
You must be signed in to change notification settings - Fork 49
/
searchableSelect.tsx
91 lines (86 loc) · 2.51 KB
/
searchableSelect.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import {h} from 'preact'
import {connect} from '../../helpers/connect'
import actions from '../../actions'
import {State} from '../../state'
import {useEffect, useRef, useState} from 'preact/hooks'
interface Props<T> {
label: string
defaultItem: T
items: T[]
displaySelectedItem: (t: T) => string
displaySelectedItemClassName: string
displayItem: (t: T) => any
onSelect: (t: T) => void
searchPredicate: (query: string, t: T) => boolean
searchPlaceholder: string
}
// <T extends {}> is workaround for <T> being recognized as JSX element instead of generics
const SearchableSelect = <T extends {}>({
label,
defaultItem,
items,
displaySelectedItem,
displaySelectedItemClassName,
displayItem,
onSelect,
searchPredicate,
searchPlaceholder,
}: Props<T>) => {
const inputEl = useRef<HTMLInputElement>(null)
const dropdownEl = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState(false)
const [value, setValue] = useState(defaultItem)
const [search, setSearch] = useState('')
const shouldShowItem = (item: T) => searchPredicate(search, item)
const showDropdown = (bool: boolean) => {
setVisible(bool)
setSearch('')
if (bool) {
inputEl.current.focus()
}
}
useEffect(() => {
dropdownEl.current.scrollTop = 0
})
return (
<div className="searchable-select-wrapper" onMouseLeave={() => showDropdown(false)}>
<div className="searchable-select-label">{label}</div>
<div
className={`searchable-select ${displaySelectedItemClassName}`}
onMouseUp={() => showDropdown(!visible)}
>
{displaySelectedItem(value)}
</div>
<div ref={dropdownEl} className={`searchable-select-dropdown ${visible ? '' : 'hide'}`}>
<input
ref={inputEl}
type="text"
className="searchable-select-input"
value={search}
onInput={(event: any) => setSearch(event.target.value)}
placeholder={searchPlaceholder}
/>
<div>
{items &&
items.map((item, i) => (
<div
className={`searchable-select-item ${shouldShowItem(item) ? '' : 'hide'}`}
key={i}
onMouseUp={() => {
setVisible(false)
setValue(item)
onSelect(item)
}}
>
{displayItem(item)}
</div>
))}
</div>
</div>
</div>
)
}
export default connect(
(state: State) => ({}),
actions
)(SearchableSelect)