npm package: @ng-vui/date-picker
Description: Angular Date Picker Component for UI Library
Author: Varun Singh
Latest version: 1.0.0

# @ng-vui/date-picker

A fully-featured Angular date picker component with calendar view, date ranges, localization, and accessibility support.

## 📚 **Quick Navigation**

| Section | Description | Perfect For |
|---------|-------------|------------|
| [🚀 Features](#-features) | Complete feature overview | Understanding capabilities |
| [📦 Installation](#-installation) | Setup and installation | Getting started |
| [⚡ Quick Start](#-quick-start) | Configuration examples | Initial setup |
| [🎯 Basic Usage](#-basic-usage) | Simple implementation | First-time setup |
| [🔧 Configuration](#-configuration) | Settings and options | Customization |
| [✨ Advanced Features](#-advanced-features) | Complex use cases | Advanced implementation |
| [🎨 Styling & Theming](#-styling--theming) | Visual customization | UI/UX design |
| [🌍 Internationalization](#-internationalization) | Multi-language support | Global applications |
| [🧪 Testing](#-testing) | Unit testing examples | Quality assurance |
| [🚀 Performance](#-performance-tips) | Optimization strategies | Large applications |

---

## 🎯 **Use Cases & Examples**

### **Single Date Selection**
Perfect for birthdays, deadlines, and appointment scheduling.
```typescript
<my-date-picker [(ngModel)]="birthday" [options]="{dateFormat: 'dd/mm/yyyy'}"></my-date-picker>
```

### **Date Range Selection**
Ideal for booking systems, reporting periods, and date filters.
```typescript
<my-date-picker [(ngModel)]="dateRange" [options]="{dateRange: true, rangeSeparator: ' - '}"></my-date-picker>
```

### **Form Integration**
Seamless integration with Angular reactive forms and validation.
```typescript
<my-date-picker [formControl]="dateControl" [options]="formDateOptions"></my-date-picker>
```

### **Localization**
Support for multiple languages and regional date formats.
```typescript
<my-date-picker [options]="{dayLabels: germanDayLabels, monthLabels: germanMonthLabels}"></my-date-picker>
```

### **Validation & Restrictions**
Min/max dates, disabled dates, and custom validation rules.
```typescript
<my-date-picker [options]="{disableDates: weekends, minYear: 2020, maxYear: 2025}"></my-date-picker>
```

---

## 🚀 Features

- ✅ **Date Selection** - Single date, date range, and multiple dates
- ✅ **Calendar Views** - Month, year, and decade navigation
- ✅ **Internationalization** - Multiple locales and date formats
- ✅ **Accessibility** - Full ARIA support and keyboard navigation
- ✅ **Validation** - Min/max dates, disabled dates, and custom validators
- ✅ **Reactive Forms** - Angular Forms integration
- ✅ **Customizable** - Custom templates and styling
- ✅ **Mobile Friendly** - Touch-optimized interface
- ✅ **TypeScript** - Full type safety

## 📦 Installation

```bash
npm install @ng-vui/date-picker
```

## ⚡ **Quick Start**

### **1. Basic Date Picker**
```typescript
const basicOptions: IMyDpOptions = {
  dateFormat: 'dd/mm/yyyy',
  markCurrentDay: true,
  todayBtnTxt: 'Today',
  clearBtnTxt: 'Clear'
};
```

### **2. Date Range Picker**
```typescript
const rangeOptions: IMyDpOptions = {
  dateRange: true,
  rangeSeparator: ' - ',
  dateFormat: 'dd/mm/yyyy',
  markCurrentDay: true,
  showWeekNumbers: true
};
```

### **3. Localized Date Picker**
```typescript
const localizedOptions: IMyDpOptions = {
  dateFormat: 'dd.mm.yyyy',
  firstDayOfWeek: 'mo',
  monthLabels: {
    1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
    5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
    9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
  },
  dayLabels: { su: 'So', mo: 'Mo', tu: 'Di', we: 'Mi', th: 'Do', fr: 'Fr', sa: 'Sa' }
};
```

### **4. Validated Date Picker**
```typescript
const validatedOptions: IMyDpOptions = {
  dateFormat: 'yyyy-mm-dd',
  minYear: 1900,
  maxYear: 2030,
  disableUntil: { year: 2020, month: 1, day: 1 },
  disableSince: { year: 2025, month: 12, day: 31 },
  disableWeekends: false
};
```

## 🎯 Basic Usage

### Import

```typescript
import { MyDatePickerModule } from '@ng-vui/date-picker';

// For modules
@NgModule({
  imports: [MyDatePickerModule],
  // ... module config
})

// For standalone components
import { MyDatePickerComponent } from '@ng-vui/date-picker';

@Component({
  standalone: true,
  imports: [MyDatePickerComponent],
  // ... component config
})
```

### Simple Date Picker

```typescript
@Component({
  selector: 'app-basic-example',
  template: `
    <my-date-picker 
      [options]="myDatePickerOptions" 
      [(ngModel)]="selectedDate"
      placeholder="Select date">
    </my-date-picker>
    
    <div class="mt-3" *ngIf="selectedDate">
      Selected: {{ selectedDate | json }}
    </div>
  `
})
export class BasicExampleComponent {
  selectedDate: any = null;
  
  myDatePickerOptions: IMyDpOptions = {
    dateFormat: 'dd.mm.yyyy',
    firstDayOfWeek: 'mo',
    sunHighlight: true,
    markCurrentDay: true,
    markCurrentMonth: true,
    markCurrentYear: true,
    monthLabels: { 
      1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
      7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' 
    },
    dayLabels: { su: 'Sun', mo: 'Mon', tu: 'Tue', we: 'Wed', th: 'Thu', fr: 'Fri', sa: 'Sat' },
    todayBtnTxt: 'Today',
    clearBtnTxt: 'Clear',
    closeBtnTxt: 'Close'
  };
}
```

### With Reactive Forms and Validation

```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ReactiveFormsModule } from '@angular/forms';
import { MyDatePickerComponent } from '@ng-vui/date-picker';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule, MyDatePickerComponent],
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <div class="space-y-4">
        <div class="form-group">
          <label class="block text-sm font-medium mb-1">Birth Date *</label>
          <my-date-picker 
            formControlName="birthDate"
            [options]="birthDateOptions"
            placeholder="Select your birth date"
            class="w-full">
          </my-date-picker>
          
          <div *ngIf="getBirthDateError()" class="mt-1 text-sm text-red-600">
            {{ getBirthDateError() }}
          </div>
        </div>

        <div class="form-group">
          <label class="block text-sm font-medium mb-1">Appointment Date *</label>
          <my-date-picker 
            formControlName="appointmentDate"
            [options]="appointmentOptions"
            placeholder="Select appointment date"
            class="w-full">
          </my-date-picker>
          
          <div *ngIf="getAppointmentError()" class="mt-1 text-sm text-red-600">
            {{ getAppointmentError() }}
          </div>
        </div>

        <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
          Schedule Appointment
        </button>
      </div>
    </form>
  `
})
export class ReactiveFormComponent implements OnInit {
  userForm!: FormGroup;
  isSubmitted = false;

  birthDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disableFuture: true, // Disable future dates for birth date
    minYear: 1900,
    maxYear: new Date().getFullYear(),
    showTodayBtn: false,
    markCurrentDay: true
  };

  appointmentOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true, // Disable past dates for appointments
    disableWeekends: true, // No weekend appointments
    showTodayBtn: true,
    markCurrentDay: true,
    todayBtnTxt: 'Today',
    clearBtnTxt: 'Clear'
  };

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.userForm = this.fb.group({
      birthDate: ['', [
        Validators.required,
        this.birthDateValidator.bind(this)
      ]],
      appointmentDate: ['', [
        Validators.required,
        this.appointmentDateValidator.bind(this)
      ]]
    });
  }

  // Birth date validation
  birthDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const today = new Date();
    const minDate = new Date('1900-01-01');
    
    if (selectedDate > today) {
      return { futureDate: true };
    }
    
    if (selectedDate < minDate) {
      return { tooOld: true };
    }
    
    // Check if person is under 18
    const age = today.getFullYear() - selectedDate.getFullYear();
    if (age < 18) {
      return { underAge: true };
    }
    
    return null;
  }

  // Appointment date validation
  appointmentDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const today = new Date();
    const dayOfWeek = selectedDate.getDay();
    
    // Check if it's in the past
    if (selectedDate < today) {
      return { pastDate: true };
    }
    
    // Check if it's a weekend
    if (dayOfWeek === 0 || dayOfWeek === 6) {
      return { weekendNotAllowed: true };
    }
    
    // Check if it's too far in the future (max 6 months)
    const maxDate = new Date(today);
    maxDate.setMonth(today.getMonth() + 6);
    
    if (selectedDate > maxDate) {
      return { tooFarInFuture: true };
    }
    
    return null;
  }

  getBirthDateError(): string | null {
    const control = this.userForm.get('birthDate');
    
    if (!control || (!control.touched && !this.isSubmitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Birth date is required';
    }
    
    if (control.errors?.['futureDate']) {
      return 'Birth date cannot be in the future';
    }
    
    if (control.errors?.['tooOld']) {
      return 'Please enter a valid birth date';
    }
    
    if (control.errors?.['underAge']) {
      return 'You must be at least 18 years old';
    }
    
    return null;
  }

  getAppointmentError(): string | null {
    const control = this.userForm.get('appointmentDate');
    
    if (!control || (!control.touched && !this.isSubmitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Appointment date is required';
    }
    
    if (control.errors?.['pastDate']) {
      return 'Appointment date cannot be in the past';
    }
    
    if (control.errors?.['weekendNotAllowed']) {
      return 'Weekend appointments are not available';
    }
    
    if (control.errors?.['tooFarInFuture']) {
      return 'Appointments can only be scheduled up to 6 months in advance';
    }
    
    return null;
  }

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

## 🚨 Error Handling & Validation

### Built-in Validation Support

The VUI date picker component integrates seamlessly with Angular's reactive forms and provides comprehensive validation capabilities for date inputs:

#### Required Validation

```typescript
@Component({
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label>Event Date *</label>
        <my-date-picker 
          formControlName="eventDate"
          [options]="eventOptions"
          placeholder="Select event date">
        </my-date-picker>
        
        <div *ngIf="getEventDateError()" class="mt-1 text-sm text-red-600">
          {{ getEventDateError() }}
        </div>
      </div>
    </form>
  `
})
export class RequiredValidationExample {
  form = this.fb.group({
    eventDate: ['', Validators.required] // Required date selection
  });
  
  eventOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    markCurrentDay: true
  };

  submitted = false;

  getEventDateError(): string | null {
    const control = this.form.get('eventDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Event date is required for registration';
    }
    
    return null;
  }

  onSubmit() {
    this.submitted = true;
    if (this.form.valid) {
      console.log('Event scheduled:', this.form.value);
    }
  }
}
```

#### Date Range Validation

```typescript
@Component({
  template: `
    <form [formGroup]="travelForm" (ngSubmit)="onSubmit()">
      <div class="grid grid-cols-2 gap-4">
        <div>
          <label>Departure Date *</label>
          <my-date-picker 
            formControlName="departureDate"
            [options]="departureDateOptions"
            placeholder="Select departure"
            (dateChanged)="onDepartureDateChange($event)">
          </my-date-picker>
          
          <div *ngIf="getDepartureDateError()" class="mt-1 text-sm text-red-600">
            {{ getDepartureDateError() }}
          </div>
        </div>

        <div>
          <label>Return Date *</label>
          <my-date-picker 
            formControlName="returnDate"
            [options]="returnDateOptions"
            placeholder="Select return">
          </my-date-picker>
          
          <div *ngIf="getReturnDateError()" class="mt-1 text-sm text-red-600">
            {{ getReturnDateError() }}
          </div>
        </div>
      </div>
    </form>
  `
})
export class DateRangeValidationExample implements OnInit {
  travelForm = this.fb.group({
    departureDate: ['', [Validators.required, this.departureDateValidator.bind(this)]],
    returnDate: ['', [Validators.required, this.returnDateValidator.bind(this)]]
  });

  departureDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true
  };

  returnDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true
  };

  submitted = false;

  ngOnInit() {
    // Watch for departure date changes to update return date restrictions
    this.travelForm.get('departureDate')?.valueChanges.subscribe(() => {
      this.updateReturnDateOptions();
      this.travelForm.get('returnDate')?.updateValueAndValidity();
    });
  }

  departureDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const today = new Date();
    
    // Must be at least tomorrow
    const tomorrow = new Date(today);
    tomorrow.setDate(today.getDate() + 1);
    
    if (selectedDate < tomorrow) {
      return { tooSoon: true };
    }
    
    // Cannot be more than 1 year in advance
    const maxDate = new Date(today);
    maxDate.setFullYear(today.getFullYear() + 1);
    
    if (selectedDate > maxDate) {
      return { tooFarAhead: true };
    }
    
    return null;
  }

  returnDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const returnDate = new Date(control.value.jsdate);
    const departureControl = this.travelForm?.get('departureDate');
    
    if (departureControl?.value) {
      const departureDate = new Date(departureControl.value.jsdate);
      
      // Return date must be after departure date
      if (returnDate <= departureDate) {
        return { beforeDeparture: true };
      }
      
      // Trip cannot exceed 30 days
      const diffTime = returnDate.getTime() - departureDate.getTime();
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
      
      if (diffDays > 30) {
        return { tripTooLong: true };
      }
    }
    
    return null;
  }

  onDepartureDateChange(event: any) {
    this.updateReturnDateOptions();
  }

  private updateReturnDateOptions() {
    const departureDate = this.travelForm.get('departureDate')?.value;
    
    if (departureDate) {
      this.returnDateOptions = {
        ...this.returnDateOptions,
        disableUntil: {
          year: departureDate.date.year,
          month: departureDate.date.month,
          day: departureDate.date.day
        }
      };
    }
  }

  getDepartureDateError(): string | null {
    const control = this.travelForm.get('departureDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Departure date is required';
    }
    
    if (control.errors?.['tooSoon']) {
      return 'Departure must be at least 1 day in advance';
    }
    
    if (control.errors?.['tooFarAhead']) {
      return 'Cannot book more than 1 year in advance';
    }
    
    return null;
  }

  getReturnDateError(): string | null {
    const control = this.travelForm.get('returnDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Return date is required';
    }
    
    if (control.errors?.['beforeDeparture']) {
      return 'Return date must be after departure date';
    }
    
    if (control.errors?.['tripTooLong']) {
      return 'Trip duration cannot exceed 30 days';
    }
    
    return null;
  }

  onSubmit() {
    this.submitted = true;
    if (this.travelForm.valid) {
      console.log('Travel booked:', this.travelForm.value);
    }
  }
}
```

#### Business Rules Validation

```typescript
@Component({
  template: `
    <form [formGroup]="businessForm">
      <div class="space-y-4">
        <div>
          <label>Meeting Date *</label>
          <my-date-picker 
            formControlName="meetingDate"
            [options]="businessDateOptions"
            placeholder="Select meeting date">
          </my-date-picker>
          
          <div *ngIf="getMeetingDateError()" class="mt-1 text-sm text-red-600">
            {{ getMeetingDateError() }}
          </div>
        </div>

        <div>
          <label>Project Deadline *</label>
          <my-date-picker 
            formControlName="deadline"
            [options]="deadlineOptions"
            placeholder="Select deadline">
          </my-date-picker>
          
          <div *ngIf="getDeadlineError()" class="mt-1 text-sm text-red-600">
            {{ getDeadlineError() }}
          </div>
        </div>
      </div>
    </form>
  `
})
export class BusinessRulesValidationExample {
  businessForm = this.fb.group({
    meetingDate: ['', [Validators.required, this.businessDateValidator.bind(this)]],
    deadline: ['', [Validators.required, this.deadlineValidator.bind(this)]]
  });

