hasari-api / packages /ui /src /components /Select.tsx
erdoganpeker's picture
v0.3.0 — multimodal vehicle damage MVP
e327f0d
import { forwardRef, useId, type SelectHTMLAttributes, type ReactNode } from 'react';
import { ChevronDown } from 'lucide-react';
import { cn } from '../utils/cn';
export interface SelectOption {
value: string;
label: string;
disabled?: boolean;
}
interface Props extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'children'> {
label?: string;
hint?: string;
error?: string;
options?: SelectOption[];
placeholder?: string;
containerClassName?: string;
children?: ReactNode;
}
export const Select = forwardRef<HTMLSelectElement, Props>(function Select(
{
label,
hint,
error,
options,
placeholder,
className,
containerClassName,
id,
required,
children,
...rest
},
ref,
) {
const generatedId = useId();
const selId = id || generatedId;
const describedBy = error
? `${selId}-error`
: hint
? `${selId}-hint`
: undefined;
return (
<div className={cn('flex flex-col gap-1.5', containerClassName)}>
{label && (
<label htmlFor={selId} className="text-sm font-medium text-slate-800">
{label}
{required && (
<span className="ml-0.5 text-red-600" aria-hidden>
*
</span>
)}
</label>
)}
<div className="relative">
<select
ref={ref}
id={selId}
aria-invalid={!!error || undefined}
aria-describedby={describedBy}
required={required}
className={cn(
'block w-full appearance-none rounded-md border bg-white px-3 py-2 pr-9 text-sm text-slate-900',
'transition-colors focus:outline-none focus:ring-2',
error
? 'border-red-400 focus:border-red-500 focus:ring-red-200'
: 'border-slate-300 focus:border-brand-500 focus:ring-brand-200',
'disabled:cursor-not-allowed disabled:bg-slate-50 disabled:text-slate-400',
className,
)}
{...rest}
>
{placeholder && (
<option value="" disabled hidden>
{placeholder}
</option>
)}
{options?.map((opt) => (
<option key={opt.value} value={opt.value} disabled={opt.disabled}>
{opt.label}
</option>
))}
{children}
</select>
<ChevronDown
className="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400"
aria-hidden
/>
</div>
{hint && !error && (
<p id={`${selId}-hint`} className="text-xs text-slate-500">
{hint}
</p>
)}
{error && (
<p id={`${selId}-error`} className="text-xs font-medium text-red-600">
{error}
</p>
)}
</div>
);
});