# Search

## Description

A versatile search input component that supports both automatic and manual search modes. In auto mode, searches are triggered automatically with debouncing as the user types. In manual mode, searches are triggered only when the user presses Enter or clicks the search button. The component includes built-in search, clear, and loading states with full keyboard navigation support.

## Aliases

- Search
- SearchInput
- SearchField
- SearchBox
- QueryInput

## Props Breakdown

**Extends:** `ControlledFormComponentProps<string>` & `Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'value'>`

| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `style` | `'Auto' \| 'Manual'` | `'Auto'` | No | Search mode - 'Auto' for debounced search on input, 'Manual' for search on enter/submit |
| `onSearch` | `SearchCallback` | - | No | Callback function to handle search with the query string |
| `debounceMs` | `number` | `300` | No | Debounce delay in milliseconds for auto mode |
| `minCharacters` | `number` | `1` | No | Minimum characters required to trigger search in auto mode |
| `showSubmitButton` | `boolean` | `true` | No | Show submit button in manual mode |
| `showClearButton` | `boolean` | `true` | No | Show clear button when there is text |
| `loading` | `boolean` | `false` | No | Loading state while search is in progress |
| `component-variant` | `string` | - | No | Provide a way to override the styling |

## Examples

### Basic Auto Search
```tsx
import { Search } from '@delightui/components';
import { useState } from 'react';

function BasicAutoSearch() {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const handleSearch = async (query: string) => {
    setLoading(true);
    try {
      // Simulate API call
      const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
      const data = await response.json();
      setResults(data.results);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <Search
        style="Auto"
        onSearch={handleSearch}
        loading={loading}
        placeholder="Search products..."
        minCharacters={2}
        debounceMs={500}
      />
      {results.length > 0 && (
        <div className="search-results">
          {results.map(result => (
            <div key={result.id}>{result.name}</div>
          ))}
        </div>
      )}
    </div>
  );
}
```

### Manual Search with Validation
```tsx
function ManualSearchExample() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [error, setError] = useState('');

  const handleSearch = async (searchQuery: string) => {
    if (searchQuery.length < 3) {
      setError('Search query must be at least 3 characters');
      return;
    }

    setError('');
    try {
      const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
      if (!response.ok) throw new Error('Search failed');
      
      const data = await response.json();
      setResults(data.results);
    } catch (err) {
      setError('Search failed. Please try again.');
    }
  };

  return (
    <div>
      <Search
        style="Manual"
        value={query}
        onValueChange={setQuery}
        onSearch={handleSearch}
        placeholder="Enter search terms and press Enter..."
        minCharacters={3}
        showSubmitButton={true}
      />
      {error && <div className="error">{error}</div>}
      {results.length > 0 && (
        <div className="results-count">
          Found {results.length} results
        </div>
      )}
    </div>
  );
}
```