  // Company holidays
  private holidays: IMyDate[] = [
    { year: 2024, month: 1, day: 1 },   // New Year
    { year: 2024, month: 7, day: 4 },   // Independence Day  
    { year: 2024, month: 11, day: 28 }, // Thanksgiving
    { year: 2024, month: 12, day: 25 }  // Christmas
  ];

  businessDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    disableWeekends: true,
    disableDates: this.holidays,
    markCurrentDay: true
  };

  deadlineOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true,
    showTodayBtn: false
  };

  submitted = false;

  businessDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const dayOfWeek = selectedDate.getDay();
    
    // Check for weekends (already disabled in UI, but validate)
    if (dayOfWeek === 0 || dayOfWeek === 6) {
      return { weekendNotAllowed: true };
    }
    
    // Check for holidays
    const isHoliday = this.holidays.some(holiday => 
      holiday.year === control.value.date.year &&
      holiday.month === control.value.date.month &&
      holiday.day === control.value.date.day
    );
    
    if (isHoliday) {
      return { holidayNotAllowed: true };
    }
    
    // Must be at least 2 business days in advance
    const today = new Date();
    const businessDaysAhead = this.calculateBusinessDays(today, selectedDate);
    
    if (businessDaysAhead < 2) {
      return { insufficientAdvanceNotice: true };
    }
    
    return null;
  }

  deadlineValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const deadline = new Date(control.value.jsdate);
    const today = new Date();
    
    // Minimum 1 week for project completion
    const minDeadline = new Date(today);
    minDeadline.setDate(today.getDate() + 7);
    
    if (deadline < minDeadline) {
      return { deadlineTooSoon: true };
    }
    
    // Maximum 1 year for project planning
    const maxDeadline = new Date(today);
    maxDeadline.setFullYear(today.getFullYear() + 1);
    
    if (deadline > maxDeadline) {
      return { deadlineTooFar: true };
    }
    
    return null;
  }

  private calculateBusinessDays(startDate: Date, endDate: Date): number {
    let count = 0;
    const current = new Date(startDate);
    
    while (current < endDate) {
      const dayOfWeek = current.getDay();
      if (dayOfWeek !== 0 && dayOfWeek !== 6) { // Not weekend
        // Check if it's not a holiday
        const isHoliday = this.holidays.some(holiday => 
          holiday.year === current.getFullYear() &&
          holiday.month === current.getMonth() + 1 &&
          holiday.day === current.getDate()
        );
        
        if (!isHoliday) {
          count++;
        }
      }
      current.setDate(current.getDate() + 1);
    }
    
    return count;
  }

  getMeetingDateError(): string | null {
    const control = this.businessForm.get('meetingDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Meeting date is required';
    }
    
    if (control.errors?.['weekendNotAllowed']) {
      return 'Meetings cannot be scheduled on weekends';
    }
    
    if (control.errors?.['holidayNotAllowed']) {
      return 'Meetings cannot be scheduled on company holidays';
    }
    
    if (control.errors?.['insufficientAdvanceNotice']) {
      return 'Meetings must be scheduled at least 2 business days in advance';
    }
    
    return null;
  }

  getDeadlineError(): string | null {
    const control = this.businessForm.get('deadline');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Project deadline is required';
    }
    
    if (control.errors?.['deadlineTooSoon']) {
      return 'Project deadline must be at least 1 week from now';
    }
    
    if (control.errors?.['deadlineTooFar']) {
      return 'Project deadline cannot be more than 1 year away';
    }
    
    return null;
  }
}
```

#### Age and Legal Date Validation

```typescript
@Component({
  template: `
    <form [formGroup]="registrationForm">
      <div class="space-y-4">
        <div>
          <label>Birth Date *</label>
          <my-date-picker 
            formControlName="birthDate"
            [options]="birthDateOptions"
            placeholder="Enter your birth date">
          </my-date-picker>
          
          <div *ngIf="getBirthDateError()" class="mt-1 text-sm text-red-600">
            {{ getBirthDateError() }}
          </div>
          
          <div *ngIf="getAge() > 0" class="mt-1 text-sm text-gray-600">
            Age: {{ getAge() }} years old
          </div>
        </div>

        <div>
          <label>License Expiry Date *</label>
          <my-date-picker 
            formControlName="licenseExpiry"
            [options]="licenseOptions"
            placeholder="License expiry date">
          </my-date-picker>
          
          <div *ngIf="getLicenseError()" class="mt-1 text-sm text-red-600">
            {{ getLicenseError() }}
          </div>
        </div>
      </div>
    </form>
  `
})
export class AgeLegalValidationExample {
  registrationForm = this.fb.group({
    birthDate: ['', [Validators.required, this.birthDateValidator.bind(this)]],
    licenseExpiry: ['', [Validators.required, this.licenseExpiryValidator.bind(this)]]
  });

  birthDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disableFuture: true,
    maxYear: new Date().getFullYear(),
    minYear: 1920,
    markCurrentDay: true
  };

  licenseOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true
  };

  submitted = false;

  birthDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const birthDate = new Date(control.value.jsdate);
    const today = new Date();
    
    // Future date check
    if (birthDate > today) {
      return { futureDate: true };
    }
    
    // Age calculation
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();
    
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
      age--;
    }
    
    // Minimum age validation (13 for social media, 18 for adult services, etc.)
    if (age < 13) {
      return { tooYoung: true };
    }
    
    // Maximum reasonable age
    if (age > 120) {
      return { unrealisticAge: true };
    }
    
    // Driving age validation (if this is for a driving-related service)
    if (age < 16) {
      return { belowDrivingAge: true };
    }
    
    return null;
  }

  licenseExpiryValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const expiryDate = new Date(control.value.jsdate);
    const today = new Date();
    
    // Must be valid (not expired)
    if (expiryDate <= today) {
      return { licenseExpired: true };
    }
    
    // Should have at least 30 days remaining
    const daysUntilExpiry = Math.ceil((expiryDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
    
    if (daysUntilExpiry < 30) {
      return { expiringSoon: true };
    }
    
    // Reasonable maximum (licenses don't last more than 10 years typically)
    const maxDate = new Date(today);
    maxDate.setFullYear(today.getFullYear() + 10);
    
    if (expiryDate > maxDate) {
      return { unrealisticExpiry: true };
    }
    
    return null;
  }

  getAge(): number {
    const birthDateControl = this.registrationForm.get('birthDate');
    
    if (!birthDateControl?.value) return 0;
    
    const birthDate = new Date(birthDateControl.value.jsdate);
    const today = new Date();
    
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();
    
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
      age--;
    }
    
    return age;
  }

  getBirthDateError(): string | null {
    const control = this.registrationForm.get('birthDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Birth date is required';
    }
    
    if (control.errors?.['futureDate']) {
      return 'Birth date cannot be in the future';
    }
    
    if (control.errors?.['tooYoung']) {
      return 'You must be at least 13 years old to register';
    }
    
    if (control.errors?.['belowDrivingAge']) {
      return 'You must be at least 16 years old for this service';
    }
    
    if (control.errors?.['unrealisticAge']) {
      return 'Please enter a valid birth date';
    }
    
    return null;
  }

  getLicenseError(): string | null {
    const control = this.registrationForm.get('licenseExpiry');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'License expiry date is required';
    }
    
    if (control.errors?.['licenseExpired']) {
      return 'License has already expired';
    }
    
    if (control.errors?.['expiringSoon']) {
      return 'License expires too soon (minimum 30 days required)';
    }
    
    if (control.errors?.['unrealisticExpiry']) {
      return 'Please enter a valid license expiry date';
    }
    
    return null;
  }
}
```

### Common Validation Patterns

#### Cross-Field Date Validation

```typescript
// Validator that compares two date fields
export function dateRangeValidator(startFieldName: string, endFieldName: string) {
  return (formGroup: AbstractControl): ValidationErrors | null => {
    const startControl = formGroup.get(startFieldName);
    const endControl = formGroup.get(endFieldName);
    
    if (!startControl?.value || !endControl?.value) {
      return null;
    }
    
    const startDate = new Date(startControl.value.jsdate);
    const endDate = new Date(endControl.value.jsdate);
    
    if (startDate >= endDate) {
      return { dateRangeInvalid: { startField: startFieldName, endField: endFieldName } };
    }
    
    return null;
  };
}

