This directory contains reusable UI components that reduce code duplication across the application and ensure consistent design patterns.
Purpose: Standardized text input with label, validation, and error display.
Props:
label(string, required) - Input labelrequired(boolean) - Shows asterisk for required fieldserror(string) - Error message to display- All standard TextInput props
Usage:
<FormInput
label="Product Name"
required
placeholder="Enter product name"
value={name}
onChangeText={setName}
error={nameError}
/>Replaces: ~15 lines of label + TextInput + error display per instance Used in: AddEditProductScreen, BillScreen (8+ instances)
Purpose: Consistent search input with icon and clear button.
Props:
value(string, required) - Current search valueonChangeText(function, required) - Handler for text changesplaceholder(string) - Placeholder text (default: "Search...")onClear(function) - Custom clear handler
Usage:
<SearchBar
value={searchQuery}
onChangeText={setSearchQuery}
placeholder="Search products..."
/>Replaces: ~12 lines of search UI per instance Used in: ProductsListScreen, SelectProductScreen, CustomerAnalyticsScreen, StockScreen (7+ instances)
Purpose: Consistent screen header with back button, title, and optional actions.
Props:
title(string, required) - Header titlesubtitle(string) - Optional subtitleonBack(function) - Back button handlerrightActions(ReactNode) - Custom right-side actionsshowBackButton(boolean) - Show/hide back button (default: true)
Usage:
<ScreenHeader
title="Product Details"
subtitle="Last updated: Today"
onBack={() => navigation.goBack()}
rightActions={
<TouchableOpacity onPress={handleEdit}>
<Icon name="edit" size={24} />
</TouchableOpacity>
}
/>Replaces: ~30-35 lines of header layout per instance Used in: ProductDetailsScreen, CustomerDetailsScreen, InvoiceHistoryScreen (8+ instances)
Purpose: Reusable bottom sheet modal with consistent overlay and animations.
Props:
visible(boolean, required) - Modal visibilityonClose(function, required) - Close handlertitle(string, required) - Modal titlechildren(ReactNode, required) - Modal contentmaxHeight(number | string) - Max height (default: "80%")
Usage:
<BottomSheetModal
visible={showFilterModal}
onClose={() => setShowFilterModal(false)}
title="Sort & Filter"
maxHeight="85%"
>
{/* Your filter content */}
</BottomSheetModal>Replaces: ~40-50 lines of modal boilerplate per instance Used in: ProductsListScreen (2 modals), SelectProductScreen, BillScreen (2 modals), StockScreen (10+ instances)
Purpose: Single chip-style button for categories, filters, or tags.
Props:
label(string, required) - Button labelactive(boolean) - Active state stylingonPress(function, required) - Press handlericon(string) - Optional icon namestyle(ViewStyle) - Custom styles
Usage:
<ChipButton
label="Electronics"
active={category === 'Electronics'}
onPress={() => setCategory('Electronics')}
icon="check"
/>Replaces: ~20-25 lines per chip button Used in: ProductsListScreen, SelectProductScreen, CustomerAnalyticsScreen (15+ instances)
Purpose: Group of chip buttons with selection management.
Props:
options(Array<{label, value}>, required) - Chip optionsselectedValue(string, required) - Currently selected valueonSelect(function, required) - Selection handlerhorizontal(boolean) - Horizontal scrolling (default: true)
Usage:
<ChipGroup
options={[
{ label: 'All', value: 'all' },
{ label: 'In Stock', value: 'in-stock' },
{ label: 'Low Stock', value: 'low' }
]}
selectedValue={stockFilter}
onSelect={setStockFilter}
/>Replaces: ~15-20 lines of ScrollView + mapped chips Used in: ProductsListScreen, SelectProductScreen (5+ instances)
Purpose: Display metric with label, value, optional icon and subtitle.
Props:
label(string, required) - Metric labelvalue(string | number, required) - Metric valuecolor(string) - Value text coloricon(ReactNode) - Optional iconsubtitle(string) - Optional subtitle
Usage:
<MetricCard
label="Total Revenue"
value="$1,234.56"
color={Colors.success}
icon={<Icon name="trending-up" size={24} color={Colors.success} />}
subtitle="↑ 12% from last month"
/>Replaces: ~15-20 lines of metric display Used in: DashboardScreen, CustomerDetailsScreen, ProductDetailsScreen (20+ instances)
Purpose: Reusable settings row with title, subtitle, and switch toggle.
Props:
title(string, required) - Setting titlesubtitle(string) - Optional descriptionvalue(boolean, required) - Switch stateonValueChange(function, required) - Handler for switch changesdisabled(boolean) - Disable switch (default: false)
Usage:
<SettingRow
title="Enable Notifications"
subtitle="Receive stock level alerts"
value={notificationsEnabled}
onValueChange={setNotificationsEnabled}
/>Replaces: ~20-25 lines of View + Text + Switch per instance Used in: NotificationSettingsScreen (4 instances)
Purpose: Number/value picker with left/right arrow buttons.
Props:
value(number | string, required) - Current value to displayonIncrement(function, required) - Handler for incrementonDecrement(function, required) - Handler for decrementlabel(string) - Optional label above valuesubtitle(string) - Optional subtitle below valuevariant('h1' | 'h2' | 'h3' | 'body') - Typography variant (default: 'h2')style(ViewStyle) - Custom styles
Usage:
<NumberPicker
value={monthlyDay}
onIncrement={() => setMonthlyDay(Math.min(31, monthlyDay + 1))}
onDecrement={() => setMonthlyDay(Math.max(1, monthlyDay - 1))}
subtitle="day of month"
/>Replaces: ~25-30 lines of arrow buttons + display container per instance Used in: NotificationSettingsScreen (3 instances)
Purpose: Simple horizontal divider line for visual separation.
Props:
color(string) - Divider color (default: Colors.border)thickness(number) - Line thickness in pixels (default: 1)spacing('sm' | 'md' | 'lg' | 'none') - Vertical spacing (default: 'md')style(ViewStyle) - Custom styles
Usage:
<Divider />
<Divider color={Colors.primary} thickness={2} spacing="lg" />Replaces: ~5-8 lines of View with height + backgroundColor per instance Used in: NotificationSettingsScreen (8+ instances), across many screens
Purpose: Reusable menu item with icon, title, subtitle, and navigation arrow.
Props:
icon(any) - Icon name to displaytitle(string) - Main text to displaysubtitle(string, optional) - Secondary text below titleonPress(() => void) - Callback function when item is pressediconColor(string, optional) - Custom icon colordanger(boolean, optional) - Display in danger/warning style
Usage:
<MenuItem
icon="settings"
title="Notification Settings"
subtitle="Manage your alerts"
onPress={() => navigation.navigate('Settings')}
/>
<MenuItem
icon="trash"
title="Delete Account"
danger
onPress={handleDelete}
/>
<MenuItem
icon="star"
title="Premium"
iconColor={Colors.gold}
onPress={() => navigation.navigate('Premium')}
/>Replaces: ~18-22 lines of TouchableOpacity + Card + Icon + Typography per instance Used in: MoreScreen (8 instances)
- SearchBar: 7 instances × 12 lines = ~84 lines
- BottomSheetModal: 10 instances × 45 lines = ~450 lines
- FormInput: 8 instances × 15 lines = ~120 lines
- ScreenHeader: 8 instances × 32 lines = ~256 lines
- ChipButton/ChipGroup: 5 instances × 18 lines = ~90 lines
- MetricCard: 10 instances × 17 lines = ~170 lines
- SettingRow: 4 instances × 22 lines = ~88 lines
- NumberPicker: 3 instances × 28 lines = ~84 lines
- Divider: 15+ instances × 6 lines = ~90 lines
- MenuItem: 8 instances × 20 lines = ~160 lines
Total Duplicate Code Removed: ~1,592 lines
- Consistency: All screens use the same UI patterns
- Maintainability: Single source of truth for each pattern
- Type Safety: Proper TypeScript interfaces for all props
- Accessibility: Centralized improvements benefit all screens
- Testing: Test once, works everywhere
- Performance: Optimized components reused throughout app
components/
├── index.ts # Centralized exports
├── FormComponents
│ ├── FormInput.tsx # Label + TextInput + validation
│ └── SearchBar.tsx # Search input with icon
├── LayoutComponents
│ ├── ScreenHeader.tsx # Screen header with back button
│ ├── BottomSheetModal.tsx # Bottom sheet modal wrapper
│ ├── ChipButton.tsx # Single chip button
│ ├── ChipGroup.tsx # Group of chips
│ ├── MetricCard.tsx # Metric display card
│ ├── SettingRow.tsx # Settings row with switch
│ ├── NumberPicker.tsx # Number picker with arrows
│ ├── Divider.tsx # Horizontal divider line
│ └── MenuItem.tsx # Menu item with icon and arrow
└── BaseComponents
├── Button.tsx
├── Card.tsx
├── Typography.tsx
├── Icon.tsx
├── EmptyState.tsx
├── ProductCard.tsx
├── ProductImage.tsx
└── StatCard.tsx
- Pattern appears 3+ times across codebase
- UI element has consistent structure and behavior
- Component can be parameterized with props
- Reduces code duplication by 10+ lines per instance
- Props over Configuration: Use props for customization
- Composition over Monoliths: Small, focused components
- TypeScript First: Strong typing for all props
- Style Props: Allow style customization via props
- Accessibility: Include proper ARIA labels and roles
// Named imports from index
import { SearchBar, FormInput, ScreenHeader } from '../../components';
// Or individual imports
import SearchBar from '../../components/SearchBar';Before:
<View style={styles.searchBar}>
<Icon name="search" size={20} />
<TextInput
style={styles.searchInput}
placeholder="Search..."
value={searchQuery}
onChangeText={setSearchQuery}
/>
{searchQuery.length > 0 && (
<TouchableOpacity onPress={() => setSearchQuery('')}>
<Icon name="close" size={20} />
</TouchableOpacity>
)}
</View>After:
<SearchBar
value={searchQuery}
onChangeText={setSearchQuery}
placeholder="Search..."
/>Before:
<Modal visible={show} animationType="slide" transparent>
<View style={styles.modalOverlay}>
<TouchableOpacity style={StyleSheet.absoluteFill} onPress={() => setShow(false)} />
<View style={styles.modalContent}>
<View style={styles.modalHeader}>
<Typography variant="h3">Title</Typography>
<TouchableOpacity onPress={() => setShow(false)}>
<Icon name="close" size={24} />
</TouchableOpacity>
</View>
<ScrollView>{/* content */}</ScrollView>
</View>
</View>
</Modal>After:
<BottomSheetModal visible={show} onClose={() => setShow(false)} title="Title">
{/* content */}
</BottomSheetModal>Potential candidates for componentization:
- TabBar: Custom tab navigation component
- Dropdown: Reusable dropdown selector
- DatePicker: Consistent date selection
- ImagePicker: Standardized image upload
- LoadingState: Consistent loading indicators
- ErrorBoundary: Error handling wrapper
Each component should have:
- Unit tests for logic
- Snapshot tests for UI
- Integration tests for user interactions
- Accessibility tests
Example:
// FormInput.test.tsx
describe('FormInput', () => {
it('displays error message when error prop is provided', () => {
const { getByText } = render(
<FormInput label="Name" error="Required field" value="" onChangeText={() => {}} />
);
expect(getByText('Required field')).toBeTruthy();
});
});