"use client"; import { Field, FieldContent, FieldDescription, FieldError, FieldLabel, } from "@/components/ui/field"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import type { LucideIcon } from "lucide-react"; import * as React from "react"; import type { FieldPath, FieldValues } from "react-hook-form"; import { Controller } from "react-hook-form"; /** Types */ type Option = { label: React.ReactNode; value: string | number; disabled?: boolean; icon?: LucideIcon; }; type BaseProps = { id?: string; icon?: LucideIcon; label?: React.ReactNode; options: Option[]; placeholder?: string; description?: React.ReactNode; className?: string; triggerClassName?: string; contentClassName?: string; disabled?: boolean; required?: boolean; allowClear?: boolean; clearLabel?: string; value?: string | number | undefined; onValueChange?: (value: string | number | undefined) => void; defaultValue?: string | number | undefined; valueAsNumber?: boolean; error?: React.ReactNode; }; export type SelectFieldProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, > = BaseProps & { name?: TName; }; /** Helpers */ const CLEAR_TOKEN = "__CLEAR__"; function normalizeOut( raw: string, { valueAsNumber }: { valueAsNumber?: boolean }, ): string | number | undefined { if (raw === CLEAR_TOKEN) return undefined; if (!valueAsNumber) return raw; const n = Number(raw); return Number.isFinite(n) ? n : undefined; } function normalizeIn(v: string | number | undefined | null): string { if (v === null || v === undefined) return ""; return String(v); } export default function SelectField< TFieldValues extends FieldValues, TName extends FieldPath, >({ name, id, icon: Icon, label, options, placeholder = "Select an option", description, className, triggerClassName, contentClassName, disabled = false, required = false, allowClear = false, clearLabel = "Clear", valueAsNumber = false, value: externalValue, onValueChange, defaultValue, error, }: SelectFieldProps) { const autoId = React.useId(); const triggerId = id ?? autoId; const [internal, setInternal] = React.useState( defaultValue, ); // Renderers const renderStandalone = () => { const current = externalValue !== undefined ? externalValue : internal; const selectValue = normalizeIn(current); const handleChange = (raw: string | null) => { const next = raw ? normalizeOut(raw, { valueAsNumber }) : undefined; if (onValueChange) onValueChange(next); else setInternal(next); }; return (
{label ? ( {Icon && } {label} {required && *} ) : null}
{description ? ( {description} ) : null} {error ? (

{error}

) : null}
); }; const renderFormControlled = () => ( { const effectiveDisabled = disabled || field.disabled; const current = externalValue !== undefined ? externalValue : (field.value as string | number | undefined); const selectValue = normalizeIn(current); const handleChange = (raw: string | null) => { const next = raw ? normalizeOut(raw, { valueAsNumber }) : undefined; field.onChange(next); onValueChange?.(next); }; return ( {label ? ( {Icon && } {label} {required && *} ) : null}
{description ? ( {description} ) : null} {fieldState.invalid && }
); }} /> ); const content = name ? renderFormControlled() : renderStandalone(); return content; }