// Usage in form group
this.form = this.fb.group({
  startDate: ['', Validators.required],
  endDate: ['', Validators.required]
}, {
  validators: [dateRangeValidator('startDate', 'endDate')]
});
```

#### Dynamic Date Restrictions

```typescript
// Method to dynamically update date picker options
updateDateRestrictions(baseDate: Date, component: 'start' | 'end') {
  if (component === 'end') {
    this.endDateOptions = {
      ...this.endDateOptions,
      disableUntil: {
        year: baseDate.getFullYear(),
        month: baseDate.getMonth() + 1,
        day: baseDate.getDate() - 1
      }
    };
  }
}
```

## 🔧 API Reference

### MyDatePickerComponent

#### Inputs

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `options` | `IMyDpOptions` | `{}` | Configuration options |
| `locale` | `string` | `'en'` | Locale for date formatting |
| `defaultMonth` | `string` | - | Default month to show (yyyy-mm) |
| `placeholder` | `string` | - | Input placeholder text |
| `selector` | `string` | - | CSS selector for input styling |

#### Outputs

| Event | Type | Description |
|-------|------|-------------|
| `dateChanged` | `EventEmitter<IMyDateModel>` | Date selection changed |
| `inputFieldChanged` | `EventEmitter<IMyInputFieldChanged>` | Input field value changed |
| `calendarViewChanged` | `EventEmitter<IMyCalendarViewChanged>` | Calendar view changed |
| `calendarToggle` | `EventEmitter<number>` | Calendar opened/closed |
| `inputFocusBlur` | `EventEmitter<IMyInputFocusBlur>` | Input focus/blur events |

### IMyDpOptions Interface

```typescript
interface IMyDpOptions {
  // Date format and display
  dateFormat?: string;              // Date format (e.g., 'dd.mm.yyyy', 'yyyy-mm-dd')
  monthLabels?: IMyMonthLabels;     // Month labels
  dayLabels?: IMyDayLabels;         // Day labels
  dayLabelsFull?: IMyDayLabels;     // Full day labels
  
