172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
import React, { useState, useEffect, useRef } from "react";
|
|
import { useFormContext } from "react-hook-form";
|
|
import { createPortal } from "react-dom";
|
|
import "./MultiSelectDropdown.css";
|
|
|
|
const SelectMultiple = ({
|
|
name,
|
|
options = [],
|
|
label = "Select options",
|
|
labelKey = "name", // Can now be a function or a string
|
|
valueKey = "id",
|
|
placeholder = "Please select...",
|
|
IsLoading = false,
|
|
}) => {
|
|
const { setValue, watch } = useFormContext();
|
|
const selectedValues = watch(name) || [];
|
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [searchText, setSearchText] = useState("");
|
|
const containerRef = useRef(null);
|
|
const dropdownRef = useRef(null);
|
|
|
|
const [dropdownStyles, setDropdownStyles] = useState({ top: 0, left: 0, width: 0 });
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e) => {
|
|
if (
|
|
containerRef.current &&
|
|
!containerRef.current.contains(e.target) &&
|
|
(!dropdownRef.current || !dropdownRef.current.contains(e.target))
|
|
) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && containerRef.current) {
|
|
const rect = containerRef.current.getBoundingClientRect();
|
|
setDropdownStyles({
|
|
top: rect.bottom + window.scrollY,
|
|
left: rect.left + window.scrollX,
|
|
width: rect.width,
|
|
});
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const getLabel = (item) => {
|
|
return typeof labelKey === "function" ? labelKey(item) : item[labelKey];
|
|
};
|
|
|
|
const handleCheckboxChange = (value) => {
|
|
const updated = selectedValues.includes(value)
|
|
? selectedValues.filter((v) => v !== value)
|
|
: [...selectedValues, value];
|
|
|
|
setValue(name, updated, { shouldValidate: true });
|
|
};
|
|
|
|
const filteredOptions = options.filter((item) => {
|
|
const label = getLabel(item);
|
|
return label?.toLowerCase().includes(searchText.toLowerCase());
|
|
});
|
|
|
|
const dropdownElement = (
|
|
<div
|
|
ref={dropdownRef}
|
|
className="multi-select-dropdown-options py-2"
|
|
style={{
|
|
position: "absolute",
|
|
top: dropdownStyles.top,
|
|
left: dropdownStyles.left,
|
|
width: dropdownStyles.width,
|
|
zIndex: 9999,
|
|
backgroundColor: "white",
|
|
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
borderRadius: 4,
|
|
maxHeight: 300,
|
|
overflowY: "auto",
|
|
}}
|
|
>
|
|
<div className="multi-select-dropdown-search" style={{ padding: 8 }}>
|
|
<input
|
|
type="text"
|
|
placeholder="Search..."
|
|
value={searchText}
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
className="multi-select-dropdown-search-input"
|
|
style={{ width: "100%", padding: 4 }}
|
|
/>
|
|
</div>
|
|
|
|
{filteredOptions.map((item) => {
|
|
const labelVal = getLabel(item);
|
|
const valueVal = item[valueKey];
|
|
const isChecked = selectedValues.includes(valueVal);
|
|
|
|
return (
|
|
<div
|
|
key={valueVal}
|
|
className={`multi-select-dropdown-option ${isChecked ? "selected" : ""}`}
|
|
style={{ display: "flex", alignItems: "center", padding: "4px 8px" }}
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
className="custom-checkbox form-check-input"
|
|
checked={isChecked}
|
|
onChange={() => handleCheckboxChange(valueVal)}
|
|
style={{ marginRight: 8 }}
|
|
/>
|
|
<label className="text-secondary">{labelVal}</label>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{!IsLoading && filteredOptions.length === 0 && (
|
|
<div className="multi-select-dropdown-Not-found" style={{ padding: 8 }}>
|
|
<label className="text-muted">Not Found {`'${searchText}'`}</label>
|
|
</div>
|
|
)}
|
|
{IsLoading && filteredOptions.length === 0 && (
|
|
<div className="multi-select-dropdown-Not-found" style={{ padding: 8 }}>
|
|
<label className="text-muted">Loading...</label>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<div ref={containerRef} className="multi-select-dropdown-container" style={{ position: "relative" }}>
|
|
<label className="form-label mb-1">{label}</label>
|
|
|
|
<div
|
|
className="multi-select-dropdown-header"
|
|
onClick={() => setIsOpen((prev) => !prev)}
|
|
style={{ cursor: "pointer" }}
|
|
>
|
|
<span
|
|
className={
|
|
selectedValues.length > 0 ? "placeholder-style-selected" : "placeholder-style"
|
|
}
|
|
>
|
|
<div className="selected-badges-container">
|
|
{selectedValues.length > 0 ? (
|
|
selectedValues.map((val) => {
|
|
const found = options.find((opt) => opt[valueKey] === val);
|
|
const label = found ? getLabel(found) : "";
|
|
return (
|
|
<span key={val} className="badge badge-selected-item mx-1 mb-1">
|
|
{label}
|
|
</span>
|
|
);
|
|
})
|
|
) : (
|
|
<span className="placeholder-text">{placeholder}</span>
|
|
)}
|
|
</div>
|
|
</span>
|
|
<i className="bx bx-chevron-down"></i>
|
|
</div>
|
|
</div>
|
|
|
|
{isOpen && createPortal(dropdownElement, document.body)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SelectMultiple;
|