{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "type": "registry:component",
  "title": "Field",
  "description": "A form control with its label, a line of help and its error, wired to each other.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [
    "https://panelui.dev/r/web/label.json"
  ],
  "files": [
    {
      "path": "components/panelui/field.tsx",
      "type": "registry:component",
      "target": "@components/panelui/field.tsx",
      "content": "/**\n * Field — a form control with its label, a line of help, and the reason it was refused.\n *\n * ```tsx\n * <Field name=\"email\">\n *   <Field.Label>Work email</Field.Label>\n *   <Input type=\"email\" required placeholder=\"you@company.com\" />\n *   <Field.Description>We send the receipt here.</Field.Description>\n *   <Field.Error match=\"valueMissing\">Enter an email address.</Field.Error>\n * </Field>\n * ```\n *\n * ## The ids are wired for you\n *\n * A label has to point at its control, and the control has to point at the\n * help and the error, or a screen reader reads the field with no name and no\n * reason. Field does that wiring: `Field.Label` is tied to the control inside\n * the field, and the control is described by `Field.Description` and the\n * error that is showing. Input, Textarea, Select, Checkbox, Radio Group and\n * Switch all take part, with nothing passed.\n *\n * ## The label is a Label\n *\n * `Field.Label` is the Label component, so `required`, `optional`,\n * `Label.Hint` and `Label.Action` work in it as they do anywhere else. It\n * turns to the destructive colour and fades with the field, without\n * `invalid` or `disabled` passed to it.\n *\n * ## Errors wait for the reader\n *\n * The browser's own checks — `required`, `type=\"email\"`, `minLength` — run\n * when the control is left, not on every key, so a field is not red while it\n * is still being typed. `Field.Error` with `match` shows a message for one of\n * those checks; with no `match`, it shows whenever the field is invalid,\n * which is the one to use with `invalid` set from a server's answer. An\n * error that appears slides in under the control, and the description it\n * replaces steps aside.\n */\nimport { Field as BaseField } from '@base-ui/react/field';\nimport { tv } from 'tailwind-variants';\nimport { Label, type LabelProps } from '@/components/panelui/label';\n\nconst fieldVariants = tv({\n  slots: {\n    root: 'group/field flex w-full min-w-0 flex-col gap-2',\n    description: [\n      'text-xs leading-4 text-muted-foreground',\n      // An error showing says more than the help line, so the help steps aside.\n      'group-has-[[data-slot=field-error]]/field:hidden',\n    ],\n    error: [\n      'text-xs leading-4 text-destructive',\n      'transition-[opacity,translate] duration-200 starting:-translate-y-1 starting:opacity-0 motion-reduce:transition-none',\n    ],\n  },\n});\n\nexport interface FieldProps extends Omit<BaseField.Root.Props, 'className'> {\n  className?: string;\n}\n\n/**\n * Groups a control with its label, help and error. Takes `name`, `invalid`\n * (for an error from outside, such as a server), `disabled`, and `validate`\n * for a check of your own.\n */\nfunction FieldRoot({ className, ...props }: FieldProps) {\n  return <BaseField.Root data-slot=\"field\" className={fieldVariants().root({ className })} {...props} />;\n}\nFieldRoot.displayName = 'Field';\n\nexport interface FieldLabelProps extends Omit<LabelProps, 'invalid' | 'disabled' | 'htmlFor'> {}\n\n/** The control's name, tied to it. Takes `required` and `optional` as Label does, and its parts. */\nfunction FieldLabel({ required, optional, className, children, ...props }: FieldLabelProps) {\n  return (\n    <BaseField.Label\n      data-slot=\"field-label\"\n      className={className}\n      render={(labelProps) => <Label {...labelProps} required={required} optional={optional} />}\n      {...(props as Omit<BaseField.Label.Props, 'className' | 'render'>)}\n    >\n      {children}\n    </BaseField.Label>\n  );\n}\nFieldLabel.displayName = 'Field.Label';\n\nexport interface FieldDescriptionProps extends Omit<BaseField.Description.Props, 'className'> {\n  className?: string;\n}\n\n/** A line of help under the control. Hidden while an error is showing. */\nfunction FieldDescription({ className, ...props }: FieldDescriptionProps) {\n  return (\n    <BaseField.Description\n      data-slot=\"field-description\"\n      className={fieldVariants().description({ className })}\n      {...props}\n    />\n  );\n}\nFieldDescription.displayName = 'Field.Description';\n\nexport interface FieldErrorProps extends Omit<BaseField.Error.Props, 'className'> {\n  className?: string;\n}\n\n/**\n * Why the value was refused. `match` names the check it is for, such as\n * `valueMissing` or `typeMismatch`; with none, it shows whenever the field is\n * invalid. With no children it shows the browser's own message.\n */\nfunction FieldError({ className, ...props }: FieldErrorProps) {\n  return <BaseField.Error data-slot=\"field-error\" className={fieldVariants().error({ className })} {...props} />;\n}\nFieldError.displayName = 'Field.Error';\n\nexport const Field = Object.assign(FieldRoot, {\n  Label: FieldLabel,\n  Description: FieldDescription,\n  Error: FieldError,\n});\n\nexport { FieldLabel, FieldDescription, FieldError };\n"
    }
  ]
}
