Spaces:
Sleeping
Sleeping
| import React, { useState, useRef, useEffect } from 'react'; | |
| interface DropdownOption { | |
| id: string; | |
| label: React.ReactNode; | |
| value: string; | |
| } | |
| interface DropdownProps { | |
| options: DropdownOption[]; | |
| value?: string; | |
| onChange: (value: string) => void; | |
| placeholder?: string; | |
| label?: string; | |
| error?: string; | |
| className?: string; | |
| disabled?: boolean; | |
| } | |
| const Dropdown: React.FC<DropdownProps> = ({ | |
| options, | |
| value, | |
| onChange, | |
| placeholder = 'θ―·ιζ©', | |
| label, | |
| error, | |
| className = '', | |
| disabled = false | |
| }) => { | |
| const [isOpen, setIsOpen] = useState(false); | |
| const dropdownRef = useRef<HTMLDivElement>(null); | |
| const selectedOption = options.find(option => option.value === value); | |
| // ε€ηηΉε»ε€ι¨ε ³ιδΈζθε | |
| useEffect(() => { | |
| const handleClickOutside = (event: MouseEvent) => { | |
| if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { | |
| setIsOpen(false); | |
| } | |
| }; | |
| document.addEventListener('mousedown', handleClickOutside); | |
| return () => { | |
| document.removeEventListener('mousedown', handleClickOutside); | |
| }; | |
| }, []); | |
| const toggleDropdown = () => { | |
| if (!disabled) { | |
| setIsOpen(!isOpen); | |
| } | |
| }; | |
| const handleOptionSelect = (optionValue: string) => { | |
| onChange(optionValue); | |
| setIsOpen(false); | |
| }; | |
| return ( | |
| <div className={`mb-4 ${className}`}> | |
| {label && ( | |
| <label className="block text-sm font-medium mb-1 text-gray-700"> | |
| {label} | |
| </label> | |
| )} | |
| <div className="relative" ref={dropdownRef}> | |
| <button | |
| type="button" | |
| onClick={toggleDropdown} | |
| className={` | |
| ios-input flex items-center justify-between w-full | |
| ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'} | |
| ${error ? 'border-red-500' : ''} | |
| `} | |
| disabled={disabled} | |
| > | |
| <span className={`${!selectedOption ? 'text-gray-400' : ''}`}> | |
| {selectedOption ? selectedOption.label : placeholder} | |
| </span> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| className={`h-5 w-5 text-gray-400 transition-transform duration-200 ${isOpen ? 'transform rotate-180' : ''}`} | |
| viewBox="0 0 20 20" | |
| fill="currentColor" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| </button> | |
| {isOpen && ( | |
| <div className="absolute z-10 mt-1 w-full bg-white rounded-md shadow-lg max-h-60 overflow-auto"> | |
| <div className="py-1"> | |
| {options.map((option) => ( | |
| <div | |
| key={option.id} | |
| className={` | |
| px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer | |
| ${option.value === value ? 'bg-blue-50 text-blue-600' : ''} | |
| `} | |
| onClick={() => handleOptionSelect(option.value)} | |
| > | |
| {option.label} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {error && <p className="mt-1 text-sm text-red-600">{error}</p>} | |
| </div> | |
| ); | |
| }; | |
| export default Dropdown; |