{
  "schemaVersion": 1,
  "name": "field",
  "useFor": "Wrap every form control in Field with a FieldLabel above it; add FieldDescription for a hint (Enter a vault key or generate a random one) and FieldError, with data-invalid on the Field and aria-invalid on the control, when the value is rejected. Group fields in FieldGroup, related choices in FieldSet with a FieldLegend. Put an action beside the label (Forgot password?) in a row with justify-between. Use orientation horizontal for a label beside its control, and FieldSeparator with \"or\" between a form and other ways in.",
  "constraints": [
    "A red label on an invalid field",
    "A placeholder as the only label",
    "Errors larger than 12px",
    "Fields closer than 12px"
  ],
  "documentation": "",
  "source": "Figma 427:2715: Login (Email address, Password, Forgot password?, or), Vault (Vault key and its description), Create Agent Identity (is not available, Will be displayed as)",
  "exports": [
    "Field",
    "FieldContent",
    "FieldDescription",
    "FieldError",
    "FieldGroup",
    "FieldLabel",
    "FieldLegend",
    "FieldSeparator",
    "FieldSet",
    "FieldTitle"
  ],
  "client": true,
  "docsUrl": "https://inkbox-ds.vercel.app/components/field",
  "markdownUrl": "https://inkbox-ds.vercel.app/r/field.md",
  "examples": [
    {
      "path": "examples/field.tsx",
      "content": "import { Button } from \"@/components/ui/button\";\nimport { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\n\nexport function Preview() {\n  return (\n    <form className=\"mx-auto w-full max-w-86\">\n      <FieldGroup>\n        <Field>\n          <FieldLabel htmlFor=\"login-email\">Email address</FieldLabel>\n          <Input id=\"login-email\" type=\"email\" placeholder=\"your@email.com\" autoComplete=\"email\" />\n        </Field>\n        <Field>\n          <div className=\"flex items-center justify-between gap-3\">\n            <FieldLabel htmlFor=\"login-password\">Password</FieldLabel>\n            <a href=\"#forgot\" className=\"text-body leading-body text-fg-brand underline-offset-4 hover:underline\">\n              Forgot password?\n            </a>\n          </div>\n          <Input id=\"login-password\" type=\"password\" autoComplete=\"current-password\" />\n        </Field>\n        <Button type=\"submit\" className=\"w-full\">Log in</Button>\n        <FieldSeparator>or</FieldSeparator>\n        <Button variant=\"secondary\" className=\"w-full\">Continue with Google</Button>\n      </FieldGroup>\n    </form>\n  );\n}\n\nexport function Description() {\n  return (\n    <Field className=\"max-w-162.5\">\n      <FieldLabel htmlFor=\"vault-key\">Vault key</FieldLabel>\n      <FieldDescription>Enter a vault key or generate a random one. You&apos;ll need this to unlock the vault.</FieldDescription>\n      <Input id=\"vault-key\" type=\"password\" defaultValue=\"correct-horse-battery\" />\n    </Field>\n  );\n}\n\nexport function Invalid() {\n  return (\n    <Field data-invalid=\"true\" className=\"max-w-100\">\n      <FieldLabel htmlFor=\"handle\">Agent handle</FieldLabel>\n      <Input id=\"handle\" defaultValue=\"acme-support\" aria-invalid aria-describedby=\"handle-error\" />\n      <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-caption leading-body text-fg\">\n        Will be displayed as\n        <span className=\"rounded-sm bg-subtle px-1.5 py-0.5\">@acme-support</span>\n        <FieldError id=\"handle-error\">(is not available)</FieldError>\n      </div>\n    </Field>\n  );\n}\n\nexport function Horizontal() {\n  return (\n    <FieldGroup className=\"max-w-120\">\n      <Field orientation=\"horizontal\">\n        <FieldContent>\n          <FieldTitle>Retention</FieldTitle>\n          <FieldDescription>Days to keep delivered messages before they are deleted.</FieldDescription>\n        </FieldContent>\n        <Input aria-label=\"Retention in days\" type=\"number\" defaultValue={30} className=\"w-24\" />\n      </Field>\n      <Field orientation=\"responsive\">\n        <FieldLabel htmlFor=\"display-name\">Display name</FieldLabel>\n        <Input id=\"display-name\" placeholder=\"Agent display name\" className=\"@md/field-group:w-64\" />\n      </Field>\n    </FieldGroup>\n  );\n}\n\nexport function Fieldset() {\n  return (\n    <FieldSet className=\"max-w-120\">\n      <FieldLegend>Webhook destination</FieldLegend>\n      <FieldDescription>Inkbox signs every delivery with the key for this agent identity.</FieldDescription>\n      <FieldGroup>\n        <Field>\n          <FieldLabel htmlFor=\"webhook-url\">Endpoint URL</FieldLabel>\n          <Input id=\"webhook-url\" type=\"url\" placeholder=\"https://example.com/inkbox\" />\n        </Field>\n        <Field data-invalid=\"true\">\n          <FieldLabel htmlFor=\"webhook-secret\">Signing secret</FieldLabel>\n          <Input id=\"webhook-secret\" aria-invalid defaultValue=\"short\" />\n          <FieldError errors={[{ message: \"Use at least 32 characters.\" }, { message: \"Include letters and numbers.\" }]} />\n        </Field>\n      </FieldGroup>\n    </FieldSet>\n  );\n}\n",
      "exports": [
        "Preview",
        "Description",
        "Invalid",
        "Horizontal",
        "Fieldset"
      ],
      "requires": [
        "field",
        "button",
        "input"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Preview",
          "url": "https://inkbox-ds.vercel.app/preview/field/Preview"
        },
        {
          "name": "Description",
          "url": "https://inkbox-ds.vercel.app/preview/field/Description"
        },
        {
          "name": "Invalid",
          "url": "https://inkbox-ds.vercel.app/preview/field/Invalid"
        },
        {
          "name": "Horizontal",
          "url": "https://inkbox-ds.vercel.app/preview/field/Horizontal"
        },
        {
          "name": "Fieldset",
          "url": "https://inkbox-ds.vercel.app/preview/field/Fieldset"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Field",
  "description": "A field's label, control, description and error stacked 4px apart, fields 12px apart, with the 'or' hairline between two ways in.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "label",
    "utils"
  ],
  "packages": [
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "components/ui/field.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { Label } from \"@/components/ui/label\";\nimport { cn } from \"@/lib/utils\";\n\n// A form's fields as the console lays them out (Figma 427:2715, Login, Vault, Create Agent Identity). A field is its\n// 14px label on a 24px line, 4px above the control; fields sit 12px apart. A description under it is 14px muted\n// (Vault key: \"Enter a vault key or generate a random one\"); an error is 12px red (is not available). \"or\" between two\n// ways in is 14px muted on a hairline. A field marked data-invalid keeps its label ink: only the control and the\n// message turn red.\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n  return <fieldset data-slot=\"field-set\" className={cn(\"m-0 flex min-w-0 flex-col gap-4 border-0 p-0 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\", className)} {...props} />;\n}\n\nfunction FieldLegend({ className, variant = \"legend\", ...props }: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\"mb-1 p-0 font-sans text-fg data-[variant=label]:text-body data-[variant=label]:leading-body data-[variant=label]:font-normal data-[variant=legend]:text-body-lg data-[variant=legend]:leading-body data-[variant=legend]:font-medium\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"field-group\" className={cn(\"group/field-group @container/field-group flex w-full flex-col gap-3 [&>[data-slot=field-group]]:gap-4\", className)} {...props} />;\n}\n\nconst fieldVariants = cva(\"group/field flex w-full gap-1 data-[disabled=true]:opacity-50\", {\n  variants: {\n    orientation: {\n      // Label over control (Email address over its field).\n      vertical: \"flex-col [&>*]:w-full [&>.sr-only]:w-auto\",\n      // Label beside its control (a channel row and its switch, a checkbox and its label).\n      horizontal: \"flex-row items-center gap-3 [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],&>[role=radio]]:mt-0.5\",\n      // Over on a narrow form, beside from 28rem of container.\n      responsive: \"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:gap-3 @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto\",\n    },\n  },\n  defaultVariants: { orientation: \"vertical\" },\n});\n\nfunction Field({ className, orientation = \"vertical\", ...props }: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\n  return <div role=\"group\" data-slot=\"field\" data-orientation={orientation} className={cn(fieldVariants({ orientation }), className)} {...props} />;\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"field-content\" className={cn(\"group/field-content flex min-w-0 flex-1 flex-col gap-0.5 leading-body\", className)} {...props} />;\n}\n\n// A label that wraps a whole field becomes a choice card: the field edge and 8px corners, 14px in; the checked one takes\n// the purple edge on the lilac.\nfunction FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      data-slot=\"field-label\"\n      className={cn(\n        \"group/field-label peer/field-label flex w-fit gap-2 leading-body group-data-[disabled=true]/field:opacity-50\",\n        \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-control has-[>[data-slot=field]]:border has-[>[data-slot=field]]:border-line-field has-[>[data-slot=field]]:transition-[background-color,border-color] has-[>[data-slot=field]]:duration-150 has-[>[data-slot=field]]:ease-out *:data-[slot=field]:p-3.5 has-[>[data-slot=field]]:hover:bg-subtle has-data-[state=checked]:border-line-brand has-data-[state=checked]:bg-subtle motion-reduce:transition-none\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"field-title\" className={cn(\"flex w-fit items-center gap-2 font-sans text-body leading-body font-medium text-fg group-data-[disabled=true]/field:opacity-50\", className)} {...props} />;\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        \"m-0 font-sans text-body leading-body font-normal text-fg-muted group-has-[[data-orientation=horizontal]]/field:text-balance [[data-variant=legend]+&]:-mt-1 [&>a]:text-fg-brand [&>a]:underline-offset-4 [&>a:hover]:underline\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/** The \"or\" between two ways in: a hairline across the form with a 14px muted word on the page color. */\nfunction FieldSeparator({ children, className, ...props }: React.ComponentProps<\"div\"> & { children?: React.ReactNode }) {\n  return (\n    <div data-slot=\"field-separator\" data-content={!!children} className={cn(\"relative flex h-6 items-center text-body leading-body\", className)} {...props}>\n      <div aria-hidden className=\"absolute inset-x-0 top-1/2 h-px bg-line-subtle\" />\n      {children && (\n        <span data-slot=\"field-separator-content\" className=\"relative mx-auto block w-fit bg-surface px-3 text-fg-muted\">\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({ className, children, errors, ...props }: React.ComponentProps<\"div\"> & { errors?: Array<{ message?: string } | undefined> }) {\n  const content = React.useMemo(() => {\n    if (children) return children;\n    if (!errors?.length) return null;\n    const unique = [...new Map(errors.map((error) => [error?.message, error])).values()];\n    if (unique.length === 1) return unique[0]?.message;\n    return <ul className=\"m-0 flex list-disc flex-col gap-0.5 pl-4\">{unique.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}</ul>;\n  }, [children, errors]);\n  if (!content) return null;\n  return (\n    <div role=\"alert\" data-slot=\"field-error\" className={cn(\"font-sans text-caption leading-body font-normal text-danger\", className)} {...props}>\n      {content}\n    </div>\n  );\n}\n\nexport { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle };\n"
    }
  ]
}
