'use client'; import { Field } from '@base-ui/react/field'; import * as React from 'react'; import { useFormState } from 'react-hook-form'; import { cn } from '@/lib/utils'; import { FormFieldContext, getErrorMessage, useFormMethods, } from './Form.context'; import { FormDescription } from './FormDescription'; import { FormError } from './FormError'; import { FormLabel } from './FormLabel'; import { type FormRadioGroupProps, FormRadioGroupRoot } from './FormRadioGroup'; import { type FormSelectProps, FormSelectRoot } from './FormSelect'; type FormFieldProps = Omit< React.ComponentProps, 'children' > & { children: React.ReactNode; controlId?: string; description?: React.ReactNode; error?: React.ReactNode; label?: React.ReactNode; }; function FormField({ children, className, controlId: controlIdProp, description, error, label, ...props }: FormFieldProps) { const { control, getFieldState } = useFormMethods(); const name = props.name; const formState = useFormState({ control, disabled: !name, exact: true, name, }); const fieldError = name ? getFieldState(name, formState).error : undefined; const errorMessage = error ?? getErrorMessage(fieldError); // The control id is the focus target for the visible field label. // It is generated by default to avoid duplicate DOM ids across forms/rows. const generatedControlId = React.useId(); const controlId = controlIdProp ?? generatedControlId; const nativeLabel = shouldUseNativeFieldLabel(children); const contextValue = React.useMemo( () => ({ controlId, name, }), [controlId, name], ); return ( {/* Native inputs use htmlFor/id for label focus; Base UI composites use non-native labels so their primitives can wire aria-labelledby. */} {label && nativeLabel ? ( {label} ) : label ? ( }> {label} ) : null} {children} {errorMessage ? ( {errorMessage} ) : description ? ( {description} ) : null} ); } function isFormRadioGroupElement( child: React.ReactNode, ): child is React.ReactElement { return React.isValidElement(child) && child.type === FormRadioGroupRoot; } function isFormSelectElement( child: React.ReactNode, ): child is React.ReactElement { return React.isValidElement(child) && child.type === FormSelectRoot; } function shouldUseNativeFieldLabel(children: React.ReactNode) { return !React.Children.toArray(children).some( (child) => isFormRadioGroupElement(child) || isFormSelectElement(child), ); } export { FormField }; export type { FormFieldProps };