# ChipInput

## Description

A powerful input field component that allows users to enter multiple values as chips or tags. Features automatic input filtering, backspace deletion, async operations with optimistic UI updates, and support for both simple strings and complex objects. Perfect for managing tags, categories, email addresses, team assignments, or any collection of discrete values.

## Key Features

- **📝 Input Filtering**: Real-time filtering of dropdown options as you type
- **⌫ Backspace Deletion**: Press backspace when input is empty to delete last chip
- **⚡ Async Operations**: Support for async add/delete operations with optimistic UI updates
- **🔄 Error Handling**: Automatic reversion of failed operations
- **📊 Type Flexibility**: Works with both `string[]` and `OptionLike[]` arrays
- **🎯 Form Integration**: Full compatibility with form validation and controlled components
- **♿ Accessibility**: Keyboard navigation and screen reader support

## Aliases

- ChipInput
- TagInput
- TokenInput
- MultiValueInput
- ChipField

## Props Breakdown

**Generic Type:** `ChipInputProps<T = string[] | OptionLike[]>`

| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `value` | `T` (string[] or OptionLike[]) | - | No | Current array of selected values |
| `onValueChange` | `(value: T) => void` | - | No | Callback fired when selected values change |
| `inputValue` | `string` | - | No | Current input field value for controlled input |
| `onInputChange` | `(value: string) => void` | - | No | Callback fired when input value changes |
| `options` | `string[]` or `OptionLike[]` | - | No | Available options (automatically filtered by input) |
| `placeholder` | `string` | - | No | Placeholder text for the input field |
| `className` | `string` | - | No | Additional CSS class names |
| `onAddItem` | `(value: string) => Promise<OptionLike>` or `OptionLike` | - | No | Async/sync callback for creating new items |
| `onDeleteItem` | `(key: OptionLikeKey) => Promise<void>` | - | No | Async callback for deleting items |
| `onAddNewOption` | `(value: string) => OptionLike` or `void` | - | No | **Deprecated:** Use `onAddItem` instead |

## Examples

### Basic Usage with Filtering
```tsx
import { ChipInput } from '@delightui/components';

function BasicExample() {
  const [tags, setTags] = useState(['react', 'javascript']);
  const availableTags = ['react', 'javascript', 'typescript', 'vue', 'angular', 'svelte'];

  return (
    <ChipInput
      value={tags}
      onValueChange={setTags}
      options={availableTags}
      placeholder="Type to filter tags..."
    />
  );
}
```

### OptionLike Objects
```tsx
function OptionLikeExample() {
  const [selectedFruits, setSelectedFruits] = useState([]);
  
  const fruitOptions = [
    { key: 1, label: 'Apple 🍎' },
    { key: 2, label: 'Banana 🍌' },
    { key: 3, label: 'Cherry 🍒' },
    { key: 4, label: 'Date 🌴' },
  ];

  return (
    <ChipInput
      value={selectedFruits}
      onValueChange={setSelectedFruits}
      options={fruitOptions}
      placeholder="Select fruits..."
    />
  );
}
```

### Async Operations with Optimistic UI
```tsx
function AsyncExample() {
  const [items, setItems] = useState([]);
  const [options, setOptions] = useState([
    { key: 1, label: 'Existing Item 1' },
    { key: 2, label: 'Existing Item 2' },
  ]);

  const handleAddItem = async (value) => {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // Simulate random error for demo
    if (value === 'error') {
      throw new Error('Simulated error');
    }
    
    const newItem = {
      key: Date.now(),
      label: `${value} ✨`
    };
    
    setOptions(prev => [...prev, newItem]);
    return newItem;
  };

  const handleDeleteItem = async (key) => {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 500));
    setOptions(prev => prev.filter(opt => opt.key !== key));
  };

  return (
    <ChipInput
      value={items}
      onValueChange={setItems}
      options={options}
      onAddItem={handleAddItem}
      onDeleteItem={handleDeleteItem}
      placeholder='Try typing "error" to see error handling'
    />
  );
}
```

