{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "form-field",
  "title": "Form Field",
  "description": "Labels, descriptions, requirements, validation, and character counts.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "input",
    "label",
    "textarea",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/form-field.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { type ComponentProps, createContext, useContext, useId, useState } from \"react\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const FORM_FIELD_VARIANTS = [\"outlined\", \"filled\", \"compact\"] as const;\nexport type FormFieldVariant = (typeof FORM_FIELD_VARIANTS)[number];\nexport type FormFieldProps = ComponentProps<\"div\"> & {\n  variant?: FormFieldVariant;\n  inputId?: string;\n  required?: boolean;\n  invalid?: boolean;\n  disabled?: boolean;\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  maxLength?: number;\n};\ntype FieldContext = {\n  id: string;\n  variant: FormFieldVariant;\n  required: boolean;\n  invalid: boolean;\n  disabled: boolean;\n  value: string;\n  maxLength?: number;\n  change: (value: string) => void;\n};\nconst Context = createContext<FieldContext | null>(null);\nfunction useField() {\n  const context = useContext(Context);\n  if (!context) throw new Error(\"FormField children must be used within FormField\");\n  return context;\n}\n\nconst formFieldVariants = cva(\"grid min-w-0 text-[13px]/[18px] text-foreground\", {\n  variants: {\n    variant: {\n      outlined: \"gap-1.5\",\n      filled: \"gap-1.5\",\n      compact: \"gap-1\",\n    },\n  },\n});\n\nconst formFieldControlVariants = cva(\n  [\n    \"box-border block h-auto w-full min-w-0 border text-foreground shadow-none outline-none\",\n    \"transition-[border-color,background-color,box-shadow] duration-120 ease-[ease-out] motion-reduce:transition-none\",\n    \"placeholder:text-subtle-foreground\",\n    \"hover:enabled:not-focus:border-border-strong\",\n    \"focus-visible:border-border-strong focus-visible:ring-3 focus-visible:ring-primary/24\",\n    \"aria-invalid:border-destructive/70 aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive/22\",\n    \"disabled:cursor-not-allowed disabled:opacity-55\",\n  ],\n  {\n    variants: {\n      variant: {\n        outlined:\n          \"rounded-[10px] border-border bg-background px-3 py-2.25 text-[13px]/[18px] md:text-[13px]/[18px] dark:bg-background\",\n        filled:\n          \"rounded-[10px] border-transparent bg-muted px-3 py-2.25 text-[13px]/[18px] md:text-[13px]/[18px] dark:bg-muted\",\n        compact:\n          \"rounded-lg border-border bg-background px-2.5 py-1.5 text-[12.5px]/[18px] md:text-[12.5px]/[18px] dark:bg-background\",\n      },\n    },\n  },\n);\n\nexport function FormField({\n  variant = \"outlined\",\n  inputId,\n  required = false,\n  invalid = false,\n  disabled = false,\n  value,\n  defaultValue = \"\",\n  onValueChange,\n  maxLength,\n  className,\n  children,\n  ...props\n}: FormFieldProps) {\n  const generatedId = useId();\n  const [internal, setInternal] = useState(defaultValue);\n  const context: FieldContext = {\n    id: inputId ?? generatedId,\n    variant,\n    required,\n    invalid,\n    disabled,\n    value: value ?? internal,\n    maxLength,\n    change: (next) => {\n      if (value === undefined) setInternal(next);\n      onValueChange?.(next);\n    },\n  };\n  return (\n    <Context.Provider value={context}>\n      <div\n        data-slot=\"form-field\"\n        data-variant={variant}\n        className={cn(formFieldVariants({ variant }), className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </Context.Provider>\n  );\n}\nexport function FormFieldLabel({ className, children, ...props }: ComponentProps<\"label\">) {\n  const context = useField();\n  return (\n    <Label\n      data-slot=\"form-field-label\"\n      className={cn(\n        \"block font-medium leading-[18px] select-auto\",\n        context.variant === \"compact\" ? \"text-[12.5px]\" : \"text-[13px]\",\n        className,\n      )}\n      {...props}\n      htmlFor={context.id}\n    >\n      {children}\n      {context.required && (\n        <span className=\"text-[12px] font-normal text-subtle-foreground\"> (required)</span>\n      )}\n    </Label>\n  );\n}\nfunction fieldControl(context: FieldContext) {\n  return {\n    id: context.id,\n    required: context.required,\n    disabled: context.disabled,\n    value: context.value,\n    maxLength: context.maxLength,\n    \"aria-invalid\": context.invalid || undefined,\n    \"aria-describedby\": `${context.id}-description ${context.id}-error ${context.id}-count`,\n  };\n}\nexport function FormFieldInput({\n  onChange,\n  className,\n  ...props\n}: Omit<\n  ComponentProps<\"input\">,\n  \"value\" | \"defaultValue\" | \"id\" | \"maxLength\" | \"required\" | \"disabled\"\n>) {\n  const context = useField();\n  return (\n    <Input\n      data-slot=\"form-field-input\"\n      className={cn(formFieldControlVariants({ variant: context.variant }), className)}\n      {...props}\n      {...fieldControl(context)}\n      onChange={(event) => {\n        onChange?.(event);\n        if (!event.defaultPrevented) context.change(event.target.value);\n      }}\n    />\n  );\n}\nexport function FormFieldTextarea({\n  onChange,\n  className,\n  ...props\n}: Omit<\n  ComponentProps<\"textarea\">,\n  \"value\" | \"defaultValue\" | \"id\" | \"maxLength\" | \"required\" | \"disabled\"\n>) {\n  const context = useField();\n  return (\n    <Textarea\n      rows={3}\n      data-slot=\"form-field-textarea\"\n      className={cn(\n        formFieldControlVariants({ variant: context.variant }),\n        \"field-sizing-fixed resize-y\",\n        context.variant === \"compact\" ? \"min-h-14\" : \"min-h-19\",\n        className,\n      )}\n      {...props}\n      {...fieldControl(context)}\n      onChange={(event) => {\n        onChange?.(event);\n        if (!event.defaultPrevented) context.change(event.target.value);\n      }}\n    />\n  );\n}\nexport function FormFieldDescription({ className, ...props }: ComponentProps<\"p\">) {\n  const context = useField();\n  return (\n    <p\n      data-slot=\"form-field-description\"\n      className={cn(\"m-0 text-xs/4 text-muted-foreground\", className)}\n      {...props}\n      id={`${context.id}-description`}\n    />\n  );\n}\nexport function FormFieldError({ className, ...props }: ComponentProps<\"p\">) {\n  const context = useField();\n  if (!context.invalid) return null;\n  return (\n    <p\n      role=\"alert\"\n      data-slot=\"form-field-error\"\n      className={cn(\n        \"m-0 text-xs/4 text-destructive-ink animate-in fade-in-0 slide-in-from-top-1 duration-200 ease-out-quint motion-reduce:animate-none\",\n        className,\n      )}\n      {...props}\n      id={`${context.id}-error`}\n    />\n  );\n}\nexport function FormFieldCount({ className, ...props }: ComponentProps<\"span\">) {\n  const context = useField();\n  const { maxLength } = context;\n  const length = context.value.length;\n  const atLimit = maxLength !== undefined && length >= maxLength;\n  const nearLimit = maxLength !== undefined && !atLimit && length >= maxLength * 0.9;\n  return (\n    <span\n      data-slot=\"form-field-count\"\n      className={cn(\n        \"text-right text-[11.5px] text-subtle-foreground tabular-nums transition-colors duration-150 ease-out motion-reduce:transition-none\",\n        nearLimit && \"text-warning-ink\",\n        atLimit && \"text-destructive-ink\",\n        className,\n      )}\n      {...props}\n      id={`${context.id}-count`}\n    >\n      {context.value.length}\n      {context.maxLength === undefined ? \" characters\" : ` / ${context.maxLength} characters`}\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/form-field.tsx"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}