{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input",
  "title": "Input",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/input/Input.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { X, Eye, EyeOff } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\ntype Size = 'small' | 'middle' | 'large'\ntype Variant = 'outlined' | 'filled' | 'borderless'\ntype Status = 'error' | 'warning'\n\nexport interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'> {\n  size?: Size\n  variant?: Variant\n  status?: Status\n  /** Leading content. A node wins over the `prefixIcon` shorthand. */\n  prefix?: React.ReactNode\n  suffix?: React.ReactNode\n  /** Shorthand for an icon at the start/end (e.g. `prefixIcon={<Mail />}`). */\n  prefixIcon?: React.ReactNode\n  suffixIcon?: React.ReactNode\n  addonBefore?: React.ReactNode\n  addonAfter?: React.ReactNode\n  allowClear?: boolean\n  showCount?: boolean\n  showPasswordToggle?: boolean\n  /** Wrapper className. Falls through to the bordered control, not the <input>. */\n  className?: string\n}\n\nconst sizeClasses: Record<Size, string> = {\n  small: 'h-8 text-xs',\n  middle: 'h-9 text-base md:text-sm',\n  large: 'h-11 text-base',\n}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  (\n    {\n      size = 'middle',\n      variant = 'outlined',\n      status,\n      prefix,\n      suffix,\n      prefixIcon,\n      suffixIcon,\n      addonBefore,\n      addonAfter,\n      allowClear,\n      showCount,\n      showPasswordToggle,\n      className,\n      type = 'text',\n      disabled,\n      readOnly,\n      maxLength,\n      value,\n      defaultValue,\n      onChange,\n      onFocus,\n      onBlur,\n      id,\n      'aria-invalid': ariaInvalid,\n      ...rest\n    },\n    ref,\n  ) => {\n    const innerRef = React.useRef<HTMLInputElement | null>(null)\n    // Merge the forwarded ref with our internal ref (needed for focus()).\n    const setRefs = React.useCallback(\n      (node: HTMLInputElement | null) => {\n        innerRef.current = node\n        if (typeof ref === 'function') ref(node)\n        else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node\n      },\n      [ref],\n    )\n\n    const isControlled = value !== undefined\n    const [internal, setInternal] = React.useState<string>(\n      defaultValue != null ? String(defaultValue) : '',\n    )\n    const currentValue = isControlled ? String(value ?? '') : internal\n\n    const [focused, setFocused] = React.useState(false)\n    const [hovered, setHovered] = React.useState(false)\n    const [passwordVisible, setPasswordVisible] = React.useState(false)\n\n    const isPassword = type === 'password'\n    const hasPrefix = !!prefix || !!prefixIcon\n    const hasSuffix = !!suffix || !!suffixIcon\n    const hasAddonBefore = !!addonBefore\n    const hasAddonAfter = !!addonAfter\n\n    const hasPasswordToggle = !!showPasswordToggle && isPassword\n    const hasCount = !!showCount && maxLength != null\n    const hasRightConfig = !!allowClear || hasPasswordToggle || hasCount\n\n    const currentLength = currentValue.length\n    const showClear = !!allowClear && currentValue.length > 0 && (focused || hovered) && !disabled && !readOnly\n    const computedType = !isPassword ? type : passwordVisible ? 'text' : 'password'\n\n    const wrapperRounded =\n      hasAddonBefore && hasAddonAfter\n        ? 'rounded-none'\n        : hasAddonBefore\n          ? 'rounded-l-none rounded-r-md'\n          : hasAddonAfter\n            ? 'rounded-r-none rounded-l-md'\n            : 'rounded-md'\n\n    const variantMap: Record<Variant, string> = {\n      outlined: 'border-input bg-transparent shadow-xs',\n      filled: 'border-transparent bg-muted/50 shadow-none',\n      borderless: 'border-transparent bg-transparent shadow-none',\n    }\n    const statusMap: Record<Status, string> = {\n      error:\n        'border-destructive focus-within:border-destructive focus-within:ring-destructive/20 dark:focus-within:ring-destructive/40',\n      warning: 'border-[var(--warning)] focus-within:border-[var(--warning)] focus-within:ring-[var(--warning)]/20',\n    }\n\n    const wrapperClasses = cn(\n      'flex w-full items-center gap-1.5 overflow-hidden border transition-[color,box-shadow] outline-none',\n      sizeClasses[size],\n      variantMap[variant],\n      status ? statusMap[status] : '',\n      !status ? 'focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]' : '',\n      disabled ? 'pointer-events-none opacity-50 cursor-not-allowed bg-muted/30' : '',\n      'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n      wrapperRounded,\n      className,\n    )\n\n    function addonClasses(position: 'before' | 'after') {\n      const roundedClass =\n        position === 'before' ? 'rounded-l-md rounded-r-none border-r-0' : 'rounded-r-md rounded-l-none border-l-0'\n      return cn(\n        'flex items-center bg-muted px-3 text-sm text-muted-foreground border border-input',\n        roundedClass,\n        sizeClasses[size],\n      )\n    }\n\n    const sidePad =\n      size === 'small' ? { l: 'pl-2', r: 'pr-2' } : size === 'large' ? { l: 'pl-3', r: 'pr-3' } : { l: 'pl-2.5', r: 'pr-2.5' }\n\n    const hasLeft = hasPrefix\n    const hasRight = hasSuffix || hasRightConfig\n    const inputPadding =\n      !hasLeft && !hasRight\n        ? cn(sidePad.l, sidePad.r)\n        : hasLeft && !hasRight\n          ? cn('pl-0', sidePad.r)\n          : !hasLeft && hasRight\n            ? cn(sidePad.l, 'pr-0')\n            : 'px-0'\n\n    function emit(next: string) {\n      if (!isControlled) setInternal(next)\n    }\n\n    function handleChange(e: React.ChangeEvent<HTMLInputElement>) {\n      emit(e.target.value)\n      onChange?.(e)\n    }\n\n    function handleClear() {\n      const node = innerRef.current\n      if (node) {\n        // Use the native setter so a controlled parent's onChange fires with a\n        // real event (React tracks the value on the DOM node's prototype).\n        const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set\n        setter?.call(node, '')\n        node.dispatchEvent(new Event('input', { bubbles: true }))\n        node.focus()\n      }\n      emit('')\n    }\n\n    function togglePassword() {\n      setPasswordVisible((v) => !v)\n      innerRef.current?.focus()\n    }\n\n    return (\n      <div className=\"flex w-full\">\n        {hasAddonBefore && <div className={addonClasses('before')}>{addonBefore}</div>}\n\n        <div\n          className={wrapperClasses}\n          data-uipkge=\"\"\n          data-slot=\"input\"\n          aria-invalid={ariaInvalid}\n          onMouseEnter={() => setHovered(true)}\n          onMouseLeave={() => setHovered(false)}\n          onClick={() => innerRef.current?.focus()}\n        >\n          {hasPrefix && (\n            <span className={cn('text-muted-foreground pointer-events-none shrink-0 select-none', sidePad.l)}>\n              {prefixIcon ?? prefix}\n            </span>\n          )}\n\n          <input\n            id={id}\n            ref={setRefs}\n            value={currentValue}\n            type={computedType}\n            disabled={disabled}\n            readOnly={readOnly}\n            maxLength={maxLength}\n            onChange={handleChange}\n            onFocus={(e) => {\n              setFocused(true)\n              onFocus?.(e)\n            }}\n            onBlur={(e) => {\n              setFocused(false)\n              onBlur?.(e)\n            }}\n            className={cn(\n              'w-full min-w-0 flex-1 bg-transparent outline-none',\n              'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground',\n              'file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium',\n              'disabled:cursor-not-allowed',\n              inputPadding,\n            )}\n            {...rest}\n          />\n\n          {/* Built-in actions render first, then the user's suffix, so the\n              slotted content is always rightmost. */}\n          <div className={cn('flex shrink-0 items-center gap-1', sidePad.r)}>\n            {showClear && (\n              <button\n                type=\"button\"\n                aria-label=\"Clear input\"\n                className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 shrink-0 rounded p-0.5 transition-colors focus-visible:ring-1 focus-visible:outline-none\"\n                onMouseDown={(e) => e.preventDefault()}\n                onClick={handleClear}\n              >\n                <X className=\"size-4\" aria-hidden=\"true\" />\n              </button>\n            )}\n\n            {hasPasswordToggle && !disabled && !readOnly && (\n              <button\n                type=\"button\"\n                aria-label={passwordVisible ? 'Hide password' : 'Show password'}\n                aria-pressed={passwordVisible}\n                className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 shrink-0 rounded p-0.5 transition-colors focus-visible:ring-1 focus-visible:outline-none\"\n                onMouseDown={(e) => e.preventDefault()}\n                onClick={togglePassword}\n              >\n                {passwordVisible ? <Eye className=\"size-4\" aria-hidden=\"true\" /> : <EyeOff className=\"size-4\" aria-hidden=\"true\" />}\n              </button>\n            )}\n\n            {hasCount && (\n              <span className=\"text-muted-foreground pointer-events-none text-xs select-none\">\n                {currentLength}/{maxLength}\n              </span>\n            )}\n\n            {hasSuffix && (\n              <span className=\"text-muted-foreground pointer-events-none select-none\">{suffixIcon ?? suffix}</span>\n            )}\n          </div>\n        </div>\n\n        {hasAddonAfter && <div className={addonClasses('after')}>{addonAfter}</div>}\n      </div>\n    )\n  },\n)\nInput.displayName = 'Input'\n\nexport { Input }\n",
      "type": "registry:ui",
      "target": "~/components/ui/input/Input.tsx"
    },
    {
      "path": "packages/registry-react/components/input/index.ts",
      "content": "export { Input, type InputProps } from './Input'\n",
      "type": "registry:ui",
      "target": "~/components/ui/input/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Text input — single-line. Three sizes, three variants (outlined / filled / borderless), error / warning status, prefix / suffix nodes, addonBefore / addonAfter, allow-clear, password toggle, and char count. Same sizing and ring treatment as the rest of the form primitives.",
  "categories": [
    "control"
  ]
}