shadimage

Input

Pop-Brutalist input control with solid 3px focus ring.

components/ui/input.tsx

'use client'

import * as React from 'react'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'

export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  leftIcon?: React.ReactNode
  rightIcon?: React.ReactNode
  clearable?: boolean
  onClear?: () => void
}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, type, leftIcon, rightIcon, clearable, onClear, value, onChange, ...props }, ref) => {
    const showClear = clearable && Boolean(value) && !props.disabled && !props.readOnly

    const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {
      e.preventDefault()
      onClear?.()
      // Synthesize a change so controlled consumers update their state.
      onChange?.({
        ...e,
        target: { ...(e.currentTarget as HTMLButtonElement), value: '' },
      } as unknown as React.ChangeEvent<HTMLInputElement>)
    }

    return (
      <div className={cn('relative w-full', className)}>
        {leftIcon && (
          <span className="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-rk-ink/50">
            {leftIcon}
          </span>
        )}
        <input
          type={type}
          value={value}
          onChange={onChange}
          className={cn(
            'flex h-12 w-full rounded-2xl bg-rk-surface px-4 py-2 text-sm font-extrabold text-rk-ink rk-border rk-shadow-sm placeholder:text-rk-ink/40 focus:outline-none focus:ring-4 focus:ring-black focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-120',
            leftIcon && 'pl-11',
            (rightIcon || showClear) && 'pr-11',
            className
          )}
          ref={ref}
          {...props}
        />
        {rightIcon && (
          <span className="pointer-events-none absolute right-4 top-1/2 -translate-y-1/2 text-rk-ink/50">
            {rightIcon}
          </span>
        )}
        {showClear && (
          <button
            type="button"
            aria-label="Clear input"
            onClick={handleClear}
            className="absolute right-3 top-1/2 -translate-y-1/2 flex h-6 w-6 items-center justify-center rounded-full rk-border-sm rk-shadow-xs bg-rk-canvas hover:bg-rk-primary focus:outline-none focus:ring-2 focus:ring-black cursor-pointer"
          >
            <X className="h-3.5 w-3.5 stroke-[3]" aria-hidden="true" />
          </button>
        )}
      </div>
    )
  }
)
Input.displayName = 'Input'

export { Input }
Visual style
brutalist

DependenciesBundle weight · light

clsxtailwind-merge