npm package: @ng-vui/text-input
Description: Angular Text Input Component for NG-VUI Library
Author: Varun Singh
Latest version: 1.0.0

# NgVuiTextInput Component

A fully-featured, accessible Angular text input component with built-in validation, error handling, and seamless form integration. Part of the ng-vui component library, designed for production-ready applications with comprehensive error messaging and visual feedback.

## 🚀 Features

- ✅ **Reactive Forms Integration** - Works seamlessly with FormControl and FormGroup
- ✅ **Built-in Validation** - Supports all Angular validators with automatic error messages
- ✅ **Error Handling** - Visual error states with customizable error messages and icons
- ✅ **Accessibility** - Full ARIA support, keyboard navigation, and screen reader compatibility
- ✅ **TypeScript** - Full type safety and IntelliSense support
- ✅ **Control Value Accessor** - Standard Angular form control interface
- ✅ **Event Handling** - Input, blur, and change event emitters
- ✅ **Auto-generated IDs** - Automatic element ID generation for accessibility
- ✅ **Required Indicators** - Visual asterisk for required fields
- ✅ **Customizable Styling** - Tailwind CSS with error state styling

## 📦 Installation

The component is part of the ng-vui component library:

```typescript
import { NgVuiTextInputComponent } from 'path/to/ng-vui-text-input';
```

### Dependencies

```bash
npm install @angular/core @angular/common @angular/forms
```

### Required CSS Framework
This component is styled with Tailwind CSS classes. Ensure your project has Tailwind CSS configured.

### CSS Setup

The component uses a combination of Tailwind CSS utility classes and custom CSS. Ensure your Tailwind CSS configuration includes all necessary utilities.

#### Custom CSS Classes
The component includes custom CSS classes for enhanced styling:
- `.animate-fadeIn` - Smooth fade-in animation for error messages
- Custom focus ring utilities
- Error state styling overrides

#### Including Component Styles
If using as a standalone component, include the component's CSS file in your build process or copy the styles to your global stylesheet.

#### Visual Features You'll Get with Proper CSS:
- ✨ **Rounded input fields** with smooth borders
- 🎯 **Violet focus rings** for accessibility
- 🚨 **Red error states** with background tint and borders
- 🔴 **Error icons** with animated messages
- ⭐ **Required field indicators** with red asterisks
- 🎨 **Smooth transitions** for all state changes
- 📱 **Responsive design** that works on all screen sizes

#### Troubleshooting Styling Issues:
If you're still not seeing proper styling:

1. **Check Tailwind CSS** - The component uses Tailwind utility classes
2. **Verify CSS Import** - Ensure the CSS file is properly imported
3. **Check Build Process** - Make sure CSS files are included in your build
4. **Browser DevTools** - Inspect elements to see if CSS classes are applied

## 🎯 Basic Usage

### Simple Text Input

```typescript
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgVuiTextInputComponent } from 'path/to/ng-vui-text-input';

@Component({
  standalone: true,
  imports: [FormsModule, NgVuiTextInputComponent],
  template: `
    <ng-vui-text-input
      label="Full Name"
      placeholder="Enter your full name"
      [(ngModel)]="name">
    </ng-vui-text-input>
  `
})
export class MyComponent {
  name = '';
}
```

### With Reactive Forms and Validation

