{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "title": "Field",
  "author": "@fma-ds/ds",
  "description": "Layout primitives for building form fields. Pairs with `Label`, `Input`, and friends.\n\n- **`Field`**: the field wrapper.\n- **`FieldSet`/`FieldLegend`**: for grouping.\n- **`FieldLabel`/`FieldDescription`/`FieldError`**: the usual label + hint + error stack.\n- **`FieldSeparator`**: splits a group.\n- **`FieldHelp`**: an ⓘ button beside a label or column header that opens help in a popover (touch and keyboard, unlike a tooltip); its required `label` names the subject.\n- **`choiceCardClassName`**: the choice-card surface shared by `CheckboxCard` and `RadioCard`; hover uses `accent`. With react-aria the control is the card, instead of shadcn's `FieldLabel` wrapping a `Field`, which would nest labels.",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "cn@^0.5.0",
    "lucide-react@^1.61.0",
    "react-aria-components@^1.22.1"
  ],
  "registryDependencies": [
    "@fma-ds/button",
    "@fma-ds/label",
    "@fma-ds/popover",
    "@fma-ds/separator"
  ],
  "files": [
    {
      "path": "src/core/field/field.tsx",
      "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport { InfoIcon } from \"lucide-react\";\nimport { useMemo } from \"react\";\nimport { Dialog, type PopoverProps } from \"react-aria-components\";\nimport { Button } from \"@/core/button/button\";\nimport { Label } from \"@/core/label/label\";\nimport { Popover, PopoverTrigger } from \"@/core/popover/popover\";\nimport { Separator } from \"@/core/separator/separator\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n  return (\n    <fieldset\n      data-slot=\"field-set\"\n      className={cn(\n        \"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = \"legend\",\n  ...props\n}: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\n        \"mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      className={cn(\n        \"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\"group/field flex w-full gap-2 data-[invalid=true]:text-destructive\", {\n  variants: {\n    orientation: {\n      vertical: \"flex-col *:w-full [&>.sr-only]:w-auto\",\n      horizontal:\n        \"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n      responsive:\n        \"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n    },\n  },\n  defaultVariants: {\n    orientation: \"vertical\",\n  },\n});\n\nfunction Field({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"field\"\n      data-orientation={orientation}\n      className={cn(fieldVariants({ orientation }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      className={cn(\"group/field-content flex flex-1 flex-col gap-0.5 leading-snug\", className)}\n      {...props}\n    />\n  );\n}\n\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-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-data-selected:border-primary/30 has-data-selected:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-accent has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 dark:has-data-selected:border-primary/20 dark:has-data-selected:bg-primary/10\",\n        \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * An ⓘ button that opens help about a field or a column in a popover: unlike\n * a tooltip, it opens with touch and keyboard and fits a couple of sentences.\n * Place it BESIDE the `FieldLabel` or column header, never inside, where it\n * would nest one interactive element in another.\n */\nfunction FieldHelp({\n  label,\n  placement = \"bottom start\",\n  className,\n  children,\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, \"children\" | \"aria-label\"> & {\n  /** Names the subject, e.g. \"More information: CIB\". Also names the popover. */\n  label: string;\n  placement?: PopoverProps[\"placement\"];\n  children: React.ReactNode;\n}) {\n  return (\n    <PopoverTrigger>\n      <Button\n        data-slot=\"field-help\"\n        variant=\"ghost\"\n        size=\"icon-xs\"\n        aria-label={label}\n        className={cn(\"text-muted-foreground\", className)}\n        {...props}\n      >\n        <InfoIcon />\n      </Button>\n      <Popover placement={placement}>\n        <Dialog aria-label={label} className=\"outline-none\">\n          {children}\n        </Dialog>\n      </Popover>\n    </PopoverTrigger>\n  );\n}\n\n/**\n * The card surface of a choice card (`CheckboxCard`, `RadioCard`): the\n * react-aria control is the card itself, so its label isn't nested in a\n * `FieldLabel`. Same look as a `FieldLabel` wrapping a `Field`.\n */\nconst choiceCardClassName = cn(\n  \"group/choice-card relative flex w-full items-start gap-3 rounded-lg border p-3 text-sm transition-colors outline-none\",\n  \"not-data-selected:not-data-disabled:data-hovered:bg-accent\",\n  \"data-selected:border-primary/30 data-selected:bg-primary/5 dark:data-selected:border-primary/20 dark:data-selected:bg-primary/10\",\n  \"data-focus-visible:border-ring data-focus-visible:ring-3 data-focus-visible:ring-ring/50\",\n  \"data-invalid:border-destructive data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n);\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-label\"\n      className={cn(\n        \"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        \"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5\",\n        \"last:mt-0 nth-last-2:-mt-1\",\n        \"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      data-slot=\"field-separator\"\n      data-content={!!children}\n      className={cn(\n        \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\n        className,\n      )}\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={error?.message ?? index}>{error.message}</li>,\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      data-slot=\"field-error\"\n      className={cn(\"text-sm font-normal text-destructive\", className)}\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  choiceCardClassName,\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldHelp,\n  FieldLabel,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldTitle,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ds/field.tsx"
    }
  ],
  "meta": {
    "status": "stable",
    "source": "customized",
    "license": "MIT",
    "origin": "shadcn/ui, style: aria-nova (base: react-aria-components)",
    "tags": [
      "forms",
      "layout",
      "react-aria"
    ],
    "contentHash": "cb9b71a3043a"
  },
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}