{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "title": "Field",
  "description": "Lays out a control with its label, description and error, and links them.",
  "dependencies": [
    "@base-ui/react",
    "@stylexjs/stylex"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json",
    "https://constella.aayush.cv/r/label.json",
    "https://constella.aayush.cv/r/separator.json"
  ],
  "files": [
    {
      "path": "src/components/primitives/field.tsx",
      "content": "\"use client\";\n\nimport { Field as FieldPrimitive } from \"@base-ui/react/field\";\nimport { Fieldset as FieldsetPrimitive } from \"@base-ui/react/fieldset\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { ComponentProps } from \"react\";\n\nimport { labelStyles } from \"@/components/primitives/label\";\nimport { Separator } from \"@/components/primitives/separator\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { typeStyles } from \"@/lib/constella/styles\";\nimport {\n  colors,\n  durations,\n  fontWeights,\n  lineHeights,\n  media,\n  radii,\n  space,\n  strokes,\n} from \"@/lib/constella/tokens.stylex\";\n\ntype Styled<T> = Omit<T, \"className\" | \"style\"> & {\n  sx?: Sx;\n};\n\nconst fieldOrientations = [\"vertical\", \"horizontal\"] as const;\ntype FieldOrientation = (typeof fieldOrientations)[number];\n\nconst legendVariants = [\"legend\", \"label\"] as const;\ntype LegendVariant = (typeof legendVariants)[number];\n\nconst card = \":has(> [data-slot='field-content'])\";\n// A flush card's field group is a list: its cards become rows, split by hairlines.\nconst list =\n  \"[data-slot='card'][data-size='flush'] > [data-slot='field-group']\";\nconst inList = \":is([data-slot='card'][data-size='flush'] > *)\";\nconst row = `:is(${list} > [data-slot='field'] > *)`;\nconst afterRow = `:is(${list} > [data-slot='field'] ~ *)`;\n\nconst cardRadius = {\n  default: null,\n  [card]: { default: radii.md, [row]: 0 },\n} as const;\nconst cardEdge = {\n  default: null,\n  [card]: { default: strokes.border, [row]: 0 },\n} as const;\n\nconst styles = stylex.create({\n  set: {\n    borderBlockEndWidth: 0,\n    borderBlockStartWidth: 0,\n    borderInlineWidth: 0,\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: space.sm,\n    marginBlock: 0,\n    marginInline: 0,\n    minWidth: 0,\n    paddingBlock: 0,\n    paddingInline: 0,\n  },\n  legend: {\n    color: colors.textPrimary,\n    fontWeight: fontWeights.medium,\n    lineHeight: lineHeights.textSm,\n    paddingBlock: 0,\n    paddingInline: 0,\n  },\n  group: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: { default: space.md, [inList]: 0 },\n    width: \"100%\",\n  },\n  field: {\n    borderBlockStartColor: { default: null, [afterRow]: colors.borderSubtle },\n    borderBlockStartStyle: { default: null, [afterRow]: \"solid\" },\n    borderBlockStartWidth: { default: null, [afterRow]: strokes.border },\n    display: \"flex\",\n    gap: space.xs,\n    width: \"100%\",\n  },\n  vertical: {\n    flexDirection: \"column\",\n  },\n  // With a description, top-aligned, so it grows downward and the control stays on the first line.\n  horizontal: {\n    alignItems: {\n      default: \"center\",\n      \":has(> [data-slot='field-content'])\": \"flex-start\",\n    },\n    flexDirection: \"row\",\n  },\n  content: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    flexGrow: 1,\n    gap: space.xxxs,\n    minWidth: 0,\n  },\n  // A label that wraps a control and its content becomes a card: the whole box toggles it.\n  label: {\n    alignItems: { default: \"center\", [card]: \"flex-start\" },\n    backgroundColor: {\n      default: null,\n      [card]: {\n        default: \"transparent\",\n        \":has([data-checked])\": {\n          default: colors.fillSubtle,\n          [row]: \"transparent\",\n        },\n        [media.hover]: {\n          default: \"transparent\",\n          \":hover:not([data-disabled])\": colors.fillSubtle,\n          \":has([data-checked])\": {\n            default: colors.fillSubtle,\n            [row]: \"transparent\",\n          },\n        },\n        \":active:not([data-disabled])\": colors.fillSubtle,\n      },\n    },\n    borderBlockEndColor: {\n      default: null,\n      [card]: {\n        default: colors.border,\n        \":has([data-checked])\": colors.infoSolid,\n      },\n    },\n    borderBlockStartColor: {\n      default: null,\n      [card]: {\n        default: colors.border,\n        \":has([data-checked])\": colors.infoSolid,\n      },\n    },\n    borderInlineColor: {\n      default: null,\n      [card]: {\n        default: colors.border,\n        \":has([data-checked])\": colors.infoSolid,\n      },\n    },\n    borderStartStartRadius: cardRadius,\n    borderStartEndRadius: cardRadius,\n    borderEndStartRadius: cardRadius,\n    borderEndEndRadius: cardRadius,\n    borderBlockEndStyle: { default: null, [card]: \"solid\" },\n    borderBlockStartStyle: { default: null, [card]: \"solid\" },\n    borderInlineStyle: { default: null, [card]: \"solid\" },\n    borderBlockEndWidth: cardEdge,\n    borderBlockStartWidth: cardEdge,\n    borderInlineWidth: cardEdge,\n    cursor: {\n      default: null,\n      [card]: { default: \"pointer\", \":is([data-disabled])\": \"not-allowed\" },\n    },\n    gap: { default: space.xs, [card]: space.sm },\n    paddingBlock: { default: null, [card]: space.sm },\n    paddingInline: {\n      default: null,\n      [card]: { default: space.sm, [row]: space.md },\n    },\n    transitionDuration: durations.hover,\n    transitionProperty: \"background-color, border-color\",\n    transitionTimingFunction: \"ease\",\n    width: { default: \"fit-content\", [card]: \"100%\" },\n  },\n  description: {\n    color: colors.textSecondary,\n    fontWeight: fontWeights.regular,\n    marginBlock: 0,\n    marginInline: 0,\n    overflowWrap: \"anywhere\",\n    textWrap: \"pretty\",\n  },\n  // A line, or a line broken by its text, so it needs no fill to sit on any surface.\n  separator: {\n    alignItems: \"center\",\n    color: colors.textMuted,\n    display: \"flex\",\n    gap: space.xs,\n    marginBlock: `calc(-1 * ${space.xs})`,\n    minHeight: lineHeights.textSm,\n  },\n  separatorLine: { flex: 1 },\n  // The only red in an invalid field besides the control's edge, a step under the label.\n  error: {\n    color: colors.dangerText,\n    overflowWrap: \"anywhere\",\n  },\n  errorList: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: space.xxxs,\n    listStylePosition: \"inside\",\n    marginBlock: 0,\n    paddingInline: 0,\n  },\n});\n\nconst orientationStyles = {\n  horizontal: styles.horizontal,\n  vertical: styles.vertical,\n} satisfies Record<FieldOrientation, stylex.StyleXStyles>;\n\nconst legendStyles = {\n  label: typeStyles.textSm,\n  legend: typeStyles.textBase,\n} satisfies Record<LegendVariant, stylex.StyleXStyles>;\n\nfunction FieldSet({ sx, ...props }: Styled<FieldsetPrimitive.Root.Props>) {\n  return (\n    <FieldsetPrimitive.Root\n      data-slot=\"field-set\"\n      {...props}\n      {...stylex.props(styles.set, sx)}\n    />\n  );\n}\n\nfunction FieldLegend({\n  sx,\n  variant = \"legend\",\n  ...props\n}: Styled<FieldsetPrimitive.Legend.Props> & { variant?: LegendVariant }) {\n  return (\n    <FieldsetPrimitive.Legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n      {...stylex.props(styles.legend, legendStyles[variant], sx)}\n    />\n  );\n}\n\nfunction FieldGroup({ sx, ...props }: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      {...props}\n      {...stylex.props(styles.group, sx)}\n    />\n  );\n}\n\nfunction Field({\n  orientation = \"vertical\",\n  sx,\n  ...props\n}: Styled<FieldPrimitive.Root.Props> & { orientation?: FieldOrientation }) {\n  return (\n    <FieldPrimitive.Root\n      data-orientation={orientation}\n      data-slot=\"field\"\n      {...props}\n      {...stylex.props(styles.field, orientationStyles[orientation], sx)}\n    />\n  );\n}\n\nfunction FieldContent({ sx, ...props }: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      {...props}\n      {...stylex.props(styles.content, sx)}\n    />\n  );\n}\n\nfunction FieldLabel({ sx, ...props }: Styled<FieldPrimitive.Label.Props>) {\n  return (\n    <FieldPrimitive.Label\n      data-slot=\"field-label\"\n      {...props}\n      {...stylex.props(labelStyles(), styles.label, sx)}\n    />\n  );\n}\n\n/** A label's look without the `<label>`, for a card's heading inside a `FieldLabel`. */\nfunction FieldTitle({\n  render,\n  sx,\n  ...props\n}: Styled<useRender.ComponentProps<\"div\">>) {\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(props, stylex.props(labelStyles(), sx)),\n    render,\n    state: { slot: \"field-title\" },\n  });\n}\n\nfunction FieldDescription({\n  sx,\n  ...props\n}: Styled<FieldPrimitive.Description.Props>) {\n  return (\n    <FieldPrimitive.Description\n      data-slot=\"field-description\"\n      {...props}\n      {...stylex.props([typeStyles.textSm, styles.description], sx)}\n    />\n  );\n}\n\n/** A fieldset's helper text. Base UI's description only works inside a `Field`. */\nfunction FieldSetDescription({ sx, ...props }: Styled<ComponentProps<\"p\">>) {\n  return (\n    <p\n      data-slot=\"field-set-description\"\n      {...props}\n      {...stylex.props([typeStyles.textSm, styles.description], sx)}\n    />\n  );\n}\n\ntype FieldErrorProps = Styled<FieldPrimitive.Error.Props> &\n  (\n    | { errors: readonly ({ message?: string } | undefined)[]; match?: never }\n    | { errors?: undefined }\n  );\n\nfunction FieldError({ errors, sx, ...props }: FieldErrorProps) {\n  if (errors === undefined) {\n    return (\n      <FieldPrimitive.Error\n        data-slot=\"field-error\"\n        {...props}\n        {...stylex.props([typeStyles.textSm, styles.error], sx)}\n      />\n    );\n  }\n  const messages = [\n    ...new Set(errors.flatMap((issue) => issue?.message ?? [])),\n  ];\n  const children =\n    props.children ??\n    (messages.length > 1 ? (\n      <ul {...stylex.props(styles.errorList)}>\n        {messages.map((message) => (\n          <li key={message}>{message}</li>\n        ))}\n      </ul>\n    ) : (\n      messages[0]\n    ));\n  if (children === undefined) {\n    return null;\n  }\n  return (\n    <FieldPrimitive.Error\n      data-slot=\"field-error\"\n      match\n      {...props}\n      {...stylex.props([typeStyles.textSm, styles.error], sx)}\n    >\n      {children}\n    </FieldPrimitive.Error>\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  sx,\n  ...props\n}: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-content={children !== undefined}\n      data-slot=\"field-separator\"\n      {...props}\n      {...stylex.props([typeStyles.textSm, styles.separator], sx)}\n    >\n      <Separator sx={styles.separatorLine} />\n      {children !== undefined && (\n        <>\n          <span data-slot=\"field-separator-content\">{children}</span>\n          <Separator sx={styles.separatorLine} />\n        </>\n      )}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldSetDescription,\n  FieldTitle,\n  fieldOrientations,\n  legendVariants,\n};\nexport type { FieldOrientation, LegendVariant };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/field/index.md",
  "type": "registry:ui"
}