### Keyboard Navigation Demo
```tsx
function KeyboardNavigationExample() {
  const [tags, setTags] = useState(['frontend', 'javascript']);
  const options = [
    'frontend', 'backend', 'javascript', 'typescript', 'react', 
    'vue', 'angular', 'node', 'python', 'java'
  ];

  return (
    <div>
      <ChipInput
        value={tags}
        onValueChange={setTags}
        options={options}
        placeholder="Try: type to filter, backspace to delete, enter to add"
      />
      <div style={{ marginTop: '8px', fontSize: '12px', color: '#666' }}>
        <strong>Keyboard shortcuts:</strong>
        <ul style={{ margin: 0, paddingLeft: '16px' }}>
          <li>Type to filter available options</li>
          <li>Press <kbd>Backspace</kbd> when input is empty to delete last chip</li>
          <li>Press <kbd>Enter</kbd> to add typed text as new chip</li>
        </ul>
      </div>
    </div>
  );
}
```

### API Integration Example
```tsx
function APIIntegrationExample() {
  const [selectedUsers, setSelectedUsers] = useState([]);
  const [availableUsers, setAvailableUsers] = useState([
    { key: 1, label: 'John Doe' },
    { key: 2, label: 'Jane Smith' },
  ]);

  const createUser = async (name) => {
    // Call your API
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name })
    });
    
    if (!response.ok) {
      throw new Error('Failed to create user');
    }
    
    const newUser = await response.json();
    setAvailableUsers(prev => [...prev, newUser]);
    return newUser;
  };

  const deleteUser = async (userId) => {
    await fetch(`/api/users/${userId}`, { method: 'DELETE' });
    setAvailableUsers(prev => prev.filter(u => u.key !== userId));
  };

  return (
    <ChipInput
      value={selectedUsers}
      onValueChange={setSelectedUsers}
      options={availableUsers}
      onAddItem={createUser}
      onDeleteItem={deleteUser}
      placeholder="Type to create or select users..."
    />
  );
}
```

### Migration from Legacy onAddNewOption
```tsx
// ❌ Old way (still works but deprecated)
function LegacyExample() {
  const [tags, setTags] = useState([]);
  
  return (
    <ChipInput
      value={tags}
      onValueChange={setTags}
      onAddNewOption={(value) => {
        console.log('Added:', value);
      }}
    />
  );
}

// ✅ New way (recommended)
function ModernExample() {
  const [tags, setTags] = useState([]);
  
  return (
    <ChipInput
      value={tags}
      onValueChange={setTags}
      onAddItem={(value) => ({
        key: value,
        label: value
      })}
    />
  );
}
```

### Email Recipients
```tsx
function EmailRecipientsExample() {
  const [recipients, setRecipients] = useState(['john@example.com']);
  const [inputValue, setInputValue] = useState('');

  const validateEmail = (email) => {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  };

  const handleAddRecipient = (email) => {
    if (validateEmail(email) && !recipients.includes(email)) {
      setRecipients([...recipients, email]);
      setInputValue('');
    }
  };

  return (
    <div className="email-recipients">
      <Text type="Heading6">Email Recipients</Text>
      <ChipInput
        value={recipients}
        onValueChange={setRecipients}
        inputValue={inputValue}
        onInputChange={setInputValue}
        placeholder="Enter email addresses..."
        onAddNewOption={handleAddRecipient}
        className="email-chip-input"
      />
      <Text type="BodySmall">
        {recipients.length} recipient{recipients.length !== 1 ? 's' : ''} added
      </Text>
    </div>
  );
}
```

### Skills Tags with Autocomplete
```tsx
function SkillsTagsExample() {
  const [skills, setSkills] = useState(['React', 'TypeScript']);
  const [inputValue, setInputValue] = useState('');
  
  const availableSkills = [
    'JavaScript', 'TypeScript', 'React', 'Vue.js', 'Angular',
    'Node.js', 'Python', 'Java', 'C++', 'HTML', 'CSS',
    'GraphQL', 'REST APIs', 'MongoDB', 'PostgreSQL'
  ];

  const handleAddSkill = (skill) => {
    if (!skills.includes(skill)) {
      setSkills([...skills, skill]);
    }
    setInputValue('');
  };

  return (
    <div className="skills-section">
      <Text type="Heading6">Technical Skills</Text>
      <ChipInput
        value={skills}
        onValueChange={setSkills}
        inputValue={inputValue}
        onInputChange={setInputValue}
        options={availableSkills}
        placeholder="Add your skills..."
        onAddNewOption={handleAddSkill}
        className="skills-chip-input"
      />
      <Text type="BodySmall">
        {skills.length} skill{skills.length !== 1 ? 's' : ''} selected
      </Text>
    </div>
  );
}
```

