{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "form",
  "type": "registry:ui",
  "title": "Form",
  "description": "React Hook Form + Zod integration: FormField wires labels, help text and errors to any Junco control.",
  "dependencies": [
    "react-hook-form@^7.91.0"
  ],
  "registryDependencies": [
    "https://junco.madebytobi.com/r/button.json",
    "https://junco.madebytobi.com/r/checkbox.json",
    "https://junco.madebytobi.com/r/field.json",
    "https://junco.madebytobi.com/r/junco-theme.json",
    "https://junco.madebytobi.com/r/label.json",
    "https://junco.madebytobi.com/r/radio-group.json",
    "https://junco.madebytobi.com/r/select.json",
    "https://junco.madebytobi.com/r/switch.json",
    "https://junco.madebytobi.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/junco/ui/form.tsx",
      "type": "registry:ui",
      "content": "import * as React from \"react\";\nimport {\n  FormProvider,\n  useController,\n  useFormContext,\n  type ControllerFieldState,\n  type ControllerRenderProps,\n  type FieldPath,\n  type FieldValues,\n  type SubmitErrorHandler,\n  type SubmitHandler,\n  type UseFormReturn,\n} from \"react-hook-form\";\nimport { Button, type ButtonProps } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Field, type FieldProps } from \"@/components/ui/field\";\nimport { Label } from \"@/components/ui/label\";\nimport { RadioGroup } from \"@/components/ui/radio-group\";\nimport { Select } from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface FormProps<TValues extends FieldValues>\n  extends Omit<React.FormHTMLAttributes<HTMLFormElement>, \"onSubmit\" | \"onError\"> {\n  /** The object from `useForm()`. */\n  form: UseFormReturn<TValues>;\n  onSubmit: SubmitHandler<TValues>;\n  onError?: SubmitErrorHandler<TValues>;\n}\n\n/**\n * React Hook Form + Zod integration: FormField wires labels, help text and errors to any Junco control.\n *\n *   const form = useForm<Values>({ resolver: zodResolver(schema), defaultValues });\n *   <Form form={form} onSubmit={save}>\n *     <FormField name=\"email\" label=\"Email\"><Input type=\"email\" /></FormField>\n *     <FormSubmit>Save</FormSubmit>\n *   </Form>\n */\nexport function Form<TValues extends FieldValues>({ form, onSubmit, onError, className, children, ...props }: FormProps<TValues>) {\n  return (\n    <FormProvider {...form}>\n      <form\n        noValidate\n        onSubmit={form.handleSubmit(onSubmit, onError)}\n        className={cn(\"grid gap-5\", className)}\n        {...props}\n      >\n        {children}\n      </form>\n    </FormProvider>\n  );\n}\n\ntype Kind = \"checked\" | \"value\" | \"event\";\n\nfunction kindOf(child: React.ReactElement): Kind {\n  if (child.type === Checkbox || child.type === Switch) return \"checked\";\n  if (child.type === Select || child.type === RadioGroup) return \"value\";\n  return \"event\";\n}\n\nexport interface FormFieldRenderProps<TValues extends FieldValues, TName extends FieldPath<TValues>> {\n  field: ControllerRenderProps<TValues, TName>;\n  fieldState: ControllerFieldState;\n  /** Spread on a RadioGroup or other custom group so it is named by the label. */\n  groupProps: { \"aria-labelledby\": string; \"aria-describedby\"?: string; \"aria-invalid\"?: boolean };\n}\n\nexport interface FormFieldProps<TValues extends FieldValues, TName extends FieldPath<TValues>>\n  extends Omit<FieldProps, \"children\" | \"error\" | \"id\"> {\n  name: TName;\n  /**\n   * A single Junco control (Input, Textarea, Select, Checkbox, Switch, RadioGroup); it gets wired\n   * up automatically. For anything else use `render`.\n   */\n  children?: React.ReactElement;\n  render?: (props: FormFieldRenderProps<TValues, TName>) => React.ReactElement;\n  /** \"inline\" puts the label beside the control (checkboxes and switches). Default: inline for those, stacked otherwise. */\n  layout?: \"stacked\" | \"inline\";\n}\n\n/** A form field bound to react-hook-form: label, control, help text and the validation error. */\nexport function FormField<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>>({\n  name,\n  label,\n  description,\n  isRequired,\n  isDisabled,\n  children,\n  render,\n  layout,\n  className,\n  classNames,\n  ...props\n}: FormFieldProps<TValues, TName>) {\n  const { control } = useFormContext<TValues>();\n  const { field, fieldState } = useController<TValues, TName>({ name, control, disabled: isDisabled });\n  const id = `form-${React.useId().replace(/[^\\w-]/g, \"\")}-${String(name).replace(/[^\\w-]/g, \"-\")}`;\n  const error = fieldState.error?.message ?? (fieldState.error ? \"Invalid value\" : undefined);\n\n  const describedBy =\n    [description ? `${id}-description` : null, error ? `${id}-error` : null].filter(Boolean).join(\" \") || undefined;\n  const groupProps = { \"aria-labelledby\": `${id}-label`, \"aria-describedby\": describedBy, \"aria-invalid\": error ? true : undefined };\n\n  let control_: React.ReactNode = null;\n  let kind: Kind = \"event\";\n  if (render) {\n    control_ = render({ field, fieldState, groupProps });\n  } else if (children && React.isValidElement(children)) {\n    kind = kindOf(children);\n    const childProps = children.props as Record<string, unknown>;\n    const bound: Record<string, unknown> =\n      kind === \"checked\"\n        ? { checked: Boolean(field.value), onCheckedChange: (v: unknown) => field.onChange(v === true), ref: field.ref }\n        : kind === \"value\"\n          ? { value: field.value ?? \"\", onValueChange: field.onChange }\n          : { value: field.value ?? \"\", onChange: field.onChange, ref: field.ref };\n    control_ = React.cloneElement(children, {\n      ...bound,\n      name: field.name,\n      onBlur: (e: unknown) => {\n        field.onBlur();\n        (childProps.onBlur as ((e: unknown) => void) | undefined)?.(e);\n      },\n      ...(children.type === RadioGroup ? groupProps : {}),\n      ...(children.type === Select ? { name: field.name } : {}),\n    } as Record<string, unknown>);\n  }\n\n  const inline = (layout ?? (kind === \"checked\" ? \"inline\" : \"stacked\")) === \"inline\";\n\n  return (\n    <Field\n      id={id}\n      label={inline ? undefined : label}\n      description={description}\n      error={error}\n      isRequired={isRequired}\n      isDisabled={isDisabled}\n      className={className}\n      classNames={classNames}\n      data-name={name}\n      {...props}\n    >\n      {inline ? (\n        <div className=\"flex items-center gap-2\">\n          {control_}\n          {label && (\n            <Label id={`${id}-label`} htmlFor={id} isRequired={isRequired} className={cn(isDisabled && \"opacity-50\", classNames?.label)}>\n              {label}\n            </Label>\n          )}\n        </div>\n      ) : (\n        control_\n      )}\n    </Field>\n  );\n}\n\n/** A submit button that shows a spinner while the form submits. */\nexport function FormSubmit({ isLoading, children = \"Submit\", ...props }: ButtonProps) {\n  const { formState } = useFormContext();\n  return (\n    <Button type=\"submit\" isLoading={isLoading ?? formState.isSubmitting} {...props}>\n      {children}\n    </Button>\n  );\n}\n\n/**\n * Shows a form-wide error set with `form.setError(\"root\", { message })`, e.g. a failed request.\n * Announced to screen readers when it appears.\n */\nexport function FormRootError({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  const { formState } = useFormContext();\n  const message = formState.errors.root?.message;\n  if (!message) return null;\n  return (\n    <div\n      role=\"alert\"\n      className={cn(\n        \"rounded-card border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive-text\",\n        className,\n      )}\n      {...props}\n    >\n      {String(message)}\n    </div>\n  );\n}\n\nexport { useForm, useFormContext, useWatch, type SubmitHandler, type UseFormReturn } from \"react-hook-form\";\n"
    }
  ],
  "docs": "Docs and examples: https://junco.madebytobi.com/docs/components/form",
  "meta": {
    "hash": "69b9c68ebb55bde2",
    "source": "packages/react/src/form/form.tsx",
    "exports": [
      "FormProps",
      "Form",
      "FormFieldRenderProps",
      "FormFieldProps",
      "FormField",
      "FormSubmit",
      "FormRootError",
      "useForm",
      "useFormContext",
      "useWatch",
      "SubmitHandler",
      "UseFormReturn"
    ],
    "props": {
      "Form": {
        "extends": "<TValues extends FieldValues> extends Omit<React.FormHTMLAttributes<HTMLFormElement>, \"onSubmit\" | \"onError\">",
        "props": [
          {
            "name": "form",
            "optional": false,
            "type": "UseFormReturn<TValues>",
            "description": "The object from `useForm()`."
          },
          {
            "name": "onSubmit",
            "optional": false,
            "type": "SubmitHandler<TValues>"
          },
          {
            "name": "onError",
            "optional": true,
            "type": "SubmitErrorHandler<TValues>"
          }
        ]
      },
      "FormFieldRender": {
        "extends": "<TValues extends FieldValues, TName extends FieldPath<TValues>>",
        "props": [
          {
            "name": "field",
            "optional": false,
            "type": "ControllerRenderProps<TValues"
          },
          {
            "name": "fieldState",
            "optional": false,
            "type": "ControllerFieldState"
          },
          {
            "name": "groupProps",
            "optional": false,
            "type": "{ \"aria-labelledby\": string; \"aria-describedby\"?: string; \"aria-invalid\"?: boolean }",
            "description": "Spread on a RadioGroup or other custom group so it is named by the label."
          }
        ]
      },
      "FormField": {
        "extends": "<TValues extends FieldValues, TName extends FieldPath<TValues>> extends Omit<FieldProps, \"children\" | \"error\" | \"id\">",
        "props": [
          {
            "name": "name",
            "optional": false,
            "type": "TName"
          },
          {
            "name": "children",
            "optional": true,
            "type": "React.ReactElement",
            "description": "A single Junco control (Input, Textarea, Select, Checkbox, Switch, RadioGroup); it gets wired up automatically. For anything else use `render`."
          },
          {
            "name": "render",
            "optional": true,
            "type": "(props: FormFieldRenderProps<TValues, TName>) => React.ReactElement"
          },
          {
            "name": "layout",
            "optional": true,
            "type": "\"stacked\" | \"inline\"",
            "description": "\"inline\" puts the label beside the control (checkboxes and switches). Default: inline for those, stacked otherwise."
          }
        ]
      }
    },
    "usage": "const form = useForm<Values>({ resolver: zodResolver(schema), defaultValues });\n<Form form={form} onSubmit={save}>\n  <FormField name=\"email\" label=\"Email\"><Input type=\"email\" /></FormField>\n  <FormSubmit>Save</FormSubmit>\n</Form>",
    "docsUrl": "https://junco.madebytobi.com/docs/components/form"
  }
}
