{
  "name": "text-field",
  "type": "registry:ui",
  "description": "Single-line input with label, description, and message parts",
  "files": [
    {
      "path": "ui/text-field.tsx",
      "type": "registry:ui",
      "content": "import { fv } from \"@facet-ui/react-variants\";\nimport { getPassthroughProps, type PassthroughProps, React, toSlotProps } from \"@lattice-ui/react-runtime\";\nimport { TextField as TextFieldPrimitive } from \"@lattice-ui/react-text-field\";\nimport { type ClassName, cn } from \"~/lib/utils\";\n\n/**\n * A `textbox` draws its own text, so unlike a button there is no second label\n * recipe here — the text classes sit on the input itself, and `font-normal` is\n * load-bearing: without a `font-*` the box renders in LegacyArial, not in the\n * theme's typeface.\n *\n * `invalid` and `disabled` are visual states of the input but props of the\n * root, because Lattice's context is what carries them to the behavior; the\n * input takes them again for its own border and fade since nothing inherits.\n *\n * One recipe object rather than five exports: every exported name costs a\n * Luau register once Vela inlines its runtime. See\n * docs/decisions/luau-register-limit.md.\n */\nexport const textFieldVariants = {\n  root: fv(\"flex-col gap-2 w-full h-fit\"),\n  input: fv(\n    \"h-9 w-full min-w-0 rounded-md border border-input shadow-sm px-3 py-1 text-left text-sm font-normal text-foreground placeholder-muted-foreground focus:border-ring\",\n  ),\n  label: fv(\"w-full h-fit text-left text-sm font-medium text-foreground\"),\n  description: fv(\"w-full h-fit whitespace-normal leading-normal text-left text-sm font-normal text-muted-foreground\"),\n  message: fv(\"w-full h-fit whitespace-normal text-left text-sm font-normal text-destructive\"),\n};\n\nexport type TextFieldProps = {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  /** Fires when the box loses focus, with the text as it stands. */\n  onValueCommit?: (value: string) => void;\n  disabled?: boolean;\n  readOnly?: boolean;\n  invalid?: boolean;\n  className?: ClassName;\n  children?: React.ReactNode;\n} & PassthroughProps<Frame>;\n\nexport type TextFieldInputProps = {\n  disabled?: boolean;\n  invalid?: boolean;\n  className?: ClassName;\n} & PassthroughProps<TextBox>;\n\nexport type TextFieldTextProps = { Text?: string; className?: ClassName } & PassthroughProps<TextLabel>;\nexport type TextFieldLabelProps = { Text?: string; className?: ClassName } & PassthroughProps<TextButton>;\n\n// The forwarded bag is widened by `toSlotProps` and then has `children`\n// dropped from its *type*: these parts type `children` as a single element\n// (what `asChild` merges onto), and the bag never actually carries one —\n// `children` is listed as an own prop — so only the type needs narrowing.\nfunction forwardProps(props: object, ownKeys: readonly string[]): { key?: React.Key } & { [index: string]: unknown } {\n  return toSlotProps(getPassthroughProps(props, ownKeys));\n}\n\nconst ROOT_OWN_PROPS = [\n  \"value\",\n  \"defaultValue\",\n  \"onValueChange\",\n  \"onValueCommit\",\n  \"disabled\",\n  \"readOnly\",\n  \"invalid\",\n  \"className\",\n  \"children\",\n] as const;\nconst INPUT_OWN_PROPS = [\"disabled\", \"invalid\", \"className\", \"children\"] as const;\nconst TEXT_OWN_PROPS = [\"Text\", \"className\", \"children\"] as const;\n\nconst NEUTRAL_PROPS = {\n  BackgroundTransparency: 1,\n  BorderSizePixel: 0,\n};\n\nexport function TextField(props: TextFieldProps) {\n  return (\n    <TextFieldPrimitive.Root\n      defaultValue={props.defaultValue}\n      disabled={props.disabled}\n      invalid={props.invalid}\n      onValueChange={props.onValueChange}\n      onValueCommit={props.onValueCommit}\n      readOnly={props.readOnly}\n      value={props.value}\n    >\n      <frame\n        className={cn(textFieldVariants.root({ className: props.className }))}\n        {...NEUTRAL_PROPS}\n        {...getPassthroughProps<Frame>(props, ROOT_OWN_PROPS)}\n      >\n        {props.children}\n      </frame>\n    </TextFieldPrimitive.Root>\n  );\n}\n\nexport function TextFieldInput(props: TextFieldInputProps) {\n  const disabled = props.disabled === true;\n\n  return (\n    <TextFieldPrimitive.Input\n      className={textFieldVariants.input({\n        className: cn(props.invalid === true && \"border-destructive\", disabled && \"opacity-50\", props.className),\n      })}\n      disabled={props.disabled}\n      {...forwardProps(props, INPUT_OWN_PROPS)}\n    />\n  );\n}\n\nexport function TextFieldLabel(props: TextFieldLabelProps) {\n  return (\n    <TextFieldPrimitive.Label\n      className={cn(textFieldVariants.label({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...forwardProps(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n\nexport function TextFieldDescription(props: TextFieldTextProps) {\n  return (\n    <TextFieldPrimitive.Description\n      className={cn(textFieldVariants.description({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...forwardProps(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n\nexport function TextFieldMessage(props: TextFieldTextProps) {\n  return (\n    <TextFieldPrimitive.Message\n      className={cn(textFieldVariants.message({ className: props.className }))}\n      Text={props.Text ?? \"\"}\n      {...forwardProps(props, TEXT_OWN_PROPS)}\n    />\n  );\n}\n"
    }
  ],
  "registryDependencies": [
    "utils"
  ],
  "dependencies": [
    "@facet-ui/react-variants@^0.1.1",
    "@lattice-ui/react-runtime@^0.8.0",
    "@lattice-ui/react-text-field@^0.8.0"
  ],
  "tokens": [
    "input",
    "foreground",
    "muted-foreground",
    "ring",
    "destructive"
  ]
}