### Product Categories
```tsx
function ProductCategoriesExample() {
  const [categories, setCategories] = useState(['Electronics']);
  const [inputValue, setInputValue] = useState('');
  
  const predefinedCategories = [
    'Electronics', 'Clothing', 'Home & Garden', 'Sports',
    'Books', 'Toys', 'Health', 'Beauty', 'Automotive'
  ];

  const handleCategoryChange = (newCategories) => {
    // Limit to maximum 5 categories
    if (newCategories.length <= 5) {
      setCategories(newCategories);
    }
  };

  return (
    <div className="product-categories">
      <div className="category-header">
        <Text type="Heading6">Product Categories</Text>
        <Chip size="Small" style="B">
          {categories.length}/5 selected
        </Chip>
      </div>
      
      <ChipInput
        value={categories}
        onValueChange={handleCategoryChange}
        inputValue={inputValue}
        onInputChange={setInputValue}
        options={predefinedCategories}
        placeholder="Select categories..."
        className="category-chip-input"
      />
      
      <div className="category-suggestions">
        <Text type="BodySmall">Suggested categories:</Text>
        <div className="suggestion-chips">
          <List
            data={predefinedCategories
              .filter(cat => !categories.includes(cat))
              .slice(0, 3)
              .map(category => ({ category }))}
            component={({ category }) => (
              <Chip 
                size="Small"
                style="B"
                onClick={() => handleCategoryChange([...categories, category])}
              >
                {category}
              </Chip>
            )}
            keyExtractor={(item) => item.category}
          />
        </div>
      </div>
    </div>
  );
}
```

### Search Filters
```tsx
function SearchFiltersExample() {
  const [filters, setFilters] = useState([]);
  const [inputValue, setInputValue] = useState('');
  
  const availableFilters = [
    'In Stock', 'On Sale', 'Free Shipping', 'New Arrivals',
    'Top Rated', 'Premium', 'Eco Friendly', 'Local Seller'
  ];

  const handleFilterToggle = (filter) => {
    setFilters(prev => 
      prev.includes(filter) 
        ? prev.filter(f => f !== filter)
        : [...prev, filter]
    );
  };

  return (
    <div className="search-filters">
      <div className="filter-header">
        <Text type="Heading6">Search Filters</Text>
        <Button 
          size="Small" 
          type="Ghost"
          onClick={() => setFilters([])}
          disabled={filters.length === 0}
        >
          Clear All
        </Button>
      </div>
      
      <ChipInput
        value={filters}
        onValueChange={setFilters}
        inputValue={inputValue}
        onInputChange={setInputValue}
        options={availableFilters}
        placeholder="Add filters..."
        className="filter-chip-input"
      />
      
      <div className="filter-options">
        <Text type="BodySmall">Quick filters:</Text>
        <div className="quick-filters">
          <List
            data={availableFilters.slice(0, 4).map(filter => ({ filter }))}
            component={({ filter }) => (
              <Button
                size="Small"
                type={filters.includes(filter) ? "Filled" : "Outlined"}
                onClick={() => handleFilterToggle(filter)}
              >
                {filter}
              </Button>
            )}
            keyExtractor={(item) => item.filter}
          />
        </div>
      </div>
    </div>
  );
}
```

