Input.tsx
1 /** 2 * ACDC Input Component 3 * Chain-aware input with all states 4 */ 5 6 import React from 'react' 7 import type { Chain } from '../tokens' 8 9 export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> { 10 size?: 'sm' | 'md' | 'lg' 11 chain?: Chain 12 error?: boolean 13 success?: boolean 14 label?: string 15 helperText?: string 16 } 17 18 export const Input = React.forwardRef<HTMLInputElement, InputProps>( 19 ( 20 { 21 size = 'md', 22 chain, 23 error = false, 24 success = false, 25 label, 26 helperText, 27 className = '', 28 id, 29 ...props 30 }, 31 ref 32 ) => { 33 const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}` 34 35 // Wise-inspired: clean, rounded, smooth focus transitions 36 const baseStyles = ` 37 w-full 38 bg-[var(--bg-tertiary)] 39 border-2 border-[var(--border-default)] 40 text-[var(--text-primary)] 41 placeholder:text-[var(--text-tertiary)] 42 transition-all duration-300 ease-out 43 focus:outline-none 44 ` 45 46 const sizeStyles = { 47 sm: 'h-10 px-4 text-sm rounded-xl', 48 md: 'h-12 px-5 text-base rounded-xl', 49 lg: 'h-14 px-6 text-lg rounded-2xl', 50 } 51 52 const stateStyles = error 53 ? 'border-[var(--error)] focus:border-[var(--error)] focus:ring-4 focus:ring-[rgba(239,68,68,0.15)]' 54 : success 55 ? 'border-[var(--success)] focus:border-[var(--success)] focus:ring-4 focus:ring-[rgba(34,197,94,0.15)]' 56 : 'hover:border-[var(--border-strong)] focus:border-[var(--input-border-focus)] focus:ring-4 focus:ring-[rgba(43,135,255,0.15)]' 57 58 const disabledStyles = props.disabled ? 'opacity-50 cursor-not-allowed' : '' 59 60 return ( 61 <div className="w-full" data-chain={chain}> 62 {label && ( 63 <label 64 htmlFor={inputId} 65 className="block text-sm font-semibold text-[var(--text-primary)] mb-3 tracking-tight" 66 > 67 {label} 68 </label> 69 )} 70 <input 71 ref={ref} 72 id={inputId} 73 className={`${baseStyles} ${sizeStyles[size]} ${stateStyles} ${disabledStyles} ${className}`} 74 {...props} 75 /> 76 {helperText && ( 77 <p 78 className={`mt-2 text-xs ${ 79 error 80 ? 'text-[var(--error)]' 81 : success 82 ? 'text-[var(--success)]' 83 : 'text-[var(--text-tertiary)]' 84 }`} 85 > 86 {helperText} 87 </p> 88 )} 89 </div> 90 ) 91 } 92 ) 93 94 Input.displayName = 'Input' 95 96 // Address input variant (monospace, with copy) 97 export interface AddressInputProps extends Omit<InputProps, 'type'> { 98 onCopy?: () => void 99 } 100 101 export const AddressInput = React.forwardRef<HTMLInputElement, AddressInputProps>( 102 ({ onCopy, className = '', ...props }, ref) => { 103 return ( 104 <div className="relative"> 105 <Input 106 ref={ref} 107 className={`font-mono pr-10 ${className}`} 108 {...props} 109 /> 110 {onCopy && ( 111 <button 112 type="button" 113 onClick={onCopy} 114 className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]" 115 > 116 <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 117 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> 118 </svg> 119 </button> 120 )} 121 </div> 122 ) 123 } 124 ) 125 126 AddressInput.displayName = 'AddressInput'