80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useRef, useEffect } from 'react'
|
|
|
|
interface Option {
|
|
value: string
|
|
label: string
|
|
}
|
|
|
|
interface SelectProps {
|
|
value: string
|
|
onChange: (value: string) => void
|
|
options: Option[]
|
|
className?: string
|
|
}
|
|
|
|
export default function Select({ value, onChange, options, className = '' }: SelectProps) {
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const ref = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
function handleClickOutside(event: MouseEvent) {
|
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
setIsOpen(false)
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleClickOutside)
|
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
}, [])
|
|
|
|
const selectedOption = options.find(o => o.value === value) || options.find(o => o.value === 'custom')
|
|
|
|
return (
|
|
<div className={`relative ${className}`} ref={ref}>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="btn-secondary min-w-[140px] flex items-center justify-between gap-2"
|
|
>
|
|
<span>{selectedOption?.label || value}</span>
|
|
<svg
|
|
className={`w-4 h-4 text-neutral-500 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="absolute right-0 mt-2 w-full min-w-[140px] bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl shadow-lg z-50 overflow-hidden py-1">
|
|
{options.map((option) => (
|
|
<button
|
|
key={option.value}
|
|
onClick={() => {
|
|
onChange(option.value)
|
|
setIsOpen(false)
|
|
}}
|
|
className={`w-full text-left px-4 py-2.5 text-sm transition-colors flex items-center justify-between
|
|
${value === option.value
|
|
? 'bg-neutral-50 dark:bg-neutral-800 text-brand-orange font-medium'
|
|
: 'text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800'
|
|
}
|
|
`}
|
|
>
|
|
{option.label}
|
|
{value === option.value && (
|
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|