64 lines
1.5 KiB
TypeScript
64 lines
1.5 KiB
TypeScript
'use client'
|
|
|
|
import {
|
|
Controller,
|
|
type FieldValues,
|
|
type FieldPath,
|
|
type Control,
|
|
type ControllerRenderProps,
|
|
type FieldError,
|
|
} from 'react-hook-form'
|
|
|
|
import {
|
|
Field,
|
|
FieldLabel,
|
|
FieldDescription,
|
|
FieldError as FieldErrorDisplay,
|
|
} from '@/components/ui/field'
|
|
|
|
interface FormFieldProps<T extends FieldValues, TName extends FieldPath<T> = FieldPath<T>> {
|
|
control: Control<T>
|
|
name: TName
|
|
label?: string
|
|
description?: string
|
|
required?: boolean
|
|
render: (props: {
|
|
field: ControllerRenderProps<T, TName>
|
|
error?: FieldError
|
|
invalid: boolean
|
|
}) => React.ReactNode
|
|
}
|
|
|
|
export function FormField<T extends FieldValues, TName extends FieldPath<T> = FieldPath<T>>({
|
|
control,
|
|
name,
|
|
label,
|
|
description,
|
|
required,
|
|
render,
|
|
}: FormFieldProps<T>) {
|
|
return (
|
|
<Controller
|
|
control={control}
|
|
name={name}
|
|
render={({ field, fieldState }) => (
|
|
<Field data-invalid={fieldState.invalid || undefined}>
|
|
{label && (
|
|
<FieldLabel htmlFor={String(name)}>
|
|
{label}
|
|
{required && <span className="text-destructive"> *</span>}
|
|
</FieldLabel>
|
|
)}
|
|
{render({ field, error: fieldState.error, invalid: fieldState.invalid })}
|
|
{description && !fieldState.error && (
|
|
<FieldDescription>{description}</FieldDescription>
|
|
)}
|
|
{fieldState.error && (
|
|
<FieldErrorDisplay errors={[fieldState.error]} />
|
|
)}
|
|
</Field>
|
|
)}
|
|
/>
|
|
)
|
|
}
|