Android_Retail/src/Pages/Product/Utils/CellSelect.jsx

246 lines
7.2 KiB
React
Raw Normal View History

2026-02-12 18:15:06 +05:30
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { IoIosArrowDown, IoIosArrowUp, IoIosCloseCircle } from 'react-icons/io';
import '../../../Styles/Product/TurboAddForm.scss';
const CellSelect = memo(
({
value,
onChange,
onKeyDown,
dataCell,
options,
hasError,
placeholder = 'Select option',
onClick,
}) => {
2026-01-27 18:27:29 +05:30
const [isOpen, setIsOpen] = useState(false);
2026-02-12 18:15:06 +05:30
const [dropdownDirection, setDropdownDirection] = useState('down');
const [searchTerm, setSearchTerm] = useState('');
const [dropdownPosition, setDropdownPosition] = useState({
top: 0,
left: 0,
width: 0,
});
2026-01-27 18:27:29 +05:30
const selectRef = useRef(null);
const dropdownRef = useRef(null);
const searchInputRef = useRef(null);
// Filter options based on search term
2026-02-12 18:15:06 +05:30
const filteredOptions =
options?.filter((option) =>
2026-01-27 18:27:29 +05:30
option.label.toLowerCase().includes(searchTerm.toLowerCase())
2026-02-12 18:15:06 +05:30
) || [];
2026-01-27 18:27:29 +05:30
// Calculate dropdown position
const calculateDropdownPosition = useCallback(() => {
2026-02-12 18:15:06 +05:30
if (!selectRef.current) return 'down';
const rect = selectRef.current.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
return spaceBelow < 200 && spaceAbove > spaceBelow ? 'up' : 'down';
2026-01-27 18:27:29 +05:30
}, []);
// Close dropdown on outside click or scroll
useEffect(() => {
2026-02-12 18:15:06 +05:30
if (!isOpen) return;
const handler = (e) => {
if (
!dropdownRef.current?.contains(e.target) &&
!selectRef.current?.contains(e.target)
) {
setIsOpen(false);
// Reset search term when closing dropdown
setSearchTerm('');
}
};
const scrollHandler = (e) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target) &&
selectRef.current &&
!selectRef.current.contains(e.target)
) {
setIsOpen(false);
setSearchTerm('');
}
};
document.addEventListener('mousedown', handler);
window.addEventListener('scroll', scrollHandler, true);
return () => {
document.removeEventListener('mousedown', handler);
window.removeEventListener('scroll', scrollHandler, true);
};
2026-01-27 18:27:29 +05:30
}, [isOpen]);
// Focus search input when dropdown opens
useEffect(() => {
2026-02-12 18:15:06 +05:30
if (isOpen && searchInputRef.current) {
searchInputRef.current.focus();
}
2026-01-27 18:27:29 +05:30
}, [isOpen]);
2026-02-12 18:15:06 +05:30
// Update dropdown direction and position
2026-01-27 18:27:29 +05:30
useEffect(() => {
2026-02-12 18:15:06 +05:30
if (isOpen && selectRef.current) {
const rect = selectRef.current.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
const direction =
spaceBelow < 200 && spaceAbove > spaceBelow ? 'up' : 'down';
setDropdownDirection(direction);
setDropdownPosition({
top: direction === 'up' ? rect.top : rect.bottom,
left: rect.left,
width: rect.width,
});
}
}, [isOpen]);
2026-01-27 18:27:29 +05:30
// Keyboard navigation
2026-02-12 18:15:06 +05:30
const handleKeyDown = (e) => {
if (
!isOpen &&
['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)
) {
e.preventDefault();
2026-01-27 18:27:29 +05:30
onKeyDown(e);
2026-02-12 18:15:06 +05:30
return;
}
2026-01-27 18:27:29 +05:30
2026-02-12 18:15:06 +05:30
if (isOpen && ['ArrowLeft', 'ArrowRight'].includes(e.key)) {
e.preventDefault();
return;
}
if (e.key === 'Enter') {
e.preventDefault();
if (isOpen && filteredOptions.length > 0) {
// Select the first option when pressing Enter
handleSelectChange(filteredOptions[0].value);
} else {
setIsOpen((v) => !v);
}
return;
}
if (e.key === 'Escape') {
e.preventDefault();
2026-01-27 18:27:29 +05:30
setIsOpen(false);
2026-02-12 18:15:06 +05:30
setSearchTerm('');
return;
}
// Allow typing to search when dropdown is open
if (isOpen && e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
setSearchTerm((prev) => prev + e.key);
e.preventDefault();
return;
}
onKeyDown(e);
};
const handleSelectChange = (v) => {
onChange({ target: { value: v } });
setIsOpen(false);
setSearchTerm(''); // Clear search term after selection
2026-01-27 18:27:29 +05:30
};
2026-02-12 18:15:06 +05:30
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
2026-01-27 18:27:29 +05:30
};
const clearSearch = () => {
2026-02-12 18:15:06 +05:30
setSearchTerm('');
if (searchInputRef.current) {
searchInputRef.current.focus();
}
2026-01-27 18:27:29 +05:30
};
2026-02-12 18:15:06 +05:30
const selectedOption = options?.find((opt) => opt.value === value);
2026-01-27 18:27:29 +05:30
const displayValue = selectedOption ? selectedOption.label : placeholder;
return (
2026-02-12 18:15:06 +05:30
<div className="custom-select-container" ref={selectRef}>
<div
className={`custom-select-trigger${hasError ? ' error' : ''}${isOpen ? ' open' : ''}`}
onClick={(e) => {
setIsOpen((v) => !v);
onClick?.(e);
}}
onKeyDown={handleKeyDown}
data-cell={dataCell}
tabIndex={0}
>
<span className={!value ? 'placeholder-text' : ''}>
{isOpen ? (
<input
ref={searchInputRef}
className="search-input"
value={searchTerm}
onChange={handleSearchChange}
onClick={(e) => e.stopPropagation()}
placeholder={selectedOption?.label || placeholder}
/>
) : (
displayValue
)}
</span>
<span className="dropdown-arrow">
{isOpen ? <IoIosArrowUp /> : <IoIosArrowDown />}
</span>
</div>
{isOpen &&
createPortal(
2026-01-27 18:27:29 +05:30
<div
2026-02-12 18:15:06 +05:30
className={`custom-select-dropdown ${dropdownDirection}`}
ref={dropdownRef}
style={{
position: 'fixed',
top:
dropdownDirection === 'up'
? 'auto'
: `${dropdownPosition.top}px`,
bottom:
dropdownDirection === 'up'
? `${window.innerHeight - dropdownPosition.top}px`
: 'auto',
left: `${dropdownPosition.left}px`,
width: `${dropdownPosition.width}px`,
zIndex: 99999,
fontFamily:"Poppins"
}}
2026-01-27 18:27:29 +05:30
>
2026-02-12 18:15:06 +05:30
<div className="dropdown-content">
{filteredOptions.length === 0 ? (
<div className="dropdown-option disabled">
{searchTerm ? 'No matches found' : 'No data'}
</div>
) : (
filteredOptions.map((option) => (
<div
key={option.value}
className={`dropdown-option${value === option.value ? ' selected' : ''}`}
onClick={() => handleSelectChange(option.value)}
>
{option.label}
2026-01-27 18:27:29 +05:30
</div>
2026-02-12 18:15:06 +05:30
))
)}
</div>
</div>,
document.body
)}
</div>
2026-01-27 18:27:29 +05:30
);
2026-02-12 18:15:06 +05:30
}
);
2026-01-27 18:27:29 +05:30
2026-02-12 18:15:06 +05:30
export default CellSelect;