88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import * as React from 'react';
|
|
import { ChevronDown } from 'lucide-react';
|
|
|
|
interface SelectOption {
|
|
value: string;
|
|
label: string;
|
|
icon?: string;
|
|
}
|
|
|
|
interface SelectProps {
|
|
value?: string;
|
|
onChange?: (value: string) => void;
|
|
options: SelectOption[];
|
|
placeholder?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function Select({ value, onChange, options, placeholder, className = '' }: SelectProps) {
|
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
React.useEffect(() => {
|
|
function handleClickOutside(event: MouseEvent) {
|
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
const selectedOption = options.find(option => option.value === value);
|
|
|
|
return (
|
|
<div className={`relative ${className}`} ref={containerRef}>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
<span className="flex items-center">
|
|
{selectedOption?.icon && (
|
|
<img
|
|
src={selectedOption.icon}
|
|
alt=""
|
|
className="mr-2 h-4 w-4 rounded-full"
|
|
/>
|
|
)}
|
|
{selectedOption?.label || placeholder}
|
|
</span>
|
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="absolute z-50 mt-1 w-full rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80">
|
|
<div className="p-1">
|
|
{options.map((option) => (
|
|
<button
|
|
key={option.value}
|
|
onClick={() => {
|
|
onChange?.(option.value);
|
|
setIsOpen(false);
|
|
}}
|
|
className={`relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground ${
|
|
option.value === value ? 'bg-accent text-accent-foreground' : ''
|
|
}`}
|
|
>
|
|
{option.icon && (
|
|
<img
|
|
src={option.icon}
|
|
alt=""
|
|
className="mr-2 h-4 w-4 rounded-full"
|
|
/>
|
|
)}
|
|
{option.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|