126 lines
3.3 KiB
JavaScript
126 lines
3.3 KiB
JavaScript
import React, { useEffect, useRef, useState } from "react";
|
|
import Label from "../Label";
|
|
|
|
const SelectField = ({
|
|
label = "Select",
|
|
options = [],
|
|
placeholder = "Select Option",
|
|
required = false,
|
|
value,
|
|
onChange,
|
|
valueKey = "id",
|
|
labelKey = "name",
|
|
isLoading = false,
|
|
}) => {
|
|
const [position, setPosition] = useState("bottom");
|
|
|
|
const [open, setOpen] = useState(false);
|
|
const dropdownRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
setOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
}, []);
|
|
|
|
const selectedOption = options.find((opt) => opt[valueKey] === value);
|
|
|
|
const displayText = selectedOption ? selectedOption[labelKey] : placeholder;
|
|
|
|
const handleSelect = (option) => {
|
|
onChange(option[valueKey]);
|
|
setOpen(false);
|
|
};
|
|
|
|
const toggleDropdown = () => {
|
|
if (!open) {
|
|
const rect = dropdownRef.current?.getBoundingClientRect();
|
|
const viewportHeight = window.innerHeight;
|
|
|
|
const spaceBelow = viewportHeight - rect.bottom;
|
|
const dropdownHeight = 200;
|
|
|
|
if (spaceBelow < dropdownHeight) {
|
|
setPosition("top"); // open upward
|
|
} else {
|
|
setPosition("bottom"); // open downward
|
|
}
|
|
}
|
|
|
|
setOpen((prev) => !prev);
|
|
};
|
|
|
|
return (
|
|
<div className="position-relative mb-3" ref={dropdownRef}>
|
|
{label && (
|
|
<Label className="form-label" required={required}>
|
|
{label}
|
|
</Label>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
|
|
open ? "show" : ""
|
|
}`}
|
|
onClick={toggleDropdown}
|
|
disabled={isLoading}
|
|
>
|
|
<span
|
|
className={`text-truncate ${!selectedOption ? "text-muted" : ""}`}
|
|
>
|
|
{isLoading ? "Loading..." : displayText}
|
|
</span>
|
|
</button>
|
|
|
|
{open && !isLoading && (
|
|
<div
|
|
className="w-full px-1 shadow-md rounded p-1"
|
|
style={{
|
|
position: "absolute",
|
|
top: position === "bottom" ? "100%" : "auto",
|
|
bottom: position === "top" ? "100%" : "auto",
|
|
left: 0,
|
|
zIndex: 1050,
|
|
|
|
marginTop: position === "bottom" ? "2px" : "0",
|
|
marginBottom: position === "top" ? "2px" : "0",
|
|
}}
|
|
>
|
|
<ul
|
|
className="dropdown-menu w-100 show border-0 rounded px-1 py-1"
|
|
style={{
|
|
position: "static",
|
|
|
|
maxHeight: "220px",
|
|
overflowY: "auto",
|
|
overflowX: "hidden",
|
|
}}
|
|
id="vertical-example"
|
|
>
|
|
{options.map((option, i) => (
|
|
<li key={i}>
|
|
<button
|
|
type="button"
|
|
className={`dropdown-item rounded text-truncate ${
|
|
option[valueKey] === value ? "active" : ""
|
|
}`}
|
|
onClick={() => handleSelect(option)}
|
|
>
|
|
{option[labelKey]}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SelectField;
|