/ src / components / Input / Input.tsx
Input.tsx
 1  import * as React from 'react'
 2  import { cn } from '../../lib/utils'
 3  
 4  export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 5    label?: string
 6    error?: string
 7    leftIcon?: React.ReactNode
 8    rightIcon?: React.ReactNode
 9  }
10  
11  export const Input = React.forwardRef<HTMLInputElement, InputProps>(
12    ({ className, label, error, leftIcon, rightIcon, disabled, id, ...props }, ref) => {
13      const inputId = id ?? label?.toLowerCase().replace(/\s+/g, '-')
14      return (
15        <div className="flex flex-col gap-1">
16          {label && (
17            <label
18              htmlFor={inputId}
19              className="text-xs font-medium text-[var(--text-secondary,oklch(65%_0_0))]"
20            >
21              {label}
22            </label>
23          )}
24          <div className="relative flex items-center">
25            {leftIcon && (
26              <span className="pointer-events-none absolute left-3 text-[var(--text-secondary,oklch(55%_0_0))]">
27                {leftIcon}
28              </span>
29            )}
30            <input
31              id={inputId}
32              ref={ref}
33              disabled={disabled}
34              className={cn(
35                'w-full rounded-[var(--radius-base,4px)] border bg-[var(--bg-tertiary,oklch(16%_0_0))]',
36                'px-3 py-2 text-sm text-[var(--text-primary,oklch(95%_0_0))]',
37                'placeholder:text-[var(--text-disabled,oklch(40%_0_0))]',
38                'transition-colors duration-100',
39                'border-[var(--border-default,oklch(25%_0_0))]',
40                'focus:border-[oklch(52.4%_0.22_264)] focus:outline-none focus:ring-1 focus:ring-[oklch(52.4%_0.22_264/0.3)]',
41                'focus-visible:ring-2 focus-visible:ring-[oklch(52.4%_0.22_264)] focus-visible:ring-offset-1 focus-visible:ring-offset-[oklch(8%_0_0)]',
42                disabled && 'cursor-not-allowed opacity-50',
43                error && 'border-[oklch(60%_0.22_25)] focus:border-[oklch(60%_0.22_25)] focus:ring-[oklch(60%_0.22_25/0.3)]',
44                leftIcon && 'pl-9',
45                rightIcon && 'pr-9',
46                className
47              )}
48              aria-invalid={!!error}
49              aria-describedby={error ? `${inputId}-error` : undefined}
50              {...props}
51            />
52            {rightIcon && (
53              <span className="pointer-events-none absolute right-3 text-[var(--text-secondary,oklch(55%_0_0))]">
54                {rightIcon}
55              </span>
56            )}
57          </div>
58          {error && (
59            <p id={`${inputId}-error`} className="text-xs text-[oklch(60%_0.22_25)]" role="alert">
60              {error}
61            </p>
62          )}
63        </div>
64      )
65    }
66  )
67  Input.displayName = 'Input'