  // Calendar behavior
  firstDayOfWeek?: string;          // First day of week ('su', 'mo', etc.)
  satHighlight?: boolean;           // Highlight Saturdays
  sunHighlight?: boolean;           // Highlight Sundays
  highlightDates?: IMyDate[];       // Dates to highlight
  markCurrentDay?: boolean;         // Mark current day
  markCurrentMonth?: boolean;       // Mark current month
  markCurrentYear?: boolean;        // Mark current year
  
  // Date restrictions
  disableUntil?: IMyDate;          // Disable dates until
  disableSince?: IMyDate;          // Disable dates since
  disableDates?: IMyDate[];        // Specific dates to disable
  disableDateRanges?: IMyDateRange[]; // Date ranges to disable
  disableWeekends?: boolean;        // Disable weekends
  disableFuture?: boolean;         // Disable future dates
  disablePast?: boolean;           // Disable past dates
  
  // Year and month limits
  minYear?: number;                // Minimum selectable year
  maxYear?: number;                // Maximum selectable year
  
  // UI elements
  showTodayBtn?: boolean;          // Show today button
  showClearBtn?: boolean;          // Show clear button
  showCloseBtn?: boolean;          // Show close button
  todayBtnTxt?: string;            // Today button text
  clearBtnTxt?: string;            // Clear button text
  closeBtnTxt?: string;            // Close button text
  
  // Calendar size and position
  width?: string;                  // Calendar width
  height?: string;                 // Calendar height
  selectorHeight?: string;         // Input height
  selectorWidth?: string;          // Input width
  