### Advanced Search with Filters
```tsx
function AdvancedSearchExample() {
  const [searchState, setSearchState] = useState({
    query: '',
    filters: {
      category: '',
      priceRange: '',
      sortBy: 'relevance'
    },
    results: [],
    loading: false,
    hasSearched: false
  });

  const performSearch = async (query: string) => {
    setSearchState(prev => ({ ...prev, loading: true }));
    
    try {
      const params = new URLSearchParams({
        q: query,
        category: searchState.filters.category,
        priceRange: searchState.filters.priceRange,
        sortBy: searchState.filters.sortBy
      });

      const response = await fetch(`/api/search?${params}`);
      const data = await response.json();
      
      setSearchState(prev => ({
        ...prev,
        results: data.results,
        loading: false,
        hasSearched: true
      }));
    } catch (error) {
      setSearchState(prev => ({
        ...prev,
        loading: false,
        hasSearched: true,
        results: []
      }));
    }
  };

  const updateFilter = (key: string, value: string) => {
    setSearchState(prev => ({
      ...prev,
      filters: { ...prev.filters, [key]: value }
    }));
    
    // Re-search if we have a query
    if (searchState.query) {
      performSearch(searchState.query);
    }
  };

  return (
    <div className="advanced-search">
      <div className="search-header">
        <Search
          style="Auto"
          value={searchState.query}
          onValueChange={(query) => setSearchState(prev => ({ ...prev, query }))}
          onSearch={performSearch}
          loading={searchState.loading}
          placeholder="Search for products..."
          debounceMs={400}
          minCharacters={2}
        />
      </div>

      <div className="search-filters">
        <Select
          value={searchState.filters.category}
          onValueChange={(value) => updateFilter('category', value)}
          placeholder="All Categories"
        >
          <Option value="">All Categories</Option>
          <Option value="electronics">Electronics</Option>
          <Option value="clothing">Clothing</Option>
          <Option value="books">Books</Option>
        </Select>

        <Select
          value={searchState.filters.priceRange}
          onValueChange={(value) => updateFilter('priceRange', value)}
          placeholder="Any Price"
        >
          <Option value="">Any Price</Option>
          <Option value="0-25">Under $25</Option>
          <Option value="25-100">$25 - $100</Option>
          <Option value="100+">Over $100</Option>
        </Select>

        <Select
          value={searchState.filters.sortBy}
          onValueChange={(value) => updateFilter('sortBy', value)}
        >
          <Option value="relevance">Relevance</Option>
          <Option value="price-low">Price: Low to High</Option>
          <Option value="price-high">Price: High to Low</Option>
          <Option value="newest">Newest First</Option>
        </Select>
      </div>

      <div className="search-results">
        {searchState.loading && <Spinner />}
        
        {searchState.hasSearched && !searchState.loading && (
          <>
            <div className="results-summary">
              {searchState.results.length > 0 ? (
                <Text type="BodyMedium">
                  Found {searchState.results.length} results for "{searchState.query}"
                </Text>
              ) : (
                <Text type="BodyMedium">
                  No results found for "{searchState.query}"
                </Text>
              )}
            </div>
            
            <div className="results-grid">
              {searchState.results.map(product => (
                <ProductCard key={product.id} product={product} />
              ))}
            </div>
          </>
        )}
      </div>
    </div>
  );
}
```

