{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "input", "type": "registry:component", "title": "Input", "description": "Componente Concorde Input — un solo archivo, Input.tsx.", "dependencies": [ "react" ], "registryDependencies": [], "files": [ { "path": "components/Input.tsx", "type": "registry:file", "target": "components/Input.tsx", "content": "\"use client\";\n\n/**\n * Input — Generado por Concorde\n * Fuente: Figma VOYAGER · \"Input\" (nodos 3008:15036 default · 3008:15028 focus · 3008:15044 error)\n *\n * Campo de texto 234×48, radio 16, relleno blanco. 3 variantes (por el borde):\n * · default → borde gradiente 1px #8460E5 → #FFF8F1\n * · focus → borde gradiente 2px #ED8936 → #8460E5 + glow naranja\n * · error → borde rojo 1px #EF4444 + mensaje de ayuda rojo debajo\n *\n * Al enfocar (real) un input \"default\" toma automáticamente el borde de focus.\n */\n\nimport { forwardRef, useId } from \"react\";\nimport type { InputHTMLAttributes, JSX } from \"react\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type InputVariant = \"default\" | \"focus\" | \"error\";\n\n/**\n * Hereda los atributos nativos de (type, name, id, placeholder, disabled,\n * aria-*, data-*, etc.) salvo onChange, que aquí entrega el string directo.\n * className se aplica al contenedor raíz (.pinput-root); el resto va al .\n */\nexport interface InputProps\n extends Omit, \"onChange\"> {\n /** Apariencia del borde (default \"default\"). El foco real activa \"focus\" solo. */\n variant?: InputVariant;\n /** Mensaje rojo bajo el campo — se muestra con variant=\"error\" */\n errorMessage?: string;\n /** Recibe el string del input (no el evento). */\n onChange?: (value: string) => void;\n}\n\n// ─── Self-contained CSS ───────────────────────────────────────────────────────\n\nconst STYLE_ID = \"concorde-input-styles\";\n\nconst INPUT_STYLES = `\n.pinput-root {\n width: 234px;\n max-width: 100%;\n font-family: var(--vmc-font-display, \"Plus Jakarta Sans\", -apple-system, sans-serif);\n}\n.pinput {\n display: flex;\n align-items: center;\n height: 48px;\n padding: 0 16px;\n border-radius: 16px;\n border: 1px solid transparent;\n /* Borde gradiente (default): #8460E5 → #FFF8F1 sobre relleno blanco */\n background-image: linear-gradient(#ffffff, #ffffff), linear-gradient(338deg, #8460E5 0%, #FFF8F1 100%);\n background-origin: border-box;\n background-clip: padding-box, border-box;\n transition: box-shadow 0.2s, border-color 0.2s;\n}\n.pinput__field {\n flex: 1;\n min-width: 0;\n width: 100%;\n border: none;\n outline: none;\n background: transparent;\n font-family: inherit;\n font-size: 16px;\n line-height: 20px;\n color: #191C1C;\n}\n.pinput__field::placeholder { color: #6B7280; }\n.pinput__field:disabled { cursor: not-allowed; }\n\n/* Focus — real (:focus-within) o forzado (variant=\"focus\") */\n.pinput:focus-within,\n.pinput--focus {\n border-width: 2px;\n padding: 0 15px;\n background-image: linear-gradient(#ffffff, #ffffff), linear-gradient(148deg, #ED8936 0%, #8460E5 100%);\n box-shadow: rgba(237,137,54,0.15) 0px 2px 6px;\n}\n\n/* Error — borde rojo sólido + sin glow (gana sobre focus por orden/especificidad) */\n.pinput--error,\n.pinput--error:focus-within {\n border: 1px solid #EF4444;\n padding: 0 16px;\n background-image: none;\n background-color: #ffffff;\n box-shadow: none;\n}\n.pinput__msg {\n margin: 6px 0 0 4px;\n font-size: 13px;\n line-height: 18px;\n color: #EF4444;\n}\n\n.pinput--disabled { opacity: 0.6; }\n\n@media (prefers-reduced-motion: reduce) {\n .pinput { transition: none; }\n}\n`;\n\nlet _stylesInjected = false;\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nconst Input = forwardRef(function Input({\n variant = \"default\",\n type = \"text\",\n disabled = false,\n errorMessage = \"Ingresa un correo válido\",\n onChange,\n className = \"\",\n ...rest\n}, ref): JSX.Element {\n const uid = useId().replace(/:/g, \"-\");\n const msgId = `${uid}-msg`;\n const showMsg = variant === \"error\" && Boolean(errorMessage);\n\n if (typeof document !== \"undefined\" && !_stylesInjected) {\n if (!document.getElementById(STYLE_ID)) {\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = INPUT_STYLES;\n document.head.appendChild(el);\n }\n _stylesInjected = true;\n }\n\n const boxClass = [\n \"pinput\",\n variant === \"focus\" ? \"pinput--focus\" : \"\",\n variant === \"error\" ? \"pinput--error\" : \"\",\n disabled ? \"pinput--disabled\" : \"\",\n ].filter(Boolean).join(\" \");\n\n return (\n <>\n