{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-label-input",
  "type": "registry:ui",
  "title": "Floating Label Input",
  "description": "A TextInput whose label floats above the field on focus or fill, with an absorbed invalid/error state.",
  "dependencies": [
    "@mantine/core@^9",
    "@tabler/icons-react@^3"
  ],
  "docs": "Adapted from Mantine UI FloatingLabelInput and InputValidation at ffbf61c559f374a7ea28fcf00355e84dcbe9a908; absorbed InputValidation's error state into one component, label/placeholder/required/autoComplete became props, and a missing aria-hidden on the decorative error icon was fixed.",
  "registryDependencies": [
    "@house/mantine-ui-license"
  ],
  "files": [
    {
      "path": "registry/mantine-ui/floating-label-input/floating-label-input.tsx",
      "type": "registry:ui",
      "content": "/**\n * Adapted from Mantine UI's FloatingLabelInput and InputValidation at\n * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see\n * LICENSES/MANTINE-UI.txt after installation.\n *\n * Controlled/uncontrolled contract (settled across floating-label-input,\n * password-strength, autocomplete-loading — the tranche's three form\n * controls): whether the public `value`/`onChange` pair is event-based or\n * value-based is decided by the props surface, not by preference.\n *   - Drop-in wrappers — `Props extends Omit<XProps, ...>` with X's own ref\n *     type, rendering nothing but `<X {...} />` — keep X's exact contract,\n *     because a consumer swapping `<X>` for the wrapper shouldn't have to\n *     rewrite their handler. TextInput/PasswordInput/Textarea are\n *     event-based (`ChangeEventHandler<HTMLInputElement>`);\n *     Autocomplete/Select and the rest of the combobox family are\n *     value-based (`(value: string) => void`).\n *   - Composite components that own their own props surface (their own\n *     BoxProps/ElementProps, their own ref) aren't a drop-in for any single\n *     base, so they expose the simple value-based contract instead.\n * Both controlled and uncontrolled work everywhere. Event-based drop-ins\n * hand-roll `useState` + `isControlled = value !== undefined`, because\n * `useUncontrolled`'s onChange payload type is tied to the tracked value\n * type and can't emit a raw DOM event. Everything else uses\n * `@mantine/hooks`' `useUncontrolled`.\n *\n * This component is a drop-in for TextInput (its props extend\n * `Omit<TextInputProps, ...>` and its ref is `HTMLInputElement`):\n * event-based onChange, hand-rolled controlled/uncontrolled state.\n */\n\nimport {\n  type Factory,\n  factory,\n  type StylesApiProps,\n  TextInput,\n  type TextInputProps,\n  useProps,\n  useStyles,\n} from \"@mantine/core\";\nimport { IconAlertTriangle } from \"@tabler/icons-react\";\nimport { type ChangeEventHandler, type ReactNode, useState } from \"react\";\n\nimport classes from \"./floating-label-input.module.css\";\n\nexport type FloatingLabelInputStylesNames = \"root\" | \"label\" | \"required\" | \"input\" | \"error\";\n\nexport interface FloatingLabelInputProps\n  extends Omit<\n      TextInputProps,\n      | \"classNames\"\n      | \"styles\"\n      | \"unstyled\"\n      | \"vars\"\n      | \"attributes\"\n      | \"variant\"\n      | \"value\"\n      | \"defaultValue\"\n      | \"onChange\"\n    >,\n    StylesApiProps<FloatingLabelInputFactory> {\n  /**\n   * Controlled value. When provided, this component is fully controlled and\n   * `onChange` is the only way its displayed value changes. When omitted,\n   * the component tracks its own internal state (seeded from\n   * `defaultValue`) so it also works as a drop-in uncontrolled input.\n   */\n  value?: string;\n  /** Initial value for uncontrolled usage. Ignored once `value` is provided. */\n  defaultValue?: string;\n  onChange?: ChangeEventHandler<HTMLInputElement>;\n  /**\n   * Icon rendered in the right section while `error` is set. Pass `null` to\n   * keep the invalid styling without an icon.\n   * @default <IconAlertTriangle />\n   */\n  errorIcon?: ReactNode | null;\n}\n\nexport type FloatingLabelInputFactory = Factory<{\n  props: FloatingLabelInputProps;\n  ref: HTMLInputElement;\n  stylesNames: FloatingLabelInputStylesNames;\n}>;\n\nconst DEFAULT_ERROR_ICON = <IconAlertTriangle stroke={1.5} size={18} aria-hidden=\"true\" />;\n\nexport const FloatingLabelInput = factory<FloatingLabelInputFactory>((_props) => {\n  const props = useProps(\"FloatingLabelInput\", null, _props);\n  const {\n    classNames,\n    className,\n    style,\n    styles,\n    unstyled,\n    vars,\n    attributes,\n    ref,\n    label = \"Floating label\",\n    placeholder = \"OMG, it also has a placeholder\",\n    required = true,\n    autoComplete = \"nope\",\n    value,\n    defaultValue,\n    onChange,\n    onFocus,\n    onBlur,\n    error,\n    errorIcon = DEFAULT_ERROR_ICON,\n    rightSection,\n    labelProps,\n    ...others\n  } = props;\n\n  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? \"\");\n  const [focused, setFocused] = useState(false);\n  const isControlled = value !== undefined;\n  const currentValue = isControlled ? value : uncontrolledValue;\n  const floating = currentValue.trim().length !== 0 || focused || undefined;\n\n  const getStyles = useStyles<FloatingLabelInputFactory>({\n    name: \"FloatingLabelInput\",\n    classes,\n    props,\n    className,\n    style,\n    classNames,\n    styles,\n    unstyled,\n    attributes,\n    vars,\n  });\n\n  const errorIconSection =\n    error && errorIcon !== null ? (\n      <span className={classes.errorIcon}>{errorIcon}</span>\n    ) : undefined;\n\n  return (\n    <TextInput\n      ref={ref}\n      unstyled={unstyled}\n      label={label}\n      placeholder={placeholder}\n      required={required}\n      autoComplete={autoComplete}\n      value={currentValue}\n      error={error}\n      rightSection={errorIconSection ?? rightSection}\n      onChange={(event) => {\n        if (!isControlled) setUncontrolledValue(event.currentTarget.value);\n        onChange?.(event);\n      }}\n      onFocus={(event) => {\n        setFocused(true);\n        onFocus?.(event);\n      }}\n      onBlur={(event) => {\n        setFocused(false);\n        onBlur?.(event);\n      }}\n      data-floating={floating}\n      labelProps={{ ...labelProps, \"data-floating\": floating }}\n      classNames={{\n        root: getStyles(\"root\").className,\n        label: getStyles(\"label\").className,\n        required: getStyles(\"required\").className,\n        input: getStyles(\"input\").className,\n        error: getStyles(\"error\").className,\n      }}\n      styles={{\n        root: getStyles(\"root\").style,\n        label: getStyles(\"label\").style,\n        required: getStyles(\"required\").style,\n        input: getStyles(\"input\").style,\n        error: getStyles(\"error\").style,\n      }}\n      {...others}\n    />\n  );\n});\n\nFloatingLabelInput.classes = classes;\nFloatingLabelInput.displayName = \"FloatingLabelInput\";\n\nexport namespace FloatingLabelInput {\n  export type Props = FloatingLabelInputProps;\n  export type StylesNames = FloatingLabelInputStylesNames;\n  export type Factory = FloatingLabelInputFactory;\n}\n"
    },
    {
      "path": "registry/mantine-ui/floating-label-input/floating-label-input.module.css",
      "type": "registry:file",
      "target": "@ui/floating-label-input.module.css",
      "content": "/*\n * Adapted from Mantine UI's FloatingLabelInput and InputValidation at\n * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see\n * LICENSES/MANTINE-UI.txt after installation.\n */\n.root {\n  position: relative;\n}\n\n.label {\n  position: absolute;\n  z-index: 2;\n  top: 7px;\n  left: var(--mantine-spacing-sm);\n  pointer-events: none;\n  color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));\n  transition:\n    transform 150ms ease,\n    font-size 150ms ease,\n    color 150ms ease;\n\n  &[data-floating] {\n    transform: translate(calc(var(--mantine-spacing-sm) * -1), -28px);\n    font-size: var(--mantine-font-size-xs);\n    font-weight: 500;\n    color: light-dark(var(--mantine-color-black), var(--mantine-color-white));\n  }\n}\n\n.required {\n  transition: opacity 150ms ease;\n  opacity: 0;\n\n  [data-floating] & {\n    opacity: 1;\n  }\n}\n\n.input {\n  &::placeholder {\n    transition: color 150ms ease;\n    color: transparent;\n  }\n\n  &[data-floating] {\n    &::placeholder {\n      color: var(--mantine-color-placeholder);\n    }\n  }\n\n  &[data-error] {\n    background-color: var(--mantine-color-red-light);\n  }\n}\n\n.errorIcon {\n  color: light-dark(var(--mantine-color-red-6), var(--mantine-color-red-7));\n}\n"
    }
  ],
  "meta": {
    "mantine": {
      "requires": ">=9",
      "provider": "MantineProvider",
      "stylesApi": {
        "FloatingLabelInput": [
          "root",
          "label",
          "required",
          "input",
          "error"
        ]
      },
      "props": {
        "FloatingLabelInput": [
          {
            "name": "label",
            "type": "string",
            "required": false,
            "default": "\"Floating label\"",
            "description": "TextInput label text, rendered floating above the field on focus or fill."
          },
          {
            "name": "placeholder",
            "type": "string",
            "required": false,
            "default": "\"OMG, it also has a placeholder\"",
            "description": "TextInput placeholder shown while the field is empty and unfocused."
          },
          {
            "name": "required",
            "type": "boolean",
            "required": false,
            "default": "true",
            "description": "Renders the asterisk, which fades in only while the label is floating."
          },
          {
            "name": "autoComplete",
            "type": "string",
            "required": false,
            "default": "\"nope\"",
            "description": "Browser-autofill-suppression default carried over from upstream."
          },
          {
            "name": "value",
            "type": "string",
            "required": false,
            "description": "Controlled value. When provided, this component is fully controlled and onChange is the only way its displayed value changes."
          },
          {
            "name": "defaultValue",
            "type": "string",
            "required": false,
            "description": "Initial value for uncontrolled usage. Ignored once value is provided."
          },
          {
            "name": "onChange",
            "type": "ChangeEventHandler<HTMLInputElement>",
            "required": false,
            "description": "Fires on every keystroke regardless of controlled/uncontrolled mode; matches TextInput's own onChange signature."
          },
          {
            "name": "error",
            "type": "ReactNode",
            "required": false,
            "description": "Inherited from TextInputProps. Renders the red invalid border/background and the error message, and composes with the floating label."
          },
          {
            "name": "errorIcon",
            "type": "ReactNode | null",
            "required": false,
            "default": "<IconAlertTriangle />",
            "description": "Shown in the right section only while error is set; pass null to keep the invalid styling without an icon."
          },
          {
            "name": "...others",
            "type": "Omit<TextInputProps, \"value\" | \"defaultValue\" | \"onChange\" | \"classNames\" | \"styles\" | \"unstyled\" | \"vars\" | \"attributes\" | \"variant\">",
            "description": "All other TextInputProps are forwarded to the underlying TextInput (size, radius, disabled, leftSection, wrapperProps, description, id, name, etc). variant is intentionally not exposed — the CSS module assumes Mantine's default input chrome. Mantine Styles API props (classNames, styles, unstyled) are also accepted."
          }
        ]
      },
      "usage": {
        "path": "registry/mantine-ui/floating-label-input/floating-label-input.usage.tsx",
        "content": "import { useState } from \"react\";\nimport { FloatingLabelInput } from \"@/components/ui/floating-label-input\";\n\nexport function SignupEmailField() {\n  const [email, setEmail] = useState(\"\");\n  const isValid = email.trim().length === 0 || email.includes(\"@\");\n\n  return (\n    <FloatingLabelInput\n      label=\"Email\"\n      placeholder=\"you@example.com\"\n      autoComplete=\"email\"\n      value={email}\n      onChange={(event) => setEmail(event.currentTarget.value)}\n      error={isValid ? undefined : \"Enter a valid email address\"}\n    />\n  );\n}\n"
      }
    }
  }
}
