/ src / components / Input.tsx
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'