first commit
This commit is contained in:
653
src/components/AutoComplete/AutoComplete.tsx
Normal file
653
src/components/AutoComplete/AutoComplete.tsx
Normal file
@@ -0,0 +1,653 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useId,
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/24/outline";
|
||||
import { getSizeStyles } from "../../data/getInputSizes";
|
||||
import Textfield from "../Textfeild/Textfeild";
|
||||
import { motion } from "framer-motion";
|
||||
import { Tooltip } from "../Tooltip/Tooltip";
|
||||
import { createPortal } from "react-dom";
|
||||
import { checkIsMobile } from "../../utils/checkIsMobile";
|
||||
|
||||
interface DataItem {
|
||||
key: number | string;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
isGroupHeader?: boolean;
|
||||
originalGroupKey?: string | number;
|
||||
}
|
||||
|
||||
interface AutoCompleteProps {
|
||||
data: DataItem[];
|
||||
multiselect?: boolean;
|
||||
inPage?: boolean;
|
||||
disabled?: boolean;
|
||||
selectedKeys: (number | string)[];
|
||||
onChange: (keys: (number | string)[]) => void | [];
|
||||
width?: string;
|
||||
buttonHeight?: number | string;
|
||||
title?: string;
|
||||
error?: boolean;
|
||||
size?: "small" | "medium" | "large";
|
||||
helperText?: string;
|
||||
onChangeValue?: (data: { value: string; key: number | string }) => void;
|
||||
onGroupHeaderClick?: (groupKey: string | number) => void;
|
||||
selectField?: boolean;
|
||||
}
|
||||
|
||||
const AutoComplete: React.FC<AutoCompleteProps> = ({
|
||||
data,
|
||||
multiselect = false,
|
||||
selectedKeys,
|
||||
onChange,
|
||||
disabled = false,
|
||||
inPage = false,
|
||||
title = "",
|
||||
error = false,
|
||||
size = "medium",
|
||||
helperText,
|
||||
onChangeValue,
|
||||
onGroupHeaderClick,
|
||||
selectField = false,
|
||||
}) => {
|
||||
const [filteredData, setFilteredData] = useState<DataItem[]>(data);
|
||||
const [showOptions, setShowOptions] = useState<boolean>(false);
|
||||
const [dropdownWidth, setDropdownWidth] = useState<number>(0);
|
||||
const [dropdownPosition, setDropdownPosition] = useState<{
|
||||
top: number;
|
||||
left: number;
|
||||
}>({ top: 0, left: 0 });
|
||||
const [maxHeight, setMaxHeight] = useState<number>(240);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const dropdownRef = useRef<HTMLUListElement>(null);
|
||||
const uniqueId = useId();
|
||||
const selectedKeysRef = useRef<(number | string)[]>(selectedKeys);
|
||||
const isInternalChangeRef = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const updateDropdownDimensions = () => {
|
||||
if (inputRef.current) {
|
||||
const rect = inputRef.current.getBoundingClientRect();
|
||||
const defaultMaxHeight = 240;
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const availableHeight = Math.max(100, spaceBelow - 10);
|
||||
const calculatedMaxHeight =
|
||||
spaceBelow < defaultMaxHeight ? availableHeight : defaultMaxHeight;
|
||||
|
||||
setDropdownWidth(rect.width);
|
||||
setMaxHeight(calculatedMaxHeight);
|
||||
setDropdownPosition({
|
||||
top: rect.bottom + window.scrollY,
|
||||
left: rect.left + window.scrollX,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateDropdownDimensions();
|
||||
|
||||
const resizeObserver = new ResizeObserver(updateDropdownDimensions);
|
||||
if (inputRef.current) {
|
||||
resizeObserver.observe(inputRef.current);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", updateDropdownDimensions);
|
||||
window.addEventListener("scroll", updateDropdownDimensions);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener("resize", updateDropdownDimensions);
|
||||
window.removeEventListener("scroll", updateDropdownDimensions);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showOptions) return;
|
||||
|
||||
let animationFrameId: number;
|
||||
let isActive = true;
|
||||
let lastTop = 0;
|
||||
let lastLeft = 0;
|
||||
let lastWidth = 0;
|
||||
let lastMaxHeight = 0;
|
||||
|
||||
const updatePosition = (force = false) => {
|
||||
if (!isActive || !inputRef.current) return;
|
||||
|
||||
const rect = inputRef.current.getBoundingClientRect();
|
||||
const defaultMaxHeight = 240;
|
||||
const viewportHeight =
|
||||
window.visualViewport?.height || window.innerHeight;
|
||||
const spaceBelow = viewportHeight - rect.bottom;
|
||||
const availableHeight = Math.max(100, spaceBelow - 10);
|
||||
const calculatedMaxHeight =
|
||||
spaceBelow < defaultMaxHeight ? availableHeight : defaultMaxHeight;
|
||||
|
||||
const newTop = rect.bottom + window.scrollY;
|
||||
const newLeft = rect.left + window.scrollX;
|
||||
const newWidth = rect.width;
|
||||
|
||||
if (
|
||||
force ||
|
||||
Math.abs(newTop - lastTop) > 0.5 ||
|
||||
Math.abs(newLeft - lastLeft) > 0.5 ||
|
||||
Math.abs(newWidth - lastWidth) > 0.5 ||
|
||||
Math.abs(calculatedMaxHeight - lastMaxHeight) > 1
|
||||
) {
|
||||
setDropdownWidth(newWidth);
|
||||
setMaxHeight(calculatedMaxHeight - 30);
|
||||
setDropdownPosition({
|
||||
top: newTop,
|
||||
left: newLeft,
|
||||
});
|
||||
lastTop = newTop;
|
||||
lastLeft = newLeft;
|
||||
lastWidth = newWidth;
|
||||
lastMaxHeight = calculatedMaxHeight;
|
||||
}
|
||||
|
||||
if (isActive && showOptions) {
|
||||
animationFrameId = requestAnimationFrame(() => updatePosition(false));
|
||||
}
|
||||
};
|
||||
|
||||
updatePosition();
|
||||
|
||||
const handleResize = () => updatePosition(true);
|
||||
const handleScroll = () => updatePosition();
|
||||
let lastViewportHeight =
|
||||
window.visualViewport?.height || window.innerHeight;
|
||||
const handleVisualViewportResize = () => {
|
||||
const currentHeight = window.visualViewport?.height || window.innerHeight;
|
||||
const heightDiff = Math.abs(currentHeight - lastViewportHeight);
|
||||
lastViewportHeight = currentHeight;
|
||||
|
||||
if (heightDiff > 50) {
|
||||
setTimeout(() => {
|
||||
updatePosition(true);
|
||||
setTimeout(() => updatePosition(true), 200);
|
||||
setTimeout(() => updatePosition(true), 400);
|
||||
}, 50);
|
||||
} else {
|
||||
setTimeout(() => updatePosition(true), 50);
|
||||
}
|
||||
};
|
||||
const handleVisualViewportScroll = () => updatePosition();
|
||||
const handleFocus = () => {
|
||||
setTimeout(() => updatePosition(true), 300);
|
||||
};
|
||||
const handleBlur = () => {
|
||||
setTimeout(() => {
|
||||
updatePosition(true);
|
||||
setTimeout(() => updatePosition(true), 200);
|
||||
setTimeout(() => updatePosition(true), 400);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
window.addEventListener("scroll", handleScroll, true);
|
||||
|
||||
if (checkIsMobile()) {
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.addEventListener(
|
||||
"resize",
|
||||
handleVisualViewportResize
|
||||
);
|
||||
window.visualViewport.addEventListener(
|
||||
"scroll",
|
||||
handleVisualViewportScroll
|
||||
);
|
||||
}
|
||||
const inputElement = inputRef.current;
|
||||
if (inputElement) {
|
||||
inputElement.addEventListener("focus", handleFocus);
|
||||
inputElement.addEventListener("blur", handleBlur);
|
||||
inputElement.addEventListener("touchstart", handleFocus);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
isActive = false;
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
window.removeEventListener("scroll", handleScroll, true);
|
||||
if (checkIsMobile()) {
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.removeEventListener(
|
||||
"resize",
|
||||
handleVisualViewportResize
|
||||
);
|
||||
window.visualViewport.removeEventListener(
|
||||
"scroll",
|
||||
handleVisualViewportScroll
|
||||
);
|
||||
}
|
||||
const inputElement = inputRef.current;
|
||||
if (inputElement) {
|
||||
inputElement.removeEventListener("focus", handleFocus);
|
||||
inputElement.removeEventListener("blur", handleBlur);
|
||||
inputElement.removeEventListener("touchstart", handleFocus);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [showOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const clickedInsideCurrent = target.closest(`.select-group-${uniqueId}`);
|
||||
const clickedOnAnotherAutocomplete =
|
||||
target.closest(".select-group") && !clickedInsideCurrent;
|
||||
|
||||
const clickedOnPortalDropdown = target.closest(
|
||||
`[data-autocomplete-portal="${uniqueId}"]`
|
||||
);
|
||||
|
||||
if (clickedOnAnotherAutocomplete) {
|
||||
setShowOptions(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clickedInsideCurrent && !clickedOnPortalDropdown) {
|
||||
setTimeout(() => {
|
||||
const isInputFocused = document.activeElement === inputRef.current;
|
||||
if (!isInputFocused) {
|
||||
setShowOptions(false);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [uniqueId]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredData(data);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedKeysRef.current = selectedKeys;
|
||||
}, [selectedKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInternalChangeRef.current) {
|
||||
isInternalChangeRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedKeys?.length > 0 && onChangeValue) {
|
||||
const selectedItem = data.find((item) => item.key === selectedKeys[0]);
|
||||
if (selectedItem) {
|
||||
onChangeValue({
|
||||
value: selectedItem.value.trim(),
|
||||
key: selectedItem.key,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [selectedKeys, data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showOptions) {
|
||||
setIsTyping(false);
|
||||
}
|
||||
}, [showOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showOptions || !checkIsMobile()) return;
|
||||
|
||||
const originalOverflow = window.getComputedStyle(document.body).overflow;
|
||||
const originalPosition = window.getComputedStyle(document.body).position;
|
||||
const originalTop = document.body.style.top;
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
document.body.style.overflow = "hidden";
|
||||
document.body.style.position = "fixed";
|
||||
document.body.style.top = `-${scrollY}px`;
|
||||
document.body.style.width = "100%";
|
||||
|
||||
const preventTouchMove = (e: TouchEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const dropdown = document.querySelector(
|
||||
`[data-autocomplete-portal="${uniqueId}"]`
|
||||
);
|
||||
|
||||
if (dropdown) {
|
||||
const touch = e.touches[0] || e.changedTouches[0];
|
||||
if (touch) {
|
||||
const elementAtPoint = document.elementFromPoint(
|
||||
touch.clientX,
|
||||
touch.clientY
|
||||
);
|
||||
if (
|
||||
elementAtPoint &&
|
||||
(dropdown.contains(elementAtPoint) || dropdown.contains(target))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else if (dropdown.contains(target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
document.addEventListener("touchmove", preventTouchMove, {
|
||||
passive: false,
|
||||
});
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = originalOverflow;
|
||||
document.body.style.position = originalPosition;
|
||||
document.body.style.top = originalTop;
|
||||
document.body.style.width = "";
|
||||
window.scrollTo(0, scrollY);
|
||||
document.removeEventListener("touchmove", preventTouchMove);
|
||||
};
|
||||
}, [showOptions, uniqueId]);
|
||||
|
||||
const inputValue = useMemo(() => {
|
||||
if (selectedKeys?.length > 0) {
|
||||
const selectedValues = data
|
||||
.filter((item) => selectedKeys?.includes(item.key))
|
||||
.map((item) => item.value);
|
||||
return selectedValues?.join(", ");
|
||||
}
|
||||
return "";
|
||||
}, [selectedKeys, data]);
|
||||
|
||||
const [localInputValue, setLocalInputValue] = useState<string>("");
|
||||
const [isTyping, setIsTyping] = useState<boolean>(false);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setLocalInputValue(value);
|
||||
setIsTyping(true);
|
||||
const filtered = data.filter((item) =>
|
||||
item.value.toLowerCase().includes(value.toLowerCase())
|
||||
);
|
||||
setFilteredData(filtered);
|
||||
setShowOptions(true);
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newSelectedKeys: (number | string)[]) => {
|
||||
isInternalChangeRef.current = true;
|
||||
onChange(newSelectedKeys);
|
||||
|
||||
if (onChangeValue && newSelectedKeys.length > 0) {
|
||||
const selectedItem = data.find(
|
||||
(item) => item.key === newSelectedKeys[0]
|
||||
);
|
||||
if (selectedItem) {
|
||||
onChangeValue({
|
||||
value: selectedItem.value.trim(),
|
||||
key: selectedItem.key,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[onChange, onChangeValue, data]
|
||||
);
|
||||
|
||||
const handleOptionClick = useCallback(
|
||||
(key: number | string) => {
|
||||
const currentSelectedKeys = selectedKeysRef.current;
|
||||
let newSelectedKeys: (number | string)[];
|
||||
|
||||
if (multiselect) {
|
||||
if (currentSelectedKeys.includes(key)) {
|
||||
newSelectedKeys = currentSelectedKeys.filter((item) => item !== key);
|
||||
} else {
|
||||
newSelectedKeys = [...currentSelectedKeys, key];
|
||||
}
|
||||
} else {
|
||||
if (currentSelectedKeys.includes(key)) {
|
||||
newSelectedKeys = currentSelectedKeys.filter((item) => item !== key);
|
||||
} else {
|
||||
newSelectedKeys = [key];
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(newSelectedKeys);
|
||||
|
||||
setIsTyping(false);
|
||||
if (!multiselect) {
|
||||
setLocalInputValue("");
|
||||
setShowOptions(false);
|
||||
}
|
||||
},
|
||||
[multiselect, handleChange]
|
||||
);
|
||||
|
||||
const handleInputClick = useCallback(() => {
|
||||
document.querySelectorAll(".select-group").forEach((el) => {
|
||||
if (!el.classList.contains(`select-group-${uniqueId}`)) {
|
||||
const input = el.querySelector("input");
|
||||
if (input) {
|
||||
input.blur();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setShowOptions(true);
|
||||
setFilteredData(data);
|
||||
setLocalInputValue("");
|
||||
setIsTyping(false);
|
||||
}, [uniqueId, data]);
|
||||
|
||||
const handleCloseInput = useCallback(() => {
|
||||
setShowOptions(false);
|
||||
setIsTyping(false);
|
||||
}, []);
|
||||
|
||||
const selectedKeysSet = useMemo(() => new Set(selectedKeys), [selectedKeys]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
const enabledItems = filteredData.filter((item) => !item.disabled);
|
||||
const allEnabledKeys = enabledItems.map((item) => item.key);
|
||||
handleChange(allEnabledKeys);
|
||||
}, [filteredData, handleChange]);
|
||||
|
||||
const handleDeselectAll = useCallback(() => {
|
||||
handleChange([]);
|
||||
}, [handleChange]);
|
||||
|
||||
const areAllSelected = useMemo(() => {
|
||||
const enabledItems = filteredData.filter((item) => !item.disabled);
|
||||
return (
|
||||
enabledItems.length > 0 &&
|
||||
enabledItems.every((item) => selectedKeysSet.has(item.key))
|
||||
);
|
||||
}, [filteredData, selectedKeysSet]);
|
||||
|
||||
const dropdownOptions = useMemo(() => {
|
||||
if (filteredData.length === 0) {
|
||||
return (
|
||||
<li className="px-4 py-3 text-gray-500 dark:text-dark-400 text-center">
|
||||
نتیجهای یافت نشد
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
const selectAllHeader = multiselect ? (
|
||||
<li
|
||||
key="select-all-header"
|
||||
onClick={areAllSelected ? handleDeselectAll : handleSelectAll}
|
||||
className="flex items-center my-1 justify-start gap-2 px-4 py-2 cursor-pointer transition-colors duration-150 rounded-md border border-gray-200 dark:border-dark-600 hover:bg-primary-100 text-dark-800 dark:text-dark-100 dark:hover:bg-dark-700 bg-gray-50 dark:bg-dark-700 font-semibold"
|
||||
>
|
||||
<span className="text-sm">
|
||||
{areAllSelected ? "عدم انتخاب همه" : "انتخاب همه"}
|
||||
</span>
|
||||
{areAllSelected && (
|
||||
<CheckIcon className="w-4 h-4 text-primary-600 dark:text-primary-400 shrink-0" />
|
||||
)}
|
||||
</li>
|
||||
) : null;
|
||||
|
||||
const options = filteredData.map((item) => {
|
||||
const isSelected = selectedKeysSet.has(item.key);
|
||||
const isGroupHeader = item.isGroupHeader;
|
||||
const handleClick = () => {
|
||||
if (isGroupHeader && onGroupHeaderClick) {
|
||||
const groupKey =
|
||||
item.originalGroupKey !== undefined
|
||||
? item.originalGroupKey
|
||||
: String(item.key).startsWith("__group__")
|
||||
? String(item.key).slice(11)
|
||||
: item.key;
|
||||
onGroupHeaderClick(groupKey);
|
||||
} else if (!item.disabled) {
|
||||
handleOptionClick(item.key);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<li
|
||||
key={`${item.key}`}
|
||||
onClick={handleClick}
|
||||
className={`flex items-center justify-between px-4 py-2 transition-colors duration-150 rounded-md
|
||||
${
|
||||
isGroupHeader && onGroupHeaderClick
|
||||
? "cursor-pointer opacity-55 hover:bg-gray-100 text-dark-800 dark:text-dark-100 dark:hover:bg-primary-900/90 font-semibold bg-gray-200 dark:bg-primary-900"
|
||||
: item.disabled
|
||||
? "text-gray-400 dark:text-dark-500 cursor-not-allowed"
|
||||
: "cursor-pointer hover:bg-primary-100 text-dark-800 dark:text-dark-100 dark:hover:bg-dark-700"
|
||||
}
|
||||
${
|
||||
isSelected && !isGroupHeader
|
||||
? "bg-primary-50 dark:bg-dark-700 font-semibold"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
aria-disabled={item?.disabled && !isGroupHeader}
|
||||
>
|
||||
{checkIsMobile() ? (
|
||||
<span
|
||||
className={`truncate ${
|
||||
item?.value.length > 55 ? "text-xs" : "text-sm"
|
||||
}`}
|
||||
>
|
||||
{item.value}
|
||||
</span>
|
||||
) : (
|
||||
<Tooltip
|
||||
title={item.value}
|
||||
hidden={item?.value?.length < 55}
|
||||
position="right"
|
||||
>
|
||||
<span
|
||||
className={`truncate ${
|
||||
item?.value.length > 55 ? "text-xs" : "text-sm"
|
||||
}`}
|
||||
>
|
||||
{item.value}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isSelected && (
|
||||
<CheckIcon className="w-4 h-4 text-primary-600 dark:text-primary-400 shrink-0" />
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return selectAllHeader ? [selectAllHeader, ...options] : options;
|
||||
}, [
|
||||
filteredData,
|
||||
selectedKeysSet,
|
||||
handleOptionClick,
|
||||
multiselect,
|
||||
areAllSelected,
|
||||
handleSelectAll,
|
||||
handleDeselectAll,
|
||||
onGroupHeaderClick,
|
||||
]);
|
||||
|
||||
const dropdownPortalContent = useMemo(() => {
|
||||
if (!showOptions) return null;
|
||||
|
||||
return createPortal(
|
||||
<motion.ul
|
||||
ref={dropdownRef}
|
||||
data-autocomplete-portal={`${uniqueId}`}
|
||||
initial={{ opacity: 0, scaleY: 0.95, y: -5 }}
|
||||
animate={{ opacity: 1, scaleY: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, ease: "easeOut" }}
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: dropdownPosition.top,
|
||||
left: dropdownPosition.left,
|
||||
width: `${dropdownWidth}px`,
|
||||
maxHeight: `${maxHeight}px`,
|
||||
zIndex: 9999,
|
||||
transformOrigin: "top center",
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "#cbd5e1 transparent",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
className={`overflow-y-auto border border-gray-200 dark:border-dark-500 bg-white dark:bg-dark-800 divide-y divide-gray-100 dark:divide-dark-600 text-sm backdrop-blur-lg rounded-xl shadow-2xl modern-scrollbar`}
|
||||
>
|
||||
{dropdownOptions}
|
||||
</motion.ul>,
|
||||
document.body
|
||||
);
|
||||
}, [
|
||||
showOptions,
|
||||
dropdownPosition,
|
||||
dropdownWidth,
|
||||
uniqueId,
|
||||
dropdownOptions,
|
||||
maxHeight,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`select-group select-group-${uniqueId} ${
|
||||
inPage ? "w-auto" : "w-full"
|
||||
}`}
|
||||
>
|
||||
<div className="relative w-full">
|
||||
<div className="relative">
|
||||
<Textfield
|
||||
disabled={disabled}
|
||||
readOnly={selectField}
|
||||
inputMode={selectField ? "none" : undefined}
|
||||
handleCloseInput={handleCloseInput}
|
||||
error={error}
|
||||
helperText={helperText}
|
||||
ref={inputRef}
|
||||
isAutoComplete
|
||||
inputSize={size}
|
||||
value={isTyping ? localInputValue : inputValue}
|
||||
onChange={handleInputChange}
|
||||
onClick={handleInputClick}
|
||||
className="selected-value w-full p-3 pl-10 outline-0 rounded-lg border border-black-100 transition-all duration-200 text-right"
|
||||
placeholder={title || "انتخاب کنید..."}
|
||||
/>
|
||||
<ChevronDownIcon
|
||||
className={`absolute left-3 text-dark-400 dark:text-dark-100 transition-transform duration-200 ${
|
||||
showOptions ? "transform rotate-180" : ""
|
||||
} ${getSizeStyles(size).icon}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{dropdownPortalContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoComplete;
|
||||
Reference in New Issue
Block a user