|
|
@@ -1,165 +1,177 @@
|
|
|
-import * as React from "react"
|
|
|
-import * as LabelPrimitive from "@radix-ui/react-label"
|
|
|
-import { Slot } from "@radix-ui/react-slot"
|
|
|
+import * as React from 'react';
|
|
|
+import * as LabelPrimitive from '@radix-ui/react-label';
|
|
|
+import { Slot } from '@radix-ui/react-slot';
|
|
|
import {
|
|
|
- Controller,
|
|
|
- FormProvider,
|
|
|
- useFormContext,
|
|
|
- useFormState,
|
|
|
- type ControllerProps,
|
|
|
- type FieldPath,
|
|
|
- type FieldValues,
|
|
|
-} from "react-hook-form"
|
|
|
+ Controller,
|
|
|
+ FormProvider,
|
|
|
+ useFormContext,
|
|
|
+ useFormState,
|
|
|
+ type ControllerProps,
|
|
|
+ type FieldPath,
|
|
|
+ type FieldValues,
|
|
|
+} from 'react-hook-form';
|
|
|
|
|
|
-import { cn } from "@/lib/utils"
|
|
|
-import { Label } from "@/components/ui/label"
|
|
|
+import { cn } from '@/lib/utils';
|
|
|
+import { Label } from '@/components/ui/label';
|
|
|
|
|
|
-const Form = FormProvider
|
|
|
+const Form = FormProvider;
|
|
|
|
|
|
type FormFieldContextValue<
|
|
|
- TFieldValues extends FieldValues = FieldValues,
|
|
|
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
|
+ TFieldValues extends FieldValues = FieldValues,
|
|
|
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
|
> = {
|
|
|
- name: TName
|
|
|
-}
|
|
|
+ name: TName;
|
|
|
+};
|
|
|
|
|
|
-const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
|
- {} as FormFieldContextValue
|
|
|
-)
|
|
|
+const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
|
|
|
|
const FormField = <
|
|
|
- TFieldValues extends FieldValues = FieldValues,
|
|
|
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
|
+ TFieldValues extends FieldValues = FieldValues,
|
|
|
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
|
>({
|
|
|
- ...props
|
|
|
+ ...props
|
|
|
}: ControllerProps<TFieldValues, TName>) => {
|
|
|
- return (
|
|
|
- <FormFieldContext.Provider value={{ name: props.name }}>
|
|
|
- <Controller {...props} />
|
|
|
- </FormFieldContext.Provider>
|
|
|
- )
|
|
|
-}
|
|
|
+ return (
|
|
|
+ <FormFieldContext.Provider value={{ name: props.name }}>
|
|
|
+ <Controller {...props} />
|
|
|
+ </FormFieldContext.Provider>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export type TranslatableFormFieldProps = {};
|
|
|
+
|
|
|
+const TranslatableFormField = <
|
|
|
+ TFieldValues extends FieldValues & { translations?: Array<{ languageCode: string }> | null } = FieldValues,
|
|
|
+>({
|
|
|
+ name,
|
|
|
+ languageCode,
|
|
|
+ ...props
|
|
|
+}: Omit<ControllerProps<TFieldValues>, 'name'> & {
|
|
|
+ name: keyof Omit<NonNullable<TFieldValues['translations']>[number], 'languageCode'>;
|
|
|
+ languageCode: string;
|
|
|
+}) => {
|
|
|
+ const index = props.control?._formValues?.translations?.findIndex(
|
|
|
+ (translation: any) => translation?.languageCode === languageCode,
|
|
|
+ );
|
|
|
+ if (index === undefined || index === -1) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ const translationName = `translations.${index}.${String(name)}` as FieldPath<TFieldValues>;
|
|
|
+ return (
|
|
|
+ <FormFieldContext.Provider value={{ name: translationName }}>
|
|
|
+ <Controller {...props} name={translationName} key={translationName} />
|
|
|
+ </FormFieldContext.Provider>
|
|
|
+ );
|
|
|
+};
|
|
|
|
|
|
const useFormField = () => {
|
|
|
- const fieldContext = React.useContext(FormFieldContext)
|
|
|
- const itemContext = React.useContext(FormItemContext)
|
|
|
- const { getFieldState } = useFormContext()
|
|
|
- const formState = useFormState({ name: fieldContext.name })
|
|
|
- const fieldState = getFieldState(fieldContext.name, formState)
|
|
|
-
|
|
|
- if (!fieldContext) {
|
|
|
- throw new Error("useFormField should be used within <FormField>")
|
|
|
- }
|
|
|
-
|
|
|
- const { id } = itemContext
|
|
|
-
|
|
|
- return {
|
|
|
- id,
|
|
|
- name: fieldContext.name,
|
|
|
- formItemId: `${id}-form-item`,
|
|
|
- formDescriptionId: `${id}-form-item-description`,
|
|
|
- formMessageId: `${id}-form-item-message`,
|
|
|
- ...fieldState,
|
|
|
- }
|
|
|
-}
|
|
|
+ const fieldContext = React.useContext(FormFieldContext);
|
|
|
+ const itemContext = React.useContext(FormItemContext);
|
|
|
+ const { getFieldState } = useFormContext();
|
|
|
+ const formState = useFormState({ name: fieldContext.name });
|
|
|
+ const fieldState = getFieldState(fieldContext.name, formState);
|
|
|
+
|
|
|
+ if (!fieldContext) {
|
|
|
+ throw new Error('useFormField should be used within <FormField>');
|
|
|
+ }
|
|
|
+
|
|
|
+ const { id } = itemContext;
|
|
|
+
|
|
|
+ return {
|
|
|
+ id,
|
|
|
+ name: fieldContext.name,
|
|
|
+ formItemId: `${id}-form-item`,
|
|
|
+ formDescriptionId: `${id}-form-item-description`,
|
|
|
+ formMessageId: `${id}-form-item-message`,
|
|
|
+ ...fieldState,
|
|
|
+ };
|
|
|
+};
|
|
|
|
|
|
type FormItemContextValue = {
|
|
|
- id: string
|
|
|
-}
|
|
|
+ id: string;
|
|
|
+};
|
|
|
+
|
|
|
+const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
|
|
|
|
-const FormItemContext = React.createContext<FormItemContextValue>(
|
|
|
- {} as FormItemContextValue
|
|
|
-)
|
|
|
-
|
|
|
-function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
- const id = React.useId()
|
|
|
-
|
|
|
- return (
|
|
|
- <FormItemContext.Provider value={{ id }}>
|
|
|
- <div
|
|
|
- data-slot="form-item"
|
|
|
- className={cn("grid gap-2", className)}
|
|
|
- {...props}
|
|
|
- />
|
|
|
- </FormItemContext.Provider>
|
|
|
- )
|
|
|
+function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
+ const id = React.useId();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <FormItemContext.Provider value={{ id }}>
|
|
|
+ <div data-slot="form-item" className={cn('grid gap-2', className)} {...props} />
|
|
|
+ </FormItemContext.Provider>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-function FormLabel({
|
|
|
- className,
|
|
|
- ...props
|
|
|
-}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
|
- const { error, formItemId } = useFormField()
|
|
|
-
|
|
|
- return (
|
|
|
- <Label
|
|
|
- data-slot="form-label"
|
|
|
- data-error={!!error}
|
|
|
- className={cn("data-[error=true]:text-destructive-foreground", className)}
|
|
|
- htmlFor={formItemId}
|
|
|
- {...props}
|
|
|
- />
|
|
|
- )
|
|
|
+function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
|
+ const { error, formItemId } = useFormField();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Label
|
|
|
+ data-slot="form-label"
|
|
|
+ data-error={!!error}
|
|
|
+ className={cn('data-[error=true]:text-destructive-foreground', className)}
|
|
|
+ htmlFor={formItemId}
|
|
|
+ {...props}
|
|
|
+ />
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|
|
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
|
-
|
|
|
- return (
|
|
|
- <Slot
|
|
|
- data-slot="form-control"
|
|
|
- id={formItemId}
|
|
|
- aria-describedby={
|
|
|
- !error
|
|
|
- ? `${formDescriptionId}`
|
|
|
- : `${formDescriptionId} ${formMessageId}`
|
|
|
- }
|
|
|
- aria-invalid={!!error}
|
|
|
- {...props}
|
|
|
- />
|
|
|
- )
|
|
|
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Slot
|
|
|
+ data-slot="form-control"
|
|
|
+ id={formItemId}
|
|
|
+ aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
|
+ aria-invalid={!!error}
|
|
|
+ {...props}
|
|
|
+ />
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
- const { formDescriptionId } = useFormField()
|
|
|
-
|
|
|
- return (
|
|
|
- <p
|
|
|
- data-slot="form-description"
|
|
|
- id={formDescriptionId}
|
|
|
- className={cn("text-muted-foreground text-sm", className)}
|
|
|
- {...props}
|
|
|
- />
|
|
|
- )
|
|
|
+function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
|
+ const { formDescriptionId } = useFormField();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <p
|
|
|
+ data-slot="form-description"
|
|
|
+ id={formDescriptionId}
|
|
|
+ className={cn('text-muted-foreground text-sm', className)}
|
|
|
+ {...props}
|
|
|
+ />
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
- const { error, formMessageId } = useFormField()
|
|
|
- const body = error ? String(error?.message ?? "") : props.children
|
|
|
-
|
|
|
- if (!body) {
|
|
|
- return null
|
|
|
- }
|
|
|
-
|
|
|
- return (
|
|
|
- <p
|
|
|
- data-slot="form-message"
|
|
|
- id={formMessageId}
|
|
|
- className={cn("text-destructive-foreground text-sm", className)}
|
|
|
- {...props}
|
|
|
- >
|
|
|
- {body}
|
|
|
- </p>
|
|
|
- )
|
|
|
+function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
|
|
|
+ const { error, formMessageId } = useFormField();
|
|
|
+ const body = error ? String(error?.message ?? '') : props.children;
|
|
|
+
|
|
|
+ if (!body) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <p
|
|
|
+ data-slot="form-message"
|
|
|
+ id={formMessageId}
|
|
|
+ className={cn('text-destructive-foreground text-sm', className)}
|
|
|
+ {...props}
|
|
|
+ >
|
|
|
+ {body}
|
|
|
+ </p>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
export {
|
|
|
- useFormField,
|
|
|
- Form,
|
|
|
- FormItem,
|
|
|
- FormLabel,
|
|
|
- FormControl,
|
|
|
- FormDescription,
|
|
|
- FormMessage,
|
|
|
- FormField,
|
|
|
-}
|
|
|
+ useFormField,
|
|
|
+ Form,
|
|
|
+ FormItem,
|
|
|
+ FormLabel,
|
|
|
+ FormControl,
|
|
|
+ FormDescription,
|
|
|
+ FormMessage,
|
|
|
+ FormField,
|
|
|
+ TranslatableFormField,
|
|
|
+};
|