FactuGES_ERP/packages/shadcn-ui/src/components/form.tsx

179 lines
4.1 KiB
TypeScript
Raw Normal View History

2025-10-21 09:30:45 +00:00
"use client"
2025-05-05 17:11:44 +00:00
2025-10-21 09:30:45 +00:00
import * as React from "react"
2026-04-12 10:07:54 +00:00
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
2025-05-05 17:11:44 +00:00
import {
Controller,
2025-09-24 11:49:17 +00:00
FormProvider,
useFormContext,
2025-10-21 09:30:45 +00:00
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form"
2025-05-05 17:11:44 +00:00
2025-10-21 09:30:45 +00:00
import { cn } from "@repo/shadcn-ui/lib/utils"
import { Label } from "@repo/shadcn-ui/components/label"
2025-05-05 17:11:44 +00:00
2025-10-21 09:30:45 +00:00
const Form = FormProvider
2025-05-05 17:11:44 +00:00
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
2026-04-12 10:07:54 +00:00
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
2025-05-05 17:11:44 +00:00
> = {
2025-10-21 09:30:45 +00:00
name: TName
}
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)
2025-05-05 17:11:44 +00:00
const FormField = <
TFieldValues extends FieldValues = FieldValues,
2026-04-12 10:07:54 +00:00
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
2025-05-05 17:11:44 +00:00
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
2025-10-21 09:30:45 +00:00
)
}
2025-05-05 17:11:44 +00:00
const useFormField = () => {
2025-10-21 09:30:45 +00:00
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
2026-04-12 10:07:54 +00:00
const { getFieldState, formState } = useFormContext()
2025-05-05 17:11:44 +00:00
if (!fieldContext) {
2025-10-21 09:30:45 +00:00
throw new Error("useFormField should be used within <FormField>")
2025-05-05 17:11:44 +00:00
}
2026-04-12 10:07:54 +00:00
if (!itemContext) {
throw new Error("useFormField should be used within <FormItem>")
}
const fieldState = getFieldState(fieldContext.name, formState)
2025-10-21 09:30:45 +00:00
const { id } = itemContext
2025-05-05 17:11:44 +00:00
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
2025-10-21 09:30:45 +00:00
}
}
2025-05-05 17:11:44 +00:00
type FormItemContextValue = {
2025-10-21 09:30:45 +00:00
id: string
}
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormItemContext = React.createContext<FormItemContextValue | null>(null)
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
2025-10-21 09:30:45 +00:00
const id = React.useId()
2025-05-05 17:11:44 +00:00
return (
<FormItemContext.Provider value={{ id }}>
2026-04-12 10:07:54 +00:00
<div ref={ref} className={cn("space-y-2", className)} {...props} />
2025-05-05 17:11:44 +00:00
</FormItemContext.Provider>
2025-10-21 09:30:45 +00:00
)
2026-04-12 10:07:54 +00:00
})
FormItem.displayName = "FormItem"
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
2025-10-21 09:30:45 +00:00
const { error, formItemId } = useFormField()
2025-05-05 17:11:44 +00:00
return (
<Label
2026-04-12 10:07:54 +00:00
ref={ref}
className={cn(error && "text-destructive", className)}
2025-05-05 17:11:44 +00:00
htmlFor={formItemId}
{...props}
/>
2025-10-21 09:30:45 +00:00
)
2026-04-12 10:07:54 +00:00
})
FormLabel.displayName = "FormLabel"
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
2025-10-21 09:30:45 +00:00
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
2025-05-05 17:11:44 +00:00
return (
2026-04-12 10:07:54 +00:00
<Slot
ref={ref}
2025-05-05 17:11:44 +00:00
id={formItemId}
2025-10-21 09:30:45 +00:00
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
2025-05-05 17:11:44 +00:00
aria-invalid={!!error}
{...props}
/>
2025-10-21 09:30:45 +00:00
)
2026-04-12 10:07:54 +00:00
})
FormControl.displayName = "FormControl"
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
2025-10-21 09:30:45 +00:00
const { formDescriptionId } = useFormField()
2025-05-05 17:11:44 +00:00
return (
<p
2026-04-12 10:07:54 +00:00
ref={ref}
2025-05-05 17:11:44 +00:00
id={formDescriptionId}
2026-04-12 10:07:54 +00:00
className={cn("text-sm text-muted-foreground", className)}
2025-05-05 17:11:44 +00:00
{...props}
/>
2025-10-21 09:30:45 +00:00
)
2026-04-12 10:07:54 +00:00
})
FormDescription.displayName = "FormDescription"
2025-05-05 17:11:44 +00:00
2026-04-12 10:07:54 +00:00
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
2025-10-21 09:30:45 +00:00
const { error, formMessageId } = useFormField()
2026-04-12 10:07:54 +00:00
const body = error ? String(error?.message ?? "") : children
2025-05-05 17:11:44 +00:00
if (!body) {
2025-10-21 09:30:45 +00:00
return null
2025-05-05 17:11:44 +00:00
}
return (
<p
2026-04-12 10:07:54 +00:00
ref={ref}
2025-05-05 17:11:44 +00:00
id={formMessageId}
2026-04-12 10:07:54 +00:00
className={cn("text-sm font-medium text-destructive", className)}
2025-05-05 17:11:44 +00:00
{...props}
>
{body}
</p>
2025-10-21 09:30:45 +00:00
)
2026-04-12 10:07:54 +00:00
})
FormMessage.displayName = "FormMessage"
2025-05-05 17:11:44 +00:00
export {
2025-10-21 09:30:45 +00:00
useFormField,
2025-05-05 17:11:44 +00:00
Form,
2025-09-24 11:49:17 +00:00
FormItem,
FormLabel,
2025-10-21 09:30:45 +00:00
FormControl,
FormDescription,
2025-09-24 11:49:17 +00:00
FormMessage,
2025-10-21 09:30:45 +00:00
FormField,
}