@atom_design/search
v1.0.1
Published
A comprehensive React Native search component with filters, debouncing, and customizable results.
Readme
@atom_design/search
A comprehensive and flexible Search Component for React Native, built as part of the Atom Design System. Features a search bar with debouncing, filter chips, and customizable results list.
✨ Features
- 🔍 Search Bar - Clean input with search icon and clear button
- ⏱️ Debounced Search - Configurable delay to reduce API calls
- 🏷️ Filter Chips - Multi-select filter tags with checkmarks
- 📋 Results List - FlatList with custom rendering support
- ⌨️ Keyboard Handling - Auto-dismiss and return key support
- 🔄 Loading State - Spinner indicator during search
- 🎨 Fully Customizable - Colors, styles, and components
- ♿ Accessible - Screen reader support with proper roles
- 🎯 TypeScript Support - Full type definitions included
📦 Installation
npm install @atom_design/searchOr with yarn:
yarn add @atom_design/searchPeer Dependencies
Make sure you have the required peer dependencies installed:
npm install react-native-vector-icons prop-typesFor react-native-vector-icons setup, follow the installation guide.
📥 Import
import Search from '@atom_design/search';
// or
import { Search } from '@atom_design/search';🎯 Basic Usage
import React, { useState, useMemo } from 'react';
import Search from '@atom_design/search';
function SearchExample() {
const [results, setResults] = useState([]);
const handleSearch = (query) => {
// Filter or fetch results based on query
const filtered = allItems.filter(item =>
item.label.toLowerCase().includes(query.toLowerCase())
);
setResults(filtered);
};
return (
<Search
placeholder="Search items..."
onSearch={handleSearch}
results={results}
onResultPress={(item) => console.log('Selected:', item)}
/>
);
}📋 Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| placeholder | string | 'Search...' | Placeholder text |
| value | string | - | Controlled input value |
| onSearch | function | - | Debounced search callback |
| onChangeText | function | - | Immediate text change callback |
| results | array | [] | Array of result items |
| filters | array | [] | Array of filter options |
| selectedFilters | array | - | Controlled selected filters |
| onFilterSelect | function | - | Filter change callback |
| onResultPress | function | - | Result item press callback |
| renderResult | function | - | Custom result renderer |
| loading | boolean | false | Show loading indicator |
| debounceDelay | number | 300 | Debounce delay in ms |
| showClearButton | boolean | true | Show clear button |
| showSearchIcon | boolean | true | Show search icon |
| emptyText | string | 'No results found' | Empty state text |
| emptyComponent | element | - | Custom empty component |
| primaryColor | string | '#d9232d' | Primary/accent color |
| disabled | boolean | false | Disable the component |
| autoFocus | boolean | false | Auto focus on mount |
| keyboardType | string | 'default' | Keyboard type |
| returnKeyType | string | 'search' | Return key type |
| onSubmitEditing | function | - | Submit callback |
| containerStyle | ViewStyle | - | Container style |
| searchBarStyle | ViewStyle | - | Search bar style |
| inputStyle | TextStyle | - | Input text style |
| filtersContainerStyle | ViewStyle | - | Filters container style |
| resultsContainerStyle | ViewStyle | - | Results list style |
| testID | string | - | Test ID |
🎨 Customization Examples
With Filters
<Search
placeholder="Search products..."
onSearch={handleSearch}
results={results}
filters={['All', 'Electronics', 'Clothing', 'Home']}
onFilterSelect={(filters) => {
console.log('Active filters:', filters);
// Re-fetch with filters
}}
/>With Loading State
const [loading, setLoading] = useState(false);
const handleSearch = async (query) => {
setLoading(true);
const data = await fetchFromAPI(query);
setResults(data);
setLoading(false);
};
<Search
onSearch={handleSearch}
results={results}
loading={loading}
/>Custom Result Rendering
<Search
onSearch={handleSearch}
results={results}
renderResult={({ item }) => (
<TouchableOpacity style={styles.customResult}>
<Image source={{ uri: item.image }} style={styles.image} />
<View>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subtitle}>{item.category}</Text>
</View>
<Text style={styles.price}>${item.price}</Text>
</TouchableOpacity>
)}
/>Controlled Mode
const [query, setQuery] = useState('');
const [filters, setFilters] = useState([]);
<Search
value={query}
onChangeText={setQuery}
selectedFilters={filters}
onFilterSelect={setFilters}
results={results}
/>Custom Colors
<Search
onSearch={handleSearch}
results={results}
filters={['All', 'Active', 'Completed']}
primaryColor="#4CAF50"
/>No Debounce (Instant Search)
<Search
onSearch={handleSearch}
results={results}
debounceDelay={0}
/>🧪 Test Screen Example
import React, { useState, useMemo } from 'react';
import { View, Text, ScrollView, StyleSheet, SafeAreaView } from 'react-native';
import Search from '@atom_design/search';
const SearchTestScreen = () => {
const [query, setQuery] = useState('');
const [selectedFilters, setSelectedFilters] = useState([]);
const [loading, setLoading] = useState(false);
const [basicQuery, setBasicQuery] = useState('');
// Sample data
const allProducts = [
{ id: '1', label: 'Industrial Safety Helmet', category: 'Safety' },
{ id: '2', label: 'Work Gloves - Heavy Duty', category: 'Safety' },
{ id: '3', label: 'Power Drill 20V', category: 'Tools' },
{ id: '4', label: 'LED Work Light', category: 'Tools' },
{ id: '5', label: 'Steel Toe Boots', category: 'Footwear' },
{ id: '6', label: 'Welding Mask Auto-Darkening', category: 'Safety' },
{ id: '7', label: 'Measuring Tape 25ft', category: 'Tools' },
{ id: '8', label: 'Safety Goggles', category: 'Safety' },
{ id: '9', label: 'Cordless Angle Grinder', category: 'Tools' },
{ id: '10', label: 'Anti-Slip Work Shoes', category: 'Footwear' },
];
const filters = ['All', 'Safety', 'Tools', 'Footwear'];
// Filter results for basic search
const basicResults = useMemo(() => {
if (!basicQuery) return allProducts;
return allProducts.filter(item =>
item.label.toLowerCase().includes(basicQuery.toLowerCase())
);
}, [basicQuery]);
// Filter results based on query and filters
const filteredResults = useMemo(() => {
let results = allProducts;
// Apply text search
if (query) {
results = results.filter(item =>
item.label.toLowerCase().includes(query.toLowerCase())
);
}
// Apply category filters
if (selectedFilters.length > 0 && !selectedFilters.includes('All')) {
results = results.filter(item =>
selectedFilters.includes(item.category)
);
}
return results;
}, [query, selectedFilters]);
// Handle search callback
const handleSearch = (searchQuery, activeFilters) => {
console.log('Search:', searchQuery, 'Filters:', activeFilters);
};
const handleResultPress = (item) => {
console.log('Selected product:', item);
// Navigate to product details
};
return (
<SafeAreaView style={styles.container}>
<ScrollView>
<Text style={styles.header}>Atom Design - Search</Text>
{/* Basic Search */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Basic Search</Text>
<Search
placeholder="Search products..."
value={basicQuery}
onChangeText={setBasicQuery}
onSearch={handleSearch}
results={basicResults}
onResultPress={handleResultPress}
/>
<Text style={styles.info}>
Showing {basicResults.length} of {allProducts.length} products
</Text>
</View>
{/* With Filters */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Search with Filters</Text>
<Search
placeholder="Search by name..."
value={query}
onChangeText={setQuery}
onSearch={handleSearch}
results={filteredResults}
filters={filters}
selectedFilters={selectedFilters}
onFilterSelect={(newFilters) => setSelectedFilters(newFilters)}
onResultPress={handleResultPress}
/>
<Text style={styles.info}>
Found {filteredResults.length} results
{selectedFilters.length > 0 && ` in ${selectedFilters.join(', ')}`}
</Text>
</View>
{/* Loading State */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Loading State Demo</Text>
<Search
placeholder="Type to search..."
loading={loading}
onSearch={() => {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
}}
results={[]}
debounceDelay={500}
/>
</View>
{/* Custom Primary Color */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Custom Color</Text>
<Search
placeholder="Search..."
results={filteredResults.slice(0, 3)}
filters={['Option A', 'Option B', 'Option C']}
primaryColor="#2196F3"
onResultPress={handleResultPress}
/>
</View>
{/* Disabled State */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Disabled State</Text>
<Search
placeholder="Search disabled..."
disabled
results={[]}
/>
</View>
{/* Custom Empty State */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Custom Empty Message</Text>
<Search
placeholder="Search something..."
value="xyz123"
results={[]}
emptyText="No products match your search. Try different keywords."
/>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 20,
color: '#1A1A1A',
},
section: {
backgroundColor: '#FFFFFF',
marginHorizontal: 16,
marginBottom: 16,
padding: 16,
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 12,
color: '#1A1A1A',
},
info: {
marginTop: 8,
fontSize: 13,
color: '#666',
fontStyle: 'italic',
},
});
export default SearchTestScreen;📱 Result Item Format
Results should be objects with at least one of these properties:
const results = [
{ id: '1', label: 'Product Name' }, // Uses 'label'
{ id: '2', title: 'Product Title' }, // Uses 'title'
{ id: '3', name: 'Product' }, // Uses 'name'
];For custom rendering, use the renderResult prop.
♿ Accessibility
The Search component includes comprehensive accessibility support:
- Search input uses
accessibilityRole="search" - Filter chips use
accessibilityRole="tab"with selection state - Result items use
accessibilityRole="button" - Clear button has
accessibilityLabel="Clear search" - Empty state is announced to screen readers
📝 TypeScript
Full TypeScript definitions are included:
import Search, { SearchProps, SearchResultItem } from '@atom_design/search';
interface Product extends SearchResultItem {
price: number;
category: string;
}
const MySearch: React.FC = () => {
const [results, setResults] = useState<Product[]>([]);
const handleSearch = (query: string, filters: string[]): void => {
// Fetch results
};
return (
<Search
onSearch={handleSearch}
results={results}
filters={['All', 'Category A']}
/>
);
};📄 License
MIT © Atom Design
👤 Author
Atom Design Team
Part of the Atom Design System for React Native.
