-
Notifications
You must be signed in to change notification settings - Fork 0
/
CustomDropdown.tsx
125 lines (115 loc) · 3.37 KB
/
CustomDropdown.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, { useState, useEffect } from "react";
import "./CustomDropdown.css";
type Option = {
label: string;
value: string | number;
};
type Props = {
options: Option[];
onChange: (selectedOption: Option) => void;
required?: boolean;
requiredMessage?: string;
forceRequired?: boolean;
placeHolder?: string;
};
const CustomDropdown: React.FC<Props> = ({
options,
onChange,
required,
requiredMessage = "This field is required",
forceRequired,
placeHolder = "Search or select options...",
}) => {
const [searchTerm, setSearchTerm] = useState("");
const [showOptions, setShowOptions] = useState(false);
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
const [filteredOptions, setFilteredOptions] = useState(options);
useEffect(() => {
setFilteredOptions(
options.filter((option) =>
option.label.toLowerCase().startsWith(searchTerm.toLowerCase())
)
);
}, [searchTerm, options]);
const handleFocus = () => setShowOptions(true);
const handleBlur = () => setTimeout(() => setShowOptions(false), 200);
const handleKeyDown = (e) => {
if (e.key === "ArrowDown") handleArrowDown(e);
if (e.key === "ArrowUp") handleArrowUp(e);
if (e.key === "Enter") handleEnter(e);
};
const handleArrowDown = (e) => {
e.preventDefault();
setSelectedOptionIndex(
Math.min(selectedOptionIndex + 1, filteredOptions.length - 1)
);
};
const handleArrowUp = (e) => {
e.preventDefault();
setSelectedOptionIndex(Math.max(selectedOptionIndex - 1, 0));
};
const handleEnter = (e) => {
e.preventDefault();
setSelectedOption(filteredOptions[selectedOptionIndex]);
setShowOptions(false);
setSearchTerm("");
if (onChange) {
onChange(filteredOptions[selectedOptionIndex]);
}
};
return (
<div className="custom-select">
<input
type="text"
className={`custom-select__input ${
(required && !selectedOption && showOptions !== undefined) ||
forceRequired
? "required"
: ""
}`}
placeholder={placeHolder || "Search or select options..."}
value={selectedOption ? selectedOption.label : searchTerm}
onChange={(e) => {
if (selectedOption) {
setSelectedOption(null);
}
setSearchTerm(e.target.value);
}}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
/>
<ul
className={
showOptions
? "custom-select__options show"
: "custom-select__options hide"
}
>
{filteredOptions.map((option, index) => (
<li
className={`custom-select__option ${
index === selectedOptionIndex ? "selected" : ""
}`}
key={index}
onClick={() => {
setSelectedOption(option);
setShowOptions(false);
if (onChange) {
onChange(option);
}
}}
>
{option.label}
</li>
))}
</ul>
{(required && !selectedOption && showOptions !== undefined) ||
(forceRequired && (
<span className="required-message">{requiredMessage}</span>
))}
</div>
);
};
export default CustomDropdown;