```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { NgVuiTextInputComponent } from 'path/to/ng-vui-text-input';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule, NgVuiTextInputComponent],
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <ng-vui-text-input
        label="Email Address"
        type="email"
        placeholder="Enter your email"
        formControlName="email"
        requiredError="Email is required for account creation"
        [submitted]="isSubmitted">
      </ng-vui-text-input>

      <ng-vui-text-input
        label="Password"
        type="password"
        placeholder="Enter your password"
        formControlName="password"
        requiredError="Password is required"
        [submitted]="isSubmitted">
      </ng-vui-text-input>

      <button type="submit" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded">
        Create Account
      </button>
    </form>
  `
})
export class UserFormComponent implements OnInit {
  userForm!: FormGroup;
  isSubmitted = false;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.userForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(8)]]
    });
  }

  onSubmit() {
    this.isSubmitted = true;
    if (this.userForm.valid) {
      console.log('Form submitted:', this.userForm.value);
    } else {
      console.log('Form has errors');
    }
  }
}
```

## 🔧 API Reference

### Component Inputs

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `label` | `string` | `''` | Label text displayed above the input |
| `type` | `string` | `'text'` | HTML input type (text, email, password, number, etc.) |
| `id` | `string \| undefined` | `undefined` | Custom element ID (auto-generated if not provided) |
| `placeholder` | `string` | `''` | Placeholder text for the input field |
| `requiredError` | `string` | `'This field is required'` | Custom error message for required validation |
| `customError` | `string \| null` | `null` | Custom error message to override all validation errors |
| `submitted` | `boolean` | `false` | Whether the parent form has been submitted (triggers error display) |

### Supported Input Types

The component supports all standard HTML input types:

| Type | Description | Example Use Case |
|------|-------------|------------------|
| `text` | Default text input | Names, titles, general text |
| `email` | Email validation | Email addresses with built-in validation |
| `password` | Masked text input | Passwords, secure fields |
| `number` | Numeric input | Ages, quantities, prices |
| `tel` | Telephone number | Phone numbers |
| `url` | URL validation | Website addresses |
| `search` | Search input | Search boxes |
| `date` | Date picker | Date selection |
| `time` | Time picker | Time selection |
| `datetime-local` | Date and time | Combined date/time selection |

### Component Outputs

| Event | Type | Description |
|-------|------|-------------|
| `blur` | `EventEmitter<string>` | Emitted when input loses focus, returns current value |
| `change` | `EventEmitter<string>` | Emitted when input value changes and loses focus |
| `input` | `EventEmitter<string>` | Emitted on every keystroke/input event |

### Form Integration

The component implements Angular's `ControlValueAccessor` interface and supports:

- **FormControl** - `new FormControl('initial value')`
- **FormGroup** - `formControlName="fieldName"`
- **NgModel** - `[(ngModel)]="property"`
- **Template-driven forms** - Works with template reference variables
- **Reactive forms** - Full validation and state management

## 🚨 Error Handling & Validation

### Built-in Validator Support

The component automatically handles and displays error messages for common Angular validators:

#### Required Validation
```typescript
@Component({
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <ng-vui-text-input
        label="Full Name"
        formControlName="name"
        requiredError="Full name is required for registration"
        [submitted]="submitted">
      </ng-vui-text-input>
    </form>
  `
})
export class RequiredValidationExample {
  form = this.fb.group({
    name: ['', Validators.required] // Shows custom error message
  });
  submitted = false;

  onSubmit() {
    this.submitted = true; // Triggers error display
  }
}
```

#### Email Validation
```typescript
// Automatically shows: "Please enter a valid email address"
this.form = this.fb.group({
  email: ['', [Validators.required, Validators.email]]
});
```

#### Length Validation
```typescript
@Component({
  template: `
    <ng-vui-text-input
      label="Username"
      formControlName="username"
      placeholder="3-20 characters"
      [submitted]="submitted">
    </ng-vui-text-input>
    <!-- Shows: "Minimum length is 3 characters" -->
    <!-- Shows: "Maximum length is 20 characters" -->
  `
})
export class LengthValidationExample {
  form = this.fb.group({
    username: ['', [
      Validators.required,
      Validators.minLength(3),    // Shows: "Minimum length is 3 characters"
      Validators.maxLength(20)    // Shows: "Maximum length is 20 characters"
    ]]
  });
}
```

#### Pattern Validation
```typescript
@Component({
  template: `
    <ng-vui-text-input
      label="Phone Number"
      formControlName="phone"
      placeholder="(555) 123-4567"
      [submitted]="submitted">
    </ng-vui-text-input>
    <!-- Shows: "Please enter a valid format" -->
  `
})
export class PatternValidationExample {
  form = this.fb.group({
    phone: ['', [
      Validators.required,
      Validators.pattern(/^\(\d{3}\) \d{3}-\d{4}$/) // Shows: "Please enter a valid format"
    ]]
  });
}
```

### Custom Error Messages

#### Override Default Messages
```typescript
@Component({
  template: `
    <ng-vui-text-input
      label="Credit Card"
      formControlName="creditCard"
      [customError]="getCreditCardError()"
      [submitted]="submitted">
    </ng-vui-text-input>
  `
})
export class CustomErrorExample {
  form = this.fb.group({
    creditCard: ['', [Validators.required, this.creditCardValidator]]
  });

  getCreditCardError(): string | null {
    const control = this.form.get('creditCard');
    
    if (control?.errors?.['required'] && (control.touched || this.submitted)) {
      return 'Credit card number is required for payment';
    }
    
    if (control?.errors?.['invalidCard'] && (control.touched || this.submitted)) {
      return 'Please enter a valid credit card number (16 digits)';
    }
    
    return null; // No custom error, use default
  }

  creditCardValidator(control: AbstractControl): ValidationErrors | null {
    const value = control.value;
    if (value && !/^\d{16}$/.test(value.replace(/\s/g, ''))) {
      return { invalidCard: true };
    }
    return null;
  }
}
```

## 🎯 Advanced Examples

### Complex Form with Multiple Validations
```typescript
@Component({
  template: `
    <form [formGroup]="registrationForm" (ngSubmit)="onRegister()">
      <div class="space-y-4">
        <!-- Email with custom error -->
        <ng-vui-text-input
          label="Email Address"
          type="email"
          formControlName="email"
          placeholder="your.email@example.com"
          requiredError="Email is required to create your account"
          [submitted]="isSubmitted">
        </ng-vui-text-input>

        <!-- Password with length validation -->
        <ng-vui-text-input
          label="Password"
          type="password"
          formControlName="password"
          placeholder="At least 8 characters"
          requiredError="Password is required for account security"
          [submitted]="isSubmitted">
        </ng-vui-text-input>

        <!-- Confirm password with custom validation -->
        <ng-vui-text-input
          label="Confirm Password"
          type="password"
          formControlName="confirmPassword"
          placeholder="Re-enter your password"
          [customError]="getPasswordMatchError()"
          [submitted]="isSubmitted">
        </ng-vui-text-input>

        <!-- Username with pattern validation -->
        <ng-vui-text-input
          label="Username"
          formControlName="username"
          placeholder="letters, numbers, underscore only"
          [submitted]="isSubmitted">
        </ng-vui-text-input>

        <button type="submit" 
                class="w-full py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
          Create Account
        </button>
      </div>
    </form>
  `
})
export class RegistrationFormComponent {
  registrationForm: FormGroup;
  isSubmitted = false;

  constructor(private fb: FormBuilder) {
    this.registrationForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [
        Validators.required, 
        Validators.minLength(8),
        this.passwordStrengthValidator
      ]],
      confirmPassword: ['', Validators.required],
      username: ['', [
        Validators.required,
        Validators.minLength(3),
        Validators.maxLength(20),
        Validators.pattern(/^[a-zA-Z0-9_]+$/)
      ]]
    }, {
      validators: this.passwordMatchValidator
    });
  }

  onRegister() {
    this.isSubmitted = true;
    if (this.registrationForm.valid) {
      console.log('Registration successful!', this.registrationForm.value);
    } else {
      console.log('Please fix form errors');
    }
  }

  getPasswordMatchError(): string | null {
    const form = this.registrationForm;
    const password = form?.get('password')?.value;
    const confirmPassword = form?.get('confirmPassword')?.value;
    
    if (form?.errors?.['passwordMismatch'] && 
        (form.get('confirmPassword')?.touched || this.isSubmitted)) {
      return 'Passwords do not match';
    }
    
    return null;
  }

  passwordMatchValidator(form: AbstractControl): ValidationErrors | null {
    const password = form.get('password')?.value;
    const confirmPassword = form.get('confirmPassword')?.value;
    
    if (password !== confirmPassword) {
      return { passwordMismatch: true };
    }
    
    return null;
  }

  passwordStrengthValidator(control: AbstractControl): ValidationErrors | null {
    const value = control.value;
    if (!value) return null;
    
    const hasUpperCase = /[A-Z]/.test(value);
    const hasLowerCase = /[a-z]/.test(value);
    const hasNumber = /[0-9]/.test(value);
    
    if (hasUpperCase && hasLowerCase && hasNumber) {
      return null;
    }
    
    return { passwordWeak: { message: 'Password must contain uppercase, lowercase, and number' } };
  }
}
```

### Real-time Search with Debouncing
```typescript
@Component({
  template: `
    <div>
      <ng-vui-text-input
        label="Search Users"
        placeholder="Type to search users..."
        formControlName="search"
        (input)="onSearchInput($event)">
      </ng-vui-text-input>

      <div *ngIf="searchResults.length > 0" class="mt-2 border rounded-lg">
        <div *ngFor="let result of searchResults" 
             class="p-3 border-b last:border-b-0 hover:bg-gray-50">
          <div class="font-medium">{{ result.name }}</div>
          <div class="text-sm text-gray-600">{{ result.email }}</div>
        </div>
      </div>

      <div *ngIf="isSearching" class="mt-2 text-gray-600">
        Searching...
      </div>
    </div>
  `
})
export class SearchComponent implements OnInit, OnDestroy {
  searchForm = this.fb.group({
    search: ['']
  });

  searchResults: any[] = [];
  isSearching = false;
  private searchSubject = new Subject<string>();
  private destroy$ = new Subject<void>();

  ngOnInit() {
    // Debounce search input
    this.searchSubject.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      takeUntil(this.destroy$)
    ).subscribe(term => {
      this.performSearch(term);
    });
  }

  onSearchInput(searchTerm: string) {
    this.searchSubject.next(searchTerm);
  }

  performSearch(term: string) {
    if (term.length < 2) {
      this.searchResults = [];
      return;
    }

    this.isSearching = true;
    
    // Simulate API call
    setTimeout(() => {
      this.searchResults = [
        { name: `User matching "${term}"`, email: 'user@example.com' },
        { name: `Another user with "${term}"`, email: 'another@example.com' }
      ];
      this.isSearching = false;
    }, 500);
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
```

### Event Handling Example
```typescript
@Component({
  template: `
    <ng-vui-text-input
      label="Comment"
      placeholder="Write a comment..."
      formControlName="comment"
      (input)="onCommentInput($event)"
      (blur)="onCommentBlur($event)"
      (change)="onCommentChange($event)">
    </ng-vui-text-input>

    <div class="mt-2 text-sm text-gray-600">
      <div>Characters: {{ characterCount }}/500</div>
      <div>Status: {{ inputStatus }}</div>
      <div>Last changed: {{ lastChanged | date:'medium' }}</div>
    </div>
  `
})
export class EventHandlingExample {
  form = this.fb.group({
    comment: ['']
  });

  characterCount = 0;
  inputStatus = 'Ready';
  lastChanged: Date | null = null;

  onCommentInput(value: string) {
    this.characterCount = value.length;
    this.inputStatus = 'Typing...';
  }

  onCommentBlur(value: string) {
    this.inputStatus = 'Focus lost';
    console.log('Comment blurred with value:', value);
  }

  onCommentChange(value: string) {
    this.lastChanged = new Date();
    this.inputStatus = 'Changed';
    console.log('Comment changed to:', value);
  }
}
```

## 🎨 Visual States & Styling

### Default State
- Clean rounded input with gray border
- Violet focus ring and border
- Smooth transitions and hover effects
- Gray placeholder text

### Error State
- Red border and background tint
- Red focus ring and border
- Error icon with descriptive message
- Red label and error text

### Required Field Indicator
- Red asterisk (*) next to label
- Only shown when field has required validator

### Disabled State (when using FormControl)
```typescript
// Disabled through form control
this.form.get('fieldName')?.disable();
```
- Gray background
- Disabled cursor
- Muted text color

## 🌐 Accessibility Features

- **Proper Labels** - Automatic `for` attribute linking label to input
- **Required Indicators** - Visual asterisk for required fields
- **Error Announcements** - Screen readers announce validation errors
- **Focus Management** - Clear focus indicators with violet ring
- **Keyboard Navigation** - Full tab and keyboard support
- **Auto-generated IDs** - Unique element IDs for accessibility

## 🧪 Testing Examples

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { NgVuiTextInputComponent } from 'path/to/ng-vui-text-input';
import { Component } from '@angular/core';

@Component({
  template: `
    <form [formGroup]="testForm">
      <ng-vui-text-input
        label="Test Input"
        formControlName="testField"
        requiredError="Test field is required"
        [submitted]="submitted">
      </ng-vui-text-input>
    </form>
  `
})
class TestHostComponent {
  testForm = this.fb.group({
    testField: ['', Validators.required]
  });
  submitted = false;

  constructor(private fb: FormBuilder) {}
}

describe('TextInputComponent Integration', () => {
  let component: TestHostComponent;
  let fixture: ComponentFixture<TestHostComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ReactiveFormsModule, NgVuiTextInputComponent],
      declarations: [TestHostComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should show required error when submitted without value', () => {
    component.submitted = true;
    fixture.detectChanges();
    
    const errorElement = fixture.nativeElement.querySelector('.text-red-600');
    expect(errorElement).toBeTruthy();
    expect(errorElement.textContent).toContain('Test field is required');
  });

  it('should emit input events correctly', () => {
    const inputElement = fixture.nativeElement.querySelector('input');
    const textInputComponent = fixture.debugElement.query(
      By.directive(NgVuiTextInputComponent)
    ).componentInstance;
    
    spyOn(textInputComponent.input, 'emit');
    
    inputElement.value = 'test value';
    inputElement.dispatchEvent(new Event('input'));
    
    expect(textInputComponent.input.emit).toHaveBeenCalledWith('test value');
  });

  it('should validate email format', () => {
    component.testForm.get('testField')?.setValidators([Validators.email]);
    component.testForm.get('testField')?.setValue('invalid-email');
    component.testForm.get('testField')?.markAsTouched();
    fixture.detectChanges();
    
    const errorElement = fixture.nativeElement.querySelector('.text-red-600');
    expect(errorElement.textContent).toContain('Please enter a valid email address');
  });
});
```

## 💡 Best Practices

### ✅ Do

```typescript
// Use meaningful labels and placeholders
<ng-vui-text-input
  label="Email Address"
  placeholder="john.doe@example.com"
  type="email"
  formControlName="email">
