import { FieldTitle, OptionalResourceContextProvider, SourceContextProvider, composeSyncValidators, isRequired, useApplyInputDefaultValues, useFormGroupContext, useFormGroups, useGetValidationErrorMessage, useSourceContext, sanitizeInputRestProps, ArrayInputContext, } from "ra-core"; import type { InputProps, SourceContextValue } from "ra-core"; import * as React from "react"; import { useEffect } from "react"; import { useFieldArray, useFormContext } from "react-hook-form"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import { Skeleton } from "../ui/skeleton"; import { InputHelperText } from "@/components/admin/input-helper-text"; import { FormError, FormField } from "@/components/admin/form"; /** * Creates a list of sub-forms for editing arrays of data embedded inside a record. * * Use `` when you need to edit array fields like order items, tags, or any * repeatable embedded data. Requires a form iterator child (typically ``) * to render and manage individual array items. * * @see {@link https://marmelab.com/shadcn-admin-kit/docs/arrayinput/ ArrayInput documentation} * * @example * import { * Edit, * SimpleForm, * TextInput, * NumberInput, * ArrayInput, * SimpleFormIterator, * } from '@/components/admin'; * * const OrderEdit = () => ( * * * * * * * * * * * * * * ); */ export const ArrayInput = (props: ArrayInputProps) => { const { className, defaultValue = [], label, isPending, children, helperText, resource: resourceFromProps, source: arraySource, validate, ...rest } = props; const formGroupName = useFormGroupContext(); const formGroups = useFormGroups(); const parentSourceContext = useSourceContext(); const finalSource = parentSourceContext.getSource(arraySource); const sanitizedValidate = Array.isArray(validate) ? composeSyncValidators(validate) : validate; const getValidationErrorMessage = useGetValidationErrorMessage(); const { getValues } = useFormContext(); const fieldProps = useFieldArray({ name: finalSource, rules: { validate: async (value) => { if (!sanitizedValidate) return true; const error = await sanitizedValidate(value, getValues(), props); if (!error) return true; return getValidationErrorMessage(error); }, }, }); useEffect(() => { if (formGroups && formGroupName != null) { formGroups.registerField(finalSource, formGroupName); } return () => { if (formGroups && formGroupName != null) { formGroups.unregisterField(finalSource, formGroupName); } }; }, [finalSource, formGroups, formGroupName]); useApplyInputDefaultValues({ inputProps: { ...props, defaultValue }, isArrayInput: true, fieldArrayInputControl: fieldProps, }); // The SourceContext will be read by children of ArrayInput to compute their composed source and label // // => SourceContext is "orders" // => SourceContext is "orders.0" // => final source for this input will be "orders.0.date" // // // const sourceContext = React.useMemo( () => ({ // source is the source of the ArrayInput child getSource: (source: string) => { if (!source) { // SimpleFormIterator calls getSource('') to get the arraySource return parentSourceContext.getSource(arraySource); } // We want to support nesting and composition with other inputs (e.g. TranslatableInputs, ReferenceOneInput, etc), // we must also take into account the parent SourceContext // // => SourceContext is "orders" // => SourceContext is "orders.0" // => final source for this input will be "orders.0.date" // => SourceContext is "orders.0.items" // => SourceContext is "orders.0.items.0" // => final source for this input will be "orders.0.items.0.reference" // // // // return parentSourceContext.getSource(`${arraySource}.${source}`); }, // if Array source is items, and child source is name, .0.name => resources.orders.fields.items.name getLabel: (source: string) => parentSourceContext.getLabel(`${arraySource}.${source}`), }), [parentSourceContext, arraySource], ); if (isPending) { return ; } return ( {children} ); }; export interface ArrayInputProps extends Omit { className?: string; children: React.ReactNode; isFetching?: boolean; isLoading?: boolean; isPending?: boolean; }