### Team Members Assignment
```tsx
function TeamAssignmentExample() {
  const [assignees, setAssignees] = useState(['john.doe']);
  const [inputValue, setInputValue] = useState('');
  
  const teamMembers = [
    { id: 'john.doe', name: 'John Doe', role: 'Developer' },
    { id: 'jane.smith', name: 'Jane Smith', role: 'Designer' },
    { id: 'mike.johnson', name: 'Mike Johnson', role: 'PM' },
    { id: 'sarah.wilson', name: 'Sarah Wilson', role: 'QA' }
  ];

  const memberOptions = teamMembers.map(member => member.id);
  
  const getMemberName = (id) => {
    const member = teamMembers.find(m => m.id === id);
    return member ? member.name : id;
  };

  const handleAssigneeChange = (newAssignees) => {
    setAssignees(newAssignees);
  };

  return (
    <div className="team-assignment">
      <div className="assignment-header">
        <Text type="Heading6">Assign Team Members</Text>
        <Chip size="Small" style="A">
          {assignees.length} assigned
        </Chip>
      </div>
      
      <ChipInput
        value={assignees}
        onValueChange={handleAssigneeChange}
        inputValue={inputValue}
        onInputChange={setInputValue}
        options={memberOptions}
        placeholder="Type to search team members..."
        className="assignment-chip-input"
      />
      
      <div className="team-list">
        <Text type="BodySmall">Available team members:</Text>
        {teamMembers
          .filter(member => !assignees.includes(member.id))
          .map(member => (
            <div key={member.id} className="member-item">
              <div className="member-info">
                <Text type="BodyMedium">{member.name}</Text>
                <Text type="BodySmall">{member.role}</Text>
              </div>
              <Button
                size="Small"
                type="Outlined"
                onClick={() => setAssignees([...assignees, member.id])}
              >
                Assign
              </Button>
            </div>
          ))}
      </div>
    </div>
  );
}
```

### Custom Validation
```tsx
function CustomValidationExample() {
  const [urls, setUrls] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [errors, setErrors] = useState([]);

  const validateURL = (url) => {
    try {
      new URL(url);
      return true;
    } catch {
      return false;
    }
  };

  const handleAddURL = (url) => {
    if (!validateURL(url)) {
      setErrors([...errors, `Invalid URL: ${url}`]);
      return;
    }
    
    if (urls.includes(url)) {
      setErrors([...errors, `URL already exists: ${url}`]);
      return;
    }

    setUrls([...urls, url]);
    setInputValue('');
    setErrors(errors.filter(error => !error.includes(url)));
  };

  const handleURLChange = (newUrls) => {
    setUrls(newUrls);
    // Clear related errors when URL is removed
    setErrors(errors.filter(error => 
      newUrls.some(url => error.includes(url))
    ));
  };

  return (
    <div className="url-validation">
      <Text type="Heading6">Website URLs</Text>
      <ChipInput
        value={urls}
        onValueChange={handleURLChange}
        inputValue={inputValue}
        onInputChange={setInputValue}
        placeholder="Enter website URLs..."
        onAddNewOption={handleAddURL}
        className="url-chip-input"
      />
      
      {errors.length > 0 && (
        <div className="error-messages">
          {errors.map((error, index) => (
            <div key={index} className="error-message">
              <Icon icon="Error" size="Small" />
              <Text type="BodySmall">{error}</Text>
            </div>
          ))}
        </div>
      )}
      
      <div className="url-stats">
        <Text type="BodySmall">
          {urls.length} valid URL{urls.length !== 1 ? 's' : ''} added
        </Text>
      </div>
    </div>
  );
}
```

### Dynamic Options Loading
```tsx
function DynamicOptionsExample() {
  const [selectedTags, setSelectedTags] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);

  const searchTags = async (query) => {
    if (query.length < 2) return;
    
    setLoading(true);
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 500));
      const mockResults = [
        `${query}-tag1`, `${query}-tag2`, `${query}-tag3`,
        `popular-${query}`, `trending-${query}`
      ];
      setOptions(mockResults);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    searchTags(inputValue);
  }, [inputValue]);

  return (
    <div className="dynamic-options">
      <div className="tags-header">
        <Text type="Heading6">Dynamic Tag Search</Text>
        {loading && <Spinner size="Small" />}
      </div>
      
      <ChipInput
        value={selectedTags}
        onValueChange={setSelectedTags}
        inputValue={inputValue}
        onInputChange={setInputValue}
        options={options}
        placeholder="Type to search tags..."
        className="dynamic-chip-input"
      />
      
      {options.length > 0 && (
        <div className="suggested-tags">
          <Text type="BodySmall">Suggested tags:</Text>
          <div className="tag-suggestions">
            {options.slice(0, 5).map(tag => (
              <Chip
                key={tag}
                size="Small"
                style="B"
                onClick={() => {
                  if (!selectedTags.includes(tag)) {
                    setSelectedTags([...selectedTags, tag]);
                    setInputValue('');
                  }
                }}
              >
                {tag}
              </Chip>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}
```