  // Animation and timing
  openSelectorTopOfInput?: boolean; // Open calendar above input
  showSelectorArrow?: boolean;     // Show calendar arrow
  alignSelectorRight?: boolean;    // Align calendar to right
  
  // Inline mode
  inline?: boolean;                // Display as inline calendar
  showDateFormatPlaceholder?: boolean; // Show format placeholder
  
  // Validation
  allowDeselectDate?: boolean;     // Allow deselecting current date
  
  // Styling
  stylesData?: IMyStyles;          // Custom styles
  divHostElement?: IMyDivHostElement; // Host element styling
}
```

### Date Model Interface

```typescript
interface IMyDateModel {
  date: IMyDate;          // Selected date object
  jsdate?: Date;          // JavaScript Date object
  formatted: string;      // Formatted date string
  epoc: number;           // Unix timestamp
}

interface IMyDate {
  year: number;           // Year (e.g., 2024)
  month: number;          // Month (1-12)
  day: number;            // Day (1-31)
}
```

## 🎯 Advanced Examples

### Date Range Selection

```typescript
@Component({
  template: `
    <div class="form-row">
      <div class="col-md-6">
        <label>Start Date</label>
        <my-date-picker 
          [(ngModel)]="startDate"
          [options]="startDateOptions"
          placeholder="Select start date">
        </my-date-picker>
      </div>
      
      <div class="col-md-6">
        <label>End Date</label>
        <my-date-picker 
          [(ngModel)]="endDate"
          [options]="endDateOptions"
          placeholder="Select end date">
        </my-date-picker>
      </div>
    </div>
    
    <div class="mt-3" *ngIf="startDate && endDate">
      Duration: {{ calculateDuration() }} days
    </div>
  `
})
export class DateRangeComponent {
  startDate: any = null;
  endDate: any = null;
  
  startDateOptions: IMyDpOptions = {
    dateFormat: 'yyyy-mm-dd',
    disablePast: true,
    showTodayBtn: true
  };
  
  endDateOptions: IMyDpOptions = {
    dateFormat: 'yyyy-mm-dd',
    disablePast: true,
    showTodayBtn: true
  };
  
  ngOnInit() {
    // Update end date options when start date changes
    this.updateEndDateRestrictions();
  }
  
  onStartDateChanged() {
    this.updateEndDateRestrictions();
    // Clear end date if it's before start date
    if (this.endDate && this.startDate && 
        new Date(this.endDate.jsdate) < new Date(this.startDate.jsdate)) {
      this.endDate = null;
    }
  }
  
  private updateEndDateRestrictions() {
    if (this.startDate) {
      this.endDateOptions = {
        ...this.endDateOptions,
        disableUntil: {
          year: this.startDate.date.year,
          month: this.startDate.date.month,
          day: this.startDate.date.day - 1
        }
      };
    }
  }
  
  calculateDuration(): number {
    if (!this.startDate || !this.endDate) return 0;
    
    const start = new Date(this.startDate.jsdate);
    const end = new Date(this.endDate.jsdate);
    const diffTime = Math.abs(end.getTime() - start.getTime());
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  }
}
```

### Custom Styling and Themes

```typescript
@Component({
  template: `
    <my-date-picker 
      [options]="customOptions"
      [(ngModel)]="selectedDate"
      placeholder="Select date">
    </my-date-picker>
  `
})
export class CustomStyledComponent {
  selectedDate: any = null;
  
  customOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    firstDayOfWeek: 'mo',
    sunHighlight: true,
    markCurrentDay: true,
    stylesData: {
      selector: 'dp1',
      styles: `
        .dp1 .myDpSelector {
          border: 2px solid #007bff;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        .dp1 .myDpSelectorArrow:after {
          border-bottom-color: #007bff;
        }
        
        .dp1 .myDpDayCurrMonth:hover,
        .dp1 .myDpMonthLabel:hover,
        .dp1 .myDpYearLabel:hover {
          background-color: #e3f2fd;
          color: #1976d2;
        }
        
        .dp1 .myDpSelectedDay {
          background-color: #007bff;
          color: white;
          border-radius: 50%;
        }
        
        .dp1 .myDpHeaderBtn {
          background-color: #007bff;
          color: white;
          border-radius: 4px;
        }
        
        .dp1 .myDpFooterBtn {
          background-color: #6c757d;
          color: white;
          border-radius: 4px;
          margin: 2px;
        }
        
        .dp1 .myDpFooterBtn:hover {
          background-color: #495057;
        }
      `
    }
  };
}
```

### Localization Example

```typescript
@Component({
  template: `
    <div class="form-group">
      <label>Language</label>
      <select [(ngModel)]="currentLocale" (change)="changeLocale()" class="form-control">
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
        <option value="de">Deutsch</option>
      </select>
    </div>
    
    <div class="form-group">
      <label>{{ getLabel('selectDate') }}</label>
      <my-date-picker 
        [options]="localizedOptions"
        [(ngModel)]="selectedDate"
        [placeholder]="getLabel('placeholder')">
      </my-date-picker>
    </div>
  `
})
export class LocalizationComponent {
  selectedDate: any = null;
  currentLocale = 'en';
  localizedOptions: IMyDpOptions = {};
  
