Skip to content
Forms

Forms

TextInput

Pill text field with label, required marker, error message and uppercase mode.

npx bezel-add add text-input

packages/ui/src/forms/TextInput.tsx · 62 lines

import React from 'react';

type TextInputProps = {
  name: string;
  label: string;
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
  error?: string;
  disabled?: boolean;
  type?: 'text' | 'number' | 'email';
  mandatory?: boolean;
  uppercase?: boolean;
  maxLength?: number;
};

export function TextInput({
  name,
  label,
  value,
  onChange,
  placeholder,
  error,
  disabled,
  type = 'text',
  mandatory,
  uppercase,
  maxLength,
}: TextInputProps) {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const v = e.target.value;
    onChange(uppercase ? v.toUpperCase() : v);
  };

  return (
    <div className="flex flex-col gap-1">
      <label htmlFor={name} className="text-sm font-medium text-neutral-700">
        {label}
        {mandatory && <span className="text-[color:var(--bz-danger,#b91c1c)] ml-1">*</span>}
      </label>
      <input
        id={name}
        name={name}
        type={type}
        value={value}
        onChange={handleChange}
        placeholder={placeholder}
        disabled={disabled}
        maxLength={maxLength}
        className={[
          'h-11 px-4 rounded-full border bg-white text-neutral-900 placeholder:text-[color:var(--bz-ink-subtle,#6b6b70)]',
          'focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-[border-color,box-shadow] duration-200',
          uppercase ? 'uppercase' : '',
          error ? 'border-red-500 focus:border-red-500' : 'border-[color:var(--bz-line-control,#8a8a8e)]',
          disabled ? 'bg-neutral-100 text-neutral-500 cursor-not-allowed' : '',
        ].join(' ')}
        autoComplete="off"
      />
      {error && <p className="text-xs text-[color:var(--bz-danger,#b91c1c)] mt-0.5">{error}</p>}
    </div>
  );
}