### Form Integration
```tsx
function FormIntegrationExample() {
  const [formData, setFormData] = useState({
    title: '',
    description: '',
    tags: ['frontend'],
    skills: [],
    collaborators: []
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
  };

  const updateFormField = (field, value) => {
    setFormData(prev => ({
      ...prev,
      [field]: value
    }));
  };

  return (
    <Form onSubmit={handleSubmit} className="chip-input-form">
      <FormField name="title" label="Project Title" required>
        <Input 
          value={formData.title}
          onValueChange={(value) => updateFormField('title', value)}
          placeholder="Enter project title"
        />
      </FormField>

      <FormField name="description" label="Description" required>
        <TextArea 
          value={formData.description}
          onValueChange={(value) => updateFormField('description', value)}
          placeholder="Describe your project"
          rows={3}
        />
      </FormField>

      <FormField name="tags" label="Tags">
        <ChipInput
          value={formData.tags}
          onValueChange={(value) => updateFormField('tags', value)}
          placeholder="Add relevant tags..."
          options={['frontend', 'backend', 'fullstack', 'mobile', 'design']}
        />
      </FormField>

      <FormField name="skills" label="Required Skills">
        <ChipInput
          value={formData.skills}
          onValueChange={(value) => updateFormField('skills', value)}
          placeholder="What skills are needed?"
          options={['React', 'Node.js', 'Python', 'Design', 'DevOps']}
        />
      </FormField>

      <FormField name="collaborators" label="Collaborators">
        <ChipInput
          value={formData.collaborators}
          onValueChange={(value) => updateFormField('collaborators', value)}
          placeholder="Add collaborator emails..."
        />
      </FormField>

      <ButtonGroup>
        <Button type="Filled" actionType="submit">
          Create Project
        </Button>
        <Button type="Outlined" actionType="reset">
          Clear Form
        </Button>
      </ButtonGroup>
    </Form>
  );
}
```

### Advanced Filtering Interface
```tsx
function AdvancedFilteringExample() {
  const [filters, setFilters] = useState({
    categories: [],
    brands: [],
    features: [],
    priceRanges: []
  });

  const filterOptions = {
    categories: ['Electronics', 'Clothing', 'Home', 'Sports'],
    brands: ['Apple', 'Samsung', 'Nike', 'Adidas'],
    features: ['Wireless', 'Waterproof', 'Bluetooth', 'Fast Charging'],
    priceRanges: ['Under $50', '$50-$100', '$100-$200', 'Over $200']
  };

  const updateFilter = (filterType, values) => {
    setFilters(prev => ({
      ...prev,
      [filterType]: values
    }));
  };

  const clearAllFilters = () => {
    setFilters({
      categories: [],
      brands: [],
      features: [],
      priceRanges: []
    });
  };

  const totalFilters = Object.values(filters).flat().length;

  return (
    <div className="advanced-filtering">
      <div className="filter-header">
        <Text type="Heading5">Product Filters</Text>
        <div className="filter-actions">
          <Chip size="Small" style="A">
            {totalFilters} filter{totalFilters !== 1 ? 's' : ''} active
          </Chip>
          <Button 
            size="Small" 
            type="Ghost"
            onClick={clearAllFilters}
            disabled={totalFilters === 0}
          >
            Clear All
          </Button>
        </div>
      </div>

      <div className="filter-sections">
        {Object.entries(filterOptions).map(([filterType, options]) => (
          <div key={filterType} className="filter-section">
            <Text type="Heading6">
              {filterType.charAt(0).toUpperCase() + filterType.slice(1)}
            </Text>
            <ChipInput
              value={filters[filterType]}
              onValueChange={(values) => updateFilter(filterType, values)}
              options={options}
              placeholder={`Select ${filterType}...`}
              className="filter-chip-input"
            />
          </div>
        ))}
      </div>

      <div className="filter-summary">
        <Text type="BodyMedium">Active Filters:</Text>
        {totalFilters > 0 ? (
          <div className="active-filters">
            {Object.entries(filters).map(([type, values]) => 
              values.map(value => (
                <Chip 
                  key={`${type}-${value}`}
                  size="Small"
                  style="A"
                  onRemove={() => updateFilter(type, values.filter(v => v !== value))}
                >
                  {value}
                </Chip>
              ))
            )}
          </div>
        ) : (
          <Text type="BodySmall">No filters applied</Text>
        )}
      </div>
    </div>
  );
}
```