  private locales = {
    en: {
      monthLabels: { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
                    7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' },
      dayLabels: { su: 'Sun', mo: 'Mon', tu: 'Tue', we: 'Wed', th: 'Thu', fr: 'Fri', sa: 'Sat' },
      todayBtnTxt: 'Today',
      clearBtnTxt: 'Clear',
      closeBtnTxt: 'Close',
      selectDate: 'Select Date',
      placeholder: 'Click to select date'
    },
    es: {
      monthLabels: { 1: 'Ene', 2: 'Feb', 3: 'Mar', 4: 'Abr', 5: 'May', 6: 'Jun',
                    7: 'Jul', 8: 'Ago', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dic' },
      dayLabels: { su: 'Dom', mo: 'Lun', tu: 'Mar', we: 'Mié', th: 'Jue', fr: 'Vie', sa: 'Sáb' },
      todayBtnTxt: 'Hoy',
      clearBtnTxt: 'Limpiar',
      closeBtnTxt: 'Cerrar',
      selectDate: 'Seleccionar Fecha',
      placeholder: 'Haz clic para seleccionar fecha'
    },
    // Add more locales...
  };
  
  ngOnInit() {
    this.changeLocale();
  }
  
  changeLocale() {
    const locale = this.locales[this.currentLocale as keyof typeof this.locales];
    
    this.localizedOptions = {
      dateFormat: 'dd/mm/yyyy',
      firstDayOfWeek: 'mo',
      markCurrentDay: true,
      monthLabels: locale.monthLabels,
      dayLabels: locale.dayLabels,
      todayBtnTxt: locale.todayBtnTxt,
      clearBtnTxt: locale.clearBtnTxt,
      closeBtnTxt: locale.closeBtnTxt
    };
  }
  
  getLabel(key: string): string {
    const locale = this.locales[this.currentLocale as keyof typeof this.locales];
    return locale[key as keyof typeof locale];
  }
}
```

### Disabled Dates and Validation

```typescript
@Component({
  template: `
    <form [formGroup]="form">
      <div class="form-group">
        <label>Appointment Date</label>
        <my-date-picker 
          formControlName="appointmentDate"
          [options]="appointmentOptions"
          placeholder="Select appointment date">
        </my-date-picker>
        
        <div *ngIf="form.get('appointmentDate')?.invalid && form.get('appointmentDate')?.touched" 
             class="text-danger">
          <div *ngIf="form.get('appointmentDate')?.hasError('required')">
            Appointment date is required
          </div>
          <div *ngIf="form.get('appointmentDate')?.hasError('weekendNotAllowed')">
            Weekend appointments are not available
          </div>
          <div *ngIf="form.get('appointmentDate')?.hasError('holidayNotAllowed')">
            Holiday appointments are not available
          </div>
        </div>
      </div>
      
      <small class="form-text text-muted">
        * Appointments are available Monday-Friday, excluding holidays
      </small>
    </form>
  `
})
export class DisabledDatesComponent {
  form = this.fb.group({
    appointmentDate: ['', [Validators.required, this.appointmentDateValidator.bind(this)]]
  });

  // Define holidays
  private holidays: IMyDate[] = [
    { year: 2024, month: 1, day: 1 },   // New Year
    { year: 2024, month: 7, day: 4 },   // Independence Day
    { year: 2024, month: 12, day: 25 }  // Christmas
  ];

  appointmentOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    firstDayOfWeek: 'mo',
    disablePast: true,
    disableWeekends: true,
    disableDates: this.holidays,
    markCurrentDay: true,
    showTodayBtn: false,
    sunHighlight: false,
    satHighlight: false
  };

  constructor(private fb: FormBuilder) {}

  appointmentDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = control.value;
    const jsDate = new Date(selectedDate.jsdate);
    
    // Check if it's a weekend
    if (jsDate.getDay() === 0 || jsDate.getDay() === 6) {
      return { weekendNotAllowed: true };
    }
    
    // Check if it's a holiday
    const isHoliday = this.holidays.some(holiday => 
      holiday.year === selectedDate.date.year &&
      holiday.month === selectedDate.date.month &&
      holiday.day === selectedDate.date.day
    );
    
    if (isHoliday) {
      return { holidayNotAllowed: true };
    }
    
    return null;
  }
}
```

### Inline Calendar

```typescript
@Component({
  template: `
    <div class="row">
      <div class="col-md-6">
        <h5>Inline Calendar</h5>
        <my-date-picker 
          [options]="inlineOptions"
          [(ngModel)]="selectedDate">
        </my-date-picker>
      </div>
      
      <div class="col-md-6">
        <h5>Selected Date Information</h5>
        <div *ngIf="selectedDate" class="card">
          <div class="card-body">
            <p><strong>Date:</strong> {{ selectedDate.formatted }}</p>
            <p><strong>Day of Week:</strong> {{ getDayOfWeek() }}</p>
            <p><strong>Week Number:</strong> {{ getWeekNumber() }}</p>
            <p><strong>Days from Today:</strong> {{ getDaysFromToday() }}</p>
          </div>
        </div>
        
        <div *ngIf="!selectedDate" class="alert alert-info">
          Please select a date from the calendar
        </div>
      </div>
    </div>
  `
})
export class InlineCalendarComponent {
  selectedDate: any = null;
  
  inlineOptions: IMyDpOptions = {
    dateFormat: 'dd.mm.yyyy',
    firstDayOfWeek: 'mo',
    inline: true,
    showTodayBtn: true,
    showClearBtn: true,
    markCurrentDay: true,
    sunHighlight: true,
    width: '100%'
  };
  
  getDayOfWeek(): string {
    if (!this.selectedDate) return '';
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    return days[new Date(this.selectedDate.jsdate).getDay()];
  }
  
  getWeekNumber(): number {
    if (!this.selectedDate) return 0;
    const date = new Date(this.selectedDate.jsdate);
    const firstDay = new Date(date.getFullYear(), 0, 1);
    return Math.ceil(((date.getTime() - firstDay.getTime()) / 86400000 + firstDay.getDay() + 1) / 7);
  }
  