### User/Contact Search
```tsx
function UserSearchExample() {
  const [selectedUsers, setSelectedUsers] = useState([]);
  const [searchResults, setSearchResults] = useState([]);
  const [searching, setSearching] = useState(false);

  const searchUsers = async (query: string) => {
    if (!query.trim()) {
      setSearchResults([]);
      return;
    }

    setSearching(true);
    try {
      const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}`);
      const users = await response.json();
      
      // Filter out already selected users
      const availableUsers = users.filter(
        user => !selectedUsers.find(selected => selected.id === user.id)
      );
      
      setSearchResults(availableUsers);
    } finally {
      setSearching(false);
    }
  };

  const addUser = (user) => {
    setSelectedUsers(prev => [...prev, user]);
    setSearchResults([]);
  };

  const removeUser = (userId) => {
    setSelectedUsers(prev => prev.filter(user => user.id !== userId));
  };

  return (
    <div className="user-search">
      <div className="search-section">
        <Text type="Heading6">Add Team Members</Text>
        <Search
          style="Auto"
          onSearch={searchUsers}
          loading={searching}
          placeholder="Search by name or email..."
          minCharacters={2}
          debounceMs={300}
        />
      </div>

      {searchResults.length > 0 && (
        <div className="search-results">
          <Text type="BodySmall">Search Results:</Text>
          {searchResults.map(user => (
            <div key={user.id} className="user-result">
              <div className="user-info">
                <img src={user.avatar} alt={user.name} className="avatar" />
                <div>
                  <Text type="BodyMedium">{user.name}</Text>
                  <Text type="BodySmall">{user.email}</Text>
                </div>
              </div>
              <Button size="Small" onClick={() => addUser(user)}>
                Add
              </Button>
            </div>
          ))}
        </div>
      )}

      {selectedUsers.length > 0 && (
        <div className="selected-users">
          <Text type="BodySmall">Selected Members ({selectedUsers.length}):</Text>
          <div className="user-chips">
            {selectedUsers.map(user => (
              <Chip 
                key={user.id}
                onRemove={() => removeUser(user.id)}
              >
                {user.name}
              </Chip>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}
```

### Document/File Search
```tsx
function DocumentSearchExample() {
  const [searchState, setSearchState] = useState({
    query: '',
    documents: [],
    loading: false,
    selectedDoc: null
  });

  const searchDocuments = async (query: string) => {
    setSearchState(prev => ({ ...prev, loading: true }));
    
    try {
      const response = await fetch(`/api/documents/search`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          query,
          filters: {
            fileTypes: ['pdf', 'doc', 'txt'],
            dateRange: 'last-month'
          }
        })
      });
      
      const results = await response.json();
      setSearchState(prev => ({
        ...prev,
        documents: results.documents,
        loading: false
      }));
    } catch (error) {
      setSearchState(prev => ({
        ...prev,
        documents: [],
        loading: false
      }));
    }
  };

  const openDocument = (doc) => {
    setSearchState(prev => ({ ...prev, selectedDoc: doc }));
  };

  return (
    <div className="document-search">
      <div className="search-bar">
        <Search
          style="Auto"
          value={searchState.query}
          onValueChange={(query) => setSearchState(prev => ({ ...prev, query }))}
          onSearch={searchDocuments}
          loading={searchState.loading}
          placeholder="Search documents, files, and content..."
          debounceMs={600}
          minCharacters={3}
        />
      </div>

      <div className="search-results">
        {searchState.documents.length > 0 && (
          <>
            <div className="results-header">
              <Text type="BodyMedium">
                {searchState.documents.length} documents found
              </Text>
            </div>
            
            <div className="document-list">
              {searchState.documents.map(doc => (
                <div key={doc.id} className="document-item">
                  <div className="doc-icon">
                    <Icon icon={getFileIcon(doc.type)} />
                  </div>
                  <div className="doc-info">
                    <Text type="BodyMedium">{doc.title}</Text>
                    <Text type="BodySmall">{doc.summary}</Text>
                    <div className="doc-meta">
                      <Text type="BodySmall">
                        {doc.type.toUpperCase()} • {formatFileSize(doc.size)} • {formatDate(doc.modified)}
                      </Text>
                    </div>
                  </div>
                  <div className="doc-actions">
                    <Button size="Small" onClick={() => openDocument(doc)}>
                      Open
                    </Button>
                  </div>
                </div>
              ))}
            </div>
          </>
        )}

        {searchState.query && searchState.documents.length === 0 && !searchState.loading && (
          <div className="no-results">
            <Text type="BodyMedium">No documents found for "{searchState.query}"</Text>
            <Text type="BodySmall">Try different keywords or check your spelling</Text>
          </div>
        )}
      </div>

      {searchState.selectedDoc && (
        <Modal onClose={() => setSearchState(prev => ({ ...prev, selectedDoc: null }))}>
          <ModalHeader>
            <Text type="Heading4">{searchState.selectedDoc.title}</Text>
          </ModalHeader>
          <div className="document-preview">
            {/* Document content preview */}
          </div>
        </Modal>
      )}
    </div>
  );
}
```

### Search with Recent History
```tsx
function SearchWithHistoryExample() {
  const [searchHistory, setSearchHistory] = useState([]);
  const [showHistory, setShowHistory] = useState(false);
  const [currentQuery, setCurrentQuery] = useState('');
  const [results, setResults] = useState([]);

  const addToHistory = (query: string) => {
    if (!query.trim()) return;
    
    setSearchHistory(prev => {
      const filtered = prev.filter(item => item !== query);
      return [query, ...filtered].slice(0, 5); // Keep last 5 searches
    });
  };

  const performSearch = async (query: string) => {
    if (!query.trim()) return;
    
    addToHistory(query);
    setShowHistory(false);
    
    // Perform actual search
    const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
    const data = await response.json();
    setResults(data.results);
  };

  const selectFromHistory = (query: string) => {
    setCurrentQuery(query);
    performSearch(query);
  };

  const clearHistory = () => {
    setSearchHistory([]);
    setShowHistory(false);
  };

  return (
    <div className="search-with-history">
      <div className="search-container">
        <Search
          style="Manual"
          value={currentQuery}
          onValueChange={setCurrentQuery}
          onSearch={performSearch}
          placeholder="Search... (Press Enter)"
          onFocus={() => setShowHistory(true)}
        />
        
        {showHistory && searchHistory.length > 0 && (
          <div className="search-history">
            <div className="history-header">
              <Text type="BodySmall">Recent Searches</Text>
              <Button size="Small" type="Ghost" onClick={clearHistory}>
                Clear
              </Button>
            </div>
            <div className="history-items">
              {searchHistory.map((query, index) => (
                <div 
                  key={index}
                  className="history-item"
                  onClick={() => selectFromHistory(query)}
                >
                  <Icon icon="Search" size="Small" />
                  <Text type="BodyMedium">{query}</Text>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>

      <div className="search-results">
        {results.map(result => (
          <div key={result.id} className="result-item">
            {/* Result content */}
          </div>
        ))}
      </div>
    </div>
  );
}
```

### Real-time Search with Suggestions
```tsx
function RealTimeSearchExample() {
  const [searchState, setSearchState] = useState({
    query: '',
    suggestions: [],
    results: [],
    showSuggestions: false,
    loading: false
  });

  const fetchSuggestions = async (query: string) => {
    if (query.length < 2) {
      setSearchState(prev => ({ ...prev, suggestions: [], showSuggestions: false }));
      return;
    }

    try {
      const response = await fetch(`/api/search/suggestions?q=${encodeURIComponent(query)}`);
      const data = await response.json();
      
      setSearchState(prev => ({
        ...prev,
        suggestions: data.suggestions,
        showSuggestions: true
      }));
    } catch (error) {
      console.error('Failed to fetch suggestions:', error);
    }
  };

  const performFullSearch = async (query: string) => {
    setSearchState(prev => ({ 
      ...prev, 
      loading: true, 
      showSuggestions: false 
    }));

    try {
      const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
      const data = await response.json();
      
      setSearchState(prev => ({
        ...prev,
        results: data.results,
        loading: false
      }));
    } catch (error) {
      setSearchState(prev => ({
        ...prev,
        results: [],
        loading: false
      }));
    }
  };

  const handleQueryChange = (query: string) => {
    setSearchState(prev => ({ ...prev, query }));
    fetchSuggestions(query);
  };

  const selectSuggestion = (suggestion: string) => {
    setSearchState(prev => ({ 
      ...prev, 
      query: suggestion, 
      showSuggestions: false 
    }));
    performFullSearch(suggestion);
  };

  return (
    <div className="realtime-search">
      <div className="search-input-container">
        <Search
          style="Auto"
          value={searchState.query}
          onValueChange={handleQueryChange}
          onSearch={performFullSearch}
          loading={searchState.loading}
          placeholder="Start typing to see suggestions..."
          debounceMs={200}
          minCharacters={2}
        />

        {searchState.showSuggestions && searchState.suggestions.length > 0 && (
          <div className="suggestions-dropdown">
            {searchState.suggestions.map((suggestion, index) => (
              <div
                key={index}
                className="suggestion-item"
                onClick={() => selectSuggestion(suggestion.text)}
              >
                <Icon icon="Search" size="Small" />
                <div className="suggestion-content">
                  <Text type="BodyMedium">{suggestion.text}</Text>
                  {suggestion.category && (
                    <Text type="BodySmall">in {suggestion.category}</Text>
                  )}
                </div>
                {suggestion.count && (
                  <Text type="BodySmall">{suggestion.count} results</Text>
                )}
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="search-results">
        {searchState.results.map(result => (
          <div key={result.id} className="search-result">
            <Text type="Heading6">{result.title}</Text>
            <Text type="BodyMedium">{result.description}</Text>
            <Text type="BodySmall">{result.url}</Text>
          </div>
        ))}
      </div>
    </div>
  );
}
```

## Search Modes

### Auto Mode
- Searches automatically as user types
- Uses debouncing to prevent excessive API calls
- Configurable minimum character threshold
- Ideal for real-time search experiences

### Manual Mode
- Searches only when user presses Enter or clicks search button
- Better for expensive search operations
- Gives users full control over when to search
- Includes visual submit button

## Accessibility Features

- Full keyboard navigation support
- ARIA labels for screen readers
- Focus management for search and clear buttons
- Semantic form structure in manual mode

## Performance Considerations

- Built-in debouncing for auto mode prevents API spam
- Duplicate search prevention
- Efficient state management with presenter pattern
- Memoized components to prevent unnecessary re-renders

## Related Components

- **[Input](Input.md)** - Base input component
- **[Button](../atoms/Button.md)** - Submit button component
- **[IconButton](../atoms/IconButton.md)** - Clear button component
- **[Icon](../atoms/Icon.md)** - Search and close icons