{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field-group",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://usva.build/r/label.json"
  ],
  "files": [
    {
      "path": "field-group.tsx",
      "target": "components/ui/field-group.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Label, type LabelProps } from \"./label\";\n\ninterface FieldCountValue {\n  length: number;\n  max?: number;\n}\n\ninterface FieldContextValue {\n  controlId: string;\n  descriptionId: string;\n  errorId: string;\n  invalid: boolean;\n  hasDescription: boolean;\n  hasCount: boolean;\n  count: FieldCountValue | null;\n  setHasError: (present: boolean) => void;\n  setHasDescription: (present: boolean) => void;\n  setHasCount: (present: boolean) => void;\n  setCount: (count: FieldCountValue | null) => void;\n}\n\nconst FieldContext = React.createContext<FieldContextValue | null>(null);\n\nfunction useFieldContext(part: string): FieldContextValue {\n  const ctx = React.useContext(FieldContext);\n  if (!ctx) {\n    throw new Error(`${part} must be used within a <FieldGroup>`);\n  }\n  return ctx;\n}\n\nexport interface FieldGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n  id?: string;\n}\n\nexport const FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n  ({ className, id, children, ...props }, ref) => {\n    const generatedId = React.useId();\n    const base = id ?? generatedId;\n    const [hasError, setHasError] = React.useState(false);\n    const [hasDescription, setHasDescription] = React.useState(false);\n    const [hasCount, setHasCount] = React.useState(false);\n    const [count, setCount] = React.useState<FieldCountValue | null>(null);\n\n    const value = React.useMemo<FieldContextValue>(\n      () => ({\n        controlId: base,\n        descriptionId: `${base}-description`,\n        errorId: `${base}-error`,\n        invalid: hasError,\n        hasDescription,\n        hasCount,\n        count,\n        setHasError,\n        setHasDescription,\n        setHasCount,\n        setCount,\n      }),\n      [base, hasError, hasDescription, hasCount, count],\n    );\n\n    return (\n      <FieldContext.Provider value={value}>\n        <div\n          ref={ref}\n          data-invalid={hasError ? \"\" : undefined}\n          className={cn(\"flex flex-col gap-2\", className)}\n          {...props}\n        >\n          {children}\n        </div>\n      </FieldContext.Provider>\n    );\n  },\n);\nFieldGroup.displayName = \"FieldGroup\";\n\nexport type FieldLabelProps = LabelProps;\n\nexport const FieldLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(\n  ({ htmlFor, ...props }, ref) => {\n    const { controlId } = useFieldContext(\"FieldLabel\");\n    return <Label ref={ref} htmlFor={htmlFor ?? controlId} {...props} />;\n  },\n);\nFieldLabel.displayName = \"FieldLabel\";\n\nexport interface FieldControlProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactElement;\n}\n\nexport const FieldControl = React.forwardRef<HTMLDivElement, FieldControlProps>(\n  ({ className, children, ...props }, ref) => {\n    const {\n      controlId,\n      descriptionId,\n      errorId,\n      invalid,\n      hasDescription,\n      hasCount,\n      setCount,\n    } = useFieldContext(\"FieldControl\");\n\n    type ControlProps = {\n      id?: string;\n      \"aria-describedby\"?: string;\n      \"aria-invalid\"?: React.AriaAttributes[\"aria-invalid\"];\n      value?: string | number | readonly string[];\n      defaultValue?: string | number | readonly string[];\n      maxLength?: number;\n      onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n    };\n    const child = React.Children.only(\n      children,\n    ) as React.ReactElement<ControlProps>;\n    const childProps: ControlProps = child.props;\n\n    const describedBy = [\n      childProps[\"aria-describedby\"],\n      hasDescription ? descriptionId : null,\n      invalid ? errorId : null,\n    ]\n      .filter(Boolean)\n      .join(\" \");\n\n    const controlled = childProps.value !== undefined;\n    const [typedLength, setTypedLength] = React.useState<number | null>(null);\n    const length = controlled\n      ? String(childProps.value).length\n      : (typedLength ?? String(childProps.defaultValue ?? \"\").length);\n    const max = childProps.maxLength;\n\n    React.useEffect(() => {\n      if (!hasCount) return;\n      setCount({ length, max });\n      return () => setCount(null);\n    }, [hasCount, length, max, setCount]);\n\n    const countProps =\n      hasCount && !controlled\n        ? {\n            onChange: (event: React.ChangeEvent<HTMLInputElement>) => {\n              childProps.onChange?.(event);\n              setTypedLength(event.target.value.length);\n            },\n          }\n        : {};\n\n    return (\n      <div ref={ref} className={cn(className)} {...props}>\n        {React.cloneElement(child, {\n          id: childProps.id ?? controlId,\n          \"aria-describedby\": describedBy || undefined,\n          \"aria-invalid\": childProps[\"aria-invalid\"] ?? (invalid || undefined),\n          ...countProps,\n        })}\n      </div>\n    );\n  },\n);\nFieldControl.displayName = \"FieldControl\";\n\nexport type FieldDescriptionProps = React.HTMLAttributes<HTMLParagraphElement>;\n\nexport const FieldDescription = React.forwardRef<\n  HTMLParagraphElement,\n  FieldDescriptionProps\n>(({ className, ...props }, ref) => {\n  const { descriptionId, setHasDescription, invalid } =\n    useFieldContext(\"FieldDescription\");\n\n  React.useEffect(() => {\n    if (invalid) return;\n    setHasDescription(true);\n    return () => setHasDescription(false);\n  }, [setHasDescription, invalid]);\n\n  if (invalid) return null;\n\n  return (\n    <p\n      ref={ref}\n      id={descriptionId}\n      className={cn(\n        \"font-mono text-[11px] leading-relaxed text-muted text-pretty\",\n        className,\n      )}\n      {...props}\n    />\n  );\n});\nFieldDescription.displayName = \"FieldDescription\";\n\nexport type FieldCountProps = React.HTMLAttributes<HTMLParagraphElement>;\n\nexport const FieldCount = React.forwardRef<\n  HTMLParagraphElement,\n  FieldCountProps\n>(({ className, children, ...props }, ref) => {\n  const { count, setHasCount } = useFieldContext(\"FieldCount\");\n\n  React.useEffect(() => {\n    setHasCount(true);\n    return () => setHasCount(false);\n  }, [setHasCount]);\n\n  const length = count?.length ?? 0;\n  const max = count?.max;\n  const over = max !== undefined && length > max;\n\n  return (\n    <p\n      ref={ref}\n      className={cn(\n        \"self-end font-mono text-[11px] leading-relaxed tabular-nums\",\n        over ? \"text-danger\" : \"text-muted\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? (max === undefined ? `${length}` : `${length} / ${max}`)}\n    </p>\n  );\n});\nFieldCount.displayName = \"FieldCount\";\n\nexport type FieldErrorProps = React.HTMLAttributes<HTMLParagraphElement>;\n\nexport const FieldError = React.forwardRef<\n  HTMLParagraphElement,\n  FieldErrorProps\n>(({ className, children, ...props }, ref) => {\n  const { errorId, setHasError } = useFieldContext(\"FieldError\");\n\n  React.useEffect(() => {\n    setHasError(true);\n    return () => setHasError(false);\n  }, [setHasError]);\n\n  return (\n    <p\n      ref={ref}\n      id={errorId}\n      role=\"alert\"\n      className={cn(\n        \"font-mono text-[11px] leading-relaxed text-danger text-pretty\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </p>\n  );\n});\n\nFieldError.displayName = \"FieldError\";\n"
    }
  ]
}