  getDaysFromToday(): number {
    if (!this.selectedDate) return 0;
    const today = new Date();
    const selected = new Date(this.selectedDate.jsdate);
    const diffTime = selected.getTime() - today.getTime();
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  }
}
```

## 🎨 Styling & Theming

### CSS Classes

```scss
// Custom date picker styles
.my-date-picker {
  .myDpSelector {
    border: 2px solid #007bff;
    border-radius: 8px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    
    &:focus {
      outline: none;
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
    }
  }
  
  .myDpCalendar {
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    
    .myDpHeader {
      background: linear-gradient(135deg, #007bff, #0056b3);
      color: white;
      
      .myDpHeaderBtn {
        color: white;
        background: transparent;
        
        &:hover {
          background: rgba(255, 255, 255, 0.2);
        }
      }
    }
    
    .myDpWeekDays {
      background: #f8f9fa;
      font-weight: 600;
      text-transform: uppercase;
      font-size: 0.75rem;
      color: #6c757d;
    }
    
    .myDpDays {
      .myDpDayCurrMonth {
        transition: all 0.2s ease;
        
        &:hover {
          background: #e3f2fd;
          color: #1976d2;
          transform: scale(1.1);
        }
      }
      
      .myDpSelectedDay {
        background: #007bff !important;
        color: white !important;
        border-radius: 50%;
        font-weight: bold;
      }
      
      .myDpCurrDay {
        background: #ffc107;
        color: #212529;
        border-radius: 50%;
        font-weight: bold;
      }
      
      .myDpSunHighlight {
        color: #dc3545;
      }
      
      .myDpSatHighlight {
        color: #fd7e14;
      }
    }
    
    .myDpFooter {
      background: #f8f9fa;
      border-top: 1px solid #dee2e6;
      
      .myDpFooterBtn {
        background: #6c757d;
        color: white;
        border-radius: 4px;
        transition: background 0.2s ease;
        
        &:hover {
          background: #495057;
        }
        
        &.today-btn {
          background: #28a745;
          
          &:hover {
            background: #1e7e34;
          }
        }
        
        &.clear-btn {
          background: #dc3545;
          
          &:hover {
            background: #c82333;
          }
        }
      }
    }
  }
}
```

### Dark Theme

```scss
.dark-theme .my-date-picker {
  .myDpSelector {
    background: #343a40;
    border-color: #6c757d;
    color: #ffffff;
  }
  
  .myDpCalendar {
    background: #495057;
    color: #ffffff;
    
    .myDpHeader {
      background: linear-gradient(135deg, #6f42c1, #563d7c);
    }
    
    .myDpWeekDays {
      background: #6c757d;
      color: #adb5bd;
    }
    
    .myDpDays {
      .myDpDayCurrMonth {
        &:hover {
          background: #6f42c1;
          color: white;
        }
      }
      
      .myDpSelectedDay {
        background: #6f42c1 !important;
      }
    }
    
    .myDpFooter {
      background: #6c757d;
      border-top-color: #495057;
    }
  }
}
```

## 🧪 Testing

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyDatePickerModule } from '@ng-vui/date-picker';
import { FormsModule } from '@angular/forms';

describe('MyDatePickerComponent', () => {
  let component: TestComponent;
  let fixture: ComponentFixture<TestComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TestComponent],
      imports: [MyDatePickerModule, FormsModule]
    });
    
    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
  });

  it('should render date picker', () => {
    fixture.detectChanges();
    
    const datePicker = fixture.nativeElement.querySelector('my-date-picker');
    expect(datePicker).toBeTruthy();
  });

  it('should open calendar on input click', () => {
    fixture.detectChanges();
    
    const input = fixture.nativeElement.querySelector('.myDpSelector');
    input.click();
    
    fixture.detectChanges();
    
    const calendar = fixture.nativeElement.querySelector('.myDpCalendar');
    expect(calendar).toBeTruthy();
  });

  it('should select date', () => {
    spyOn(component, 'onDateChanged');
    
    fixture.detectChanges();
    
    // Open calendar and click on a date
    const input = fixture.nativeElement.querySelector('.myDpSelector');
    input.click();
    fixture.detectChanges();
    
    const dateCell = fixture.nativeElement.querySelector('.myDpDayCurrMonth');
    dateCell.click();
    
    expect(component.onDateChanged).toHaveBeenCalled();
  });
});

@Component({
  template: `
    <my-date-picker 
      [options]="options" 
      [(ngModel)]="selectedDate"
      (dateChanged)="onDateChanged($event)">
    </my-date-picker>
  `
})
class TestComponent {
  selectedDate: any = null;
  options = { dateFormat: 'dd.mm.yyyy' };
  
  onDateChanged(event: any) {
    console.log('Date changed:', event);
  }
}
```

## 🚀 Performance Tips

1. **Use OnPush change detection** for better performance:
```typescript
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
```

2. **Limit date ranges** for better calendar rendering:
```typescript
options = {
  minYear: 2020,
  maxYear: 2030
};
```

3. **Use trackBy functions** in templates with date arrays

## 🤝 Contributing

1. Fork the repository
2. Create your feature branch: `git checkout -b feature/my-feature`
3. Commit your changes: `git commit -am 'Add my feature'`
4. Push to the branch: `git push origin feature/my-feature`
5. Submit a pull request

## 📄 License

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

## 🔗 Related Packages

- `ng-vui-select-input` - Single select dropdown
- `ng-vui-grid` - Data grid component
- `ng-vui-multi-select` - Multi-select dropdown component
- `ng-vui-text-input` - Text input component
- `ng-vui-textarea` - Textarea component
- `ng-vui-auto-complete` - Auto complete component