{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"input","type":"registry:ui","title":"Input","description":"Text input with icon slots, three sizes, a focus line that sweeps out from the center and an error state that shakes and slides in its message.","dependencies":[],"registryDependencies":["utils"],"files":[{"path":"registry/ui/input.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useId, useState, type InputHTMLAttributes, type ReactNode, type Ref } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst css = `\n@keyframes ui-input-shake{0%,100%{translate:0}20%{translate:-5px}40%{translate:4px}60%{translate:-3px}80%{translate:2px}}\n.ui-input[data-invalid]{animation:ui-input-shake .38s cubic-bezier(.36,.07,.19,.97)}\n@media (prefers-reduced-motion:reduce){.ui-input[data-invalid]{animation:none}}\n`;\n\nexport type InputSize = \"sm\" | \"md\" | \"lg\";\n\nconst sizes: Record<InputSize, string> = {\n  sm: \"h-8 rounded-md text-xs [--px:0.625rem] [&_svg]:size-3.5\",\n  md: \"h-10 rounded-lg text-sm [--px:0.75rem] [&_svg]:size-4\",\n  lg: \"h-12 rounded-xl text-base [--px:1rem] [&_svg]:size-5\",\n};\n\nexport interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"size\"> {\n  size?: InputSize;\n  /** Icon or text before the value (decorative). */\n  startIcon?: ReactNode;\n  /** Icon, text or a small button after the value. */\n  endIcon?: ReactNode;\n  /** Error message shown under the field. `true` marks the field invalid without a message. */\n  error?: ReactNode;\n  ref?: Ref<HTMLInputElement>;\n}\n\nexport function Input({ size = \"md\", startIcon, endIcon, error, className, id, disabled, ref, ...props }: InputProps) {\n  const auto = useId();\n  const inputId = id ?? auto;\n  const invalid = !!error || props[\"aria-invalid\"] === true || props[\"aria-invalid\"] === \"true\";\n  const message = error && error !== true ? error : null;\n  // Keep the last message around so it can collapse out instead of vanishing.\n  const [shown, setShown] = useState<ReactNode>(message);\n  if (message && message !== shown) setShown(message);\n  const describedBy = [props[\"aria-describedby\"], message ? `${inputId}-error` : null].filter(Boolean).join(\" \") || undefined;\n\n  return (\n    <div className={cn(\"grid w-full\", className)}>\n      <style href=\"ui-input\" precedence=\"default\">\n        {css}\n      </style>\n      <div\n        data-invalid={invalid || undefined}\n        className={cn(\n          \"ui-input group/input relative flex items-center gap-2 border border-input bg-background px-(--px) text-foreground shadow-xs\",\n          \"transition-[border-color,box-shadow,background-color] duration-200 hover:border-foreground/25\",\n          \"focus-within:border-ring focus-within:shadow-[0_0_0_3px_color-mix(in_oklab,var(--ring)_25%,transparent)]\",\n          \"data-invalid:border-destructive/70 data-invalid:focus-within:border-destructive data-invalid:focus-within:shadow-[0_0_0_3px_color-mix(in_oklab,var(--destructive)_22%,transparent)]\",\n          disabled && \"pointer-events-none opacity-50\",\n          sizes[size],\n        )}\n      >\n        {startIcon && (\n          <span\n            aria-hidden\n            className=\"flex shrink-0 items-center text-muted-foreground transition-[color,scale] duration-200 group-focus-within/input:scale-110 group-focus-within/input:text-foreground group-data-invalid/input:text-destructive motion-reduce:transition-none\"\n          >\n            {startIcon}\n          </span>\n        )}\n        <input\n          {...props}\n          ref={ref}\n          id={inputId}\n          disabled={disabled}\n          aria-invalid={invalid || undefined}\n          aria-describedby={describedBy}\n          className=\"h-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground placeholder:transition-opacity focus:placeholder:opacity-60 disabled:cursor-not-allowed\"\n        />\n        {endIcon && <span className=\"flex shrink-0 items-center text-muted-foreground\">{endIcon}</span>}\n        {/* Focus sweep: a bright line grows out from the center of the bottom edge. */}\n        <span\n          aria-hidden\n          className=\"pointer-events-none absolute inset-x-(--px) -bottom-px h-0.5 origin-center scale-x-0 rounded-full bg-ring transition-transform duration-300 ease-[cubic-bezier(.2,.8,.2,1)] group-focus-within/input:scale-x-100 group-data-invalid/input:bg-destructive motion-reduce:transition-none\"\n        />\n      </div>\n      <div\n        className=\"grid transition-[grid-template-rows,opacity] duration-200 ease-out motion-reduce:transition-none\"\n        style={{ gridTemplateRows: message ? \"1fr\" : \"0fr\", opacity: message ? 1 : 0 }}\n      >\n        <p id={`${inputId}-error`} role={message ? \"alert\" : undefined} className=\"overflow-hidden text-xs text-destructive\">\n          <span className=\"block pt-1.5\">{shown}</span>\n        </p>\n      </div>\n    </div>\n  );\n}\n"}]}