import { cloneElement, useId } from 'react'; import type { CSSProperties, ReactElement, ReactNode } from 'react'; import { Controller } from 'react-hook-form'; import type { Control, ControllerProps, FieldValues, Path } from 'react-hook-form'; import { Form } from 'antd'; import { useTranslation } from 'react-i18next'; import { normalizeAntdOnChange, type ValueProp } from './normalizeAntdOnChange'; import { toDotted, type FieldName } from './toDotted'; interface FormFieldTransform { input?: (value: unknown) => unknown; output?: (eventValue: unknown) => unknown; } export interface FormFieldProps { name: FieldName; control?: Control; label?: ReactNode; tooltip?: ReactNode; extra?: ReactNode; valueProp?: ValueProp; transform?: FormFieldTransform; onAfterChange?: (value: unknown) => void; rules?: ControllerProps['rules']; required?: boolean; noStyle?: boolean; className?: string; style?: CSSProperties; children: ReactElement; } export function FormField({ name, control, label, tooltip, extra, valueProp = 'value', transform, onAfterChange, rules, required, noStyle, className, style, children, }: FormFieldProps) { const { t } = useTranslation(); const dottedName = toDotted(name) as Path; const generatedId = useId(); const explicitId = (children as ReactElement<{ id?: string }>).props.id; const fieldId = explicitId ?? generatedId; return ( { const displayValue = transform?.input ? transform.input(field.value) : field.value; const help = fieldState.error?.message ? t(fieldState.error.message, { defaultValue: fieldState.error.message }) : undefined; const childProps: Record = { id: fieldId, [valueProp]: displayValue, onChange: (...args: unknown[]) => { const raw = normalizeAntdOnChange(args, valueProp); const next = transform?.output ? transform.output(raw) : raw; field.onChange(next); onAfterChange?.(next); }, onBlur: field.onBlur, ref: field.ref, }; return ( {cloneElement(children as ReactElement>, childProps)} ); }} /> ); } export default FormField;