</ng-vui-text-input>

// Handle form submission state properly
onSubmit() {
  this.isSubmitted = true;
  if (this.form.valid) {
    // Process form
  }
}

// Use appropriate input types
<ng-vui-text-input type="email" ...> <!-- for emails -->
<ng-vui-text-input type="password" ...> <!-- for passwords -->
<ng-vui-text-input type="tel" ...> <!-- for phone numbers -->

// Provide custom error messages for better UX
<ng-vui-text-input
  requiredError="Email is required to create your account"
  formControlName="email">
</ng-vui-text-input>
```

### ❌ Don't

```typescript
// Don't use generic labels
<ng-vui-text-input label="Input" placeholder="Enter value">

// Don't forget to handle submitted state
<ng-vui-text-input formControlName="email"> <!-- Missing [submitted] binding -->

// Don't ignore validation setup
this.form = this.fb.group({
  email: [''] // Missing validators
});

// Don't use without proper form integration
<input type="text" /> <!-- Use the component instead -->
```

## 🚀 Performance Tips

1. **Use OnPush Change Detection**
```typescript
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  // ...
})
```

2. **Debounce Input Events** for search functionality
3. **Use TrackBy** functions when rendering multiple inputs in lists
4. **Lazy Load Validation** for complex forms with conditional fields

## 🔗 Related Components

This text input works seamlessly with other ng-vui library components:
- `ng-vui-accordion` - For collapsible form sections
- `ng-vui-multi-select` - For dropdown selections
- `ng-vui-date-picker` - For date inputs
- `ng-vui-modal` - For form dialogs
- `ng-vui-file-uploader-mini` - For file upload forms
- `ng-vui-switch` - For toggle inputs
- `ng-vui-rating-stars` - For rating inputs

## 🤝 Contributing

We welcome contributions! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Submit a pull request

## 📄 License

MIT © [VUI](https://github.com/ultimatevarun/ng-vui-examples)

---

**Need help?** Check our [documentation](https://ultimatevarun.github.io/ng-vui-examples/text-input.html) or open an issue on GitHub.