aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components/ui/form.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/ui/form.jsx')
-rw-r--r--frontend/src/components/ui/form.jsx144
1 files changed, 144 insertions, 0 deletions
diff --git a/frontend/src/components/ui/form.jsx b/frontend/src/components/ui/form.jsx
new file mode 100644
index 0000000..728ea87
--- /dev/null
+++ b/frontend/src/components/ui/form.jsx
@@ -0,0 +1,144 @@
+"use client";
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { Controller, FormProvider, useFormContext, useFormState } from "react-hook-form";
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+const FormFieldContext = React.createContext({})
+
+const FormField = (
+ {
+ ...props
+ }
+) => {
+ return (
+ <FormFieldContext.Provider value={{ name: props.name }}>
+ <Controller {...props} />
+ </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 FormItemContext = React.createContext({})
+
+function FormItem({
+ className,
+ ...props
+}) {
+ 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
+}) {
+ const { error, formItemId } = useFormField()
+
+ return (
+ <Label
+ data-slot="form-label"
+ data-error={!!error}
+ className={cn("data-[error=true]:text-destructive", className)}
+ htmlFor={formItemId}
+ {...props} />
+ );
+}
+
+function FormControl({
+ ...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
+}) {
+ const { formDescriptionId } = useFormField()
+
+ return (
+ <p
+ data-slot="form-description"
+ id={formDescriptionId}
+ className={cn("text-muted-foreground text-sm", className)}
+ {...props} />
+ );
+}
+
+function FormMessage({
+ className,
+ ...props
+}) {
+ 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 text-sm", className)}
+ {...props}>
+ {body}
+ </p>
+ );
+}
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}