UTA DevHub

Implementation Patterns

Advanced patterns for icon registry architecture, unified component design, automation workflows, and scalable icon system integration

Icon Implementation Patterns

Overview

This guide covers advanced implementation patterns for building a robust, scalable, and maintainable icon system. It focuses on architectural patterns, automation workflows, and integration strategies that scale from small teams to enterprise-level applications.

Prerequisites

To get the most out of this document, we recommend you first understand these key areas and review the related guides:

Registry Architecture Patterns

Why Use an Icon Registry Pattern?

The Icon Registry Pattern is a centralized approach to managing icon definitions, mappings, and configurations. Before diving into implementation, it's important to understand why this pattern is recommended over alternatives.

Problem Statement

Without a registry pattern, teams typically face these challenges:

// ❌ PROBLEM: Scattered icon usage throughout codebase
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import Ionicons from 'react-native-vector-icons/Ionicons';
 
// Inconsistent icon names across the app
<MaterialIcons name="home" size={24} />           // One screen
<Ionicons name="home-outline" size={24} />        // Another screen  
<MaterialIcons name="house" size={24} />          // Yet another screen
 
// Magic strings everywhere
<MaterialIcons name="settings" size={24} />       // No type safety
<MaterialIcons name="account_circle" size={24} /> // Typo goes unnoticed

Registry Pattern Benefits

BenefitDescriptionImpact
Type SafetyCompile-time validation of icon namesPrevents runtime errors from typos
ConsistencySingle source of truth for icon mappingsEnsures visual consistency
MaintainabilityCentralized changes affect entire appEasy to swap icon libraries
Semantic NamingBusiness-friendly names vs library-specificBetter code readability
Auto-completionIDE support for available iconsImproved developer experience
Refactoring SafetyChanges propagate automaticallyReduces update overhead

Comparison: Registry vs Direct Usage

// ❌ Direct library usage - Problems:
 
// 1. Scattered imports across many files
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import Ionicons from 'react-native-vector-icons/Ionicons';
 
// 2. Magic strings with no type safety
<MaterialIcons name="home" size={24} />
<MaterialIcons name="seting" size={24} /> // Typo!
 
// 3. Inconsistent icon choices
<MaterialIcons name="home" />          // Some screens
<Ionicons name="home-outline" />       // Other screens
<MaterialIcons name="house" />         // More screens
 
// 4. Platform differences ignored
<MaterialIcons name="arrow-back" />    // Wrong on iOS
 
// 5. Library changes require global find/replace
// Need to change ALL MaterialIcons to Ionicons? 
// Find/replace across 100+ files!

Registry Pattern Trade-offs

✅ Advantages:

  • (Do ✅) Complete type safety and autocomplete support
  • (Do ✅) Centralized icon management and consistency
  • (Do ✅) Easy library swapping and updates
  • (Do ✅) Semantic naming improves code readability
  • (Do ✅) Platform-specific logic handled once
  • (Do ✅) Testing and validation simplified

⚠️ Disadvantages:

  • (Consider 🤔) Additional abstraction layer adds complexity
  • (Consider 🤔) Initial setup overhead for registry configuration
  • (Consider 🤔) All icon names must be pre-defined (less flexibility)
  • (Be Aware ❗) Registry can become large with many icon types
  • (Be Aware ❗) Team must learn registry API instead of direct library usage

When to Use Registry Pattern

✅ Use Registry When:

  • Team size > 3 developers (consistency becomes critical)
  • App has > 50 icons (management overhead pays off)
  • Multiple icon sources (vector libs + custom + images)
  • Long-term maintenance expected (>1 year project)
  • Type safety is priority
  • Platform-specific icons needed

🤔 Consider Alternatives When:

  • Small prototype or MVP (< 20 icons)
  • Single developer project
  • One-time use application
  • Performance is absolutely critical (minimal abstraction needed)

Alternative Approaches Comparison

ApproachType SafetyConsistencyMaintainabilitySetup CostPerformance
Direct Library Usage❌ None❌ Manual❌ Scattered✅ Zero✅ Optimal
Constants File⚠️ Partial✅ Good⚠️ Moderate✅ Low✅ Good
Registry Pattern✅ Complete✅ Excellent✅ Excellent⚠️ Medium✅ Good
Icon Component Library✅ Complete✅ Excellent✅ Good❌ High⚠️ Depends

Co-located Registry Pattern

The co-located registry pattern keeps icon definitions close to the Icon component for better maintainability and type safety.

// ui/foundation/Icon/registry.ts
import { IconSet } from '@expo/vector-icons';
import { customIcons, CustomIconName } from '../icons/custom';
 
// Vector icon configurations
const vectorIconConfigs = {
  ionicons: {
    component: IconSet.Ionicons,
    prefix: '',
    map: {
      // Semantic name -> actual icon name mapping
      'home': 'home-outline',
      'search': 'search-outline', 
      'profile': 'person-outline',
      'settings': 'settings-outline',
      'menu': 'menu-outline',
      'close': 'close-outline',
      'back': 'arrow-back-outline',
      'forward': 'arrow-forward-outline',
      'edit': 'create-outline',
      'delete': 'trash-outline',
      'save': 'checkmark-outline',
    }
  },
  material: {
    component: IconSet.MaterialIcons,
    prefix: '',
    map: {
      'home': 'home',
      'search': 'search',
      'profile': 'person',
      'settings': 'settings',
      'notification': 'notifications',
      'favorite': 'favorite_border',
    }
  }
} as const;
 
// Centralized icon registry
export const IconRegistry = {
  vector: vectorIconConfigs,
  custom: customIcons,
  
  // Icon fonts (if using custom fonts)
  fonts: {
    // Custom icon font mappings would go here
  },
  
  // Image-based icons (for complex graphics)
  images: {
    // PNG/JPG icon mappings would go here  
  },
} as const;
 
// Type definitions for all icon sources
export type VectorLibrary = keyof typeof IconRegistry.vector;
export type VectorIconName = keyof typeof IconRegistry.vector.ionicons.map;
export type { CustomIconName };
export type ImageIconName = keyof typeof IconRegistry.images;
export type IconName = VectorIconName | CustomIconName | ImageIconName;
 
// Registry utilities
export const getVectorIcon = (library: VectorLibrary, name: VectorIconName) => {
  const config = IconRegistry.vector[library];
  return {
    component: config.component,
    name: config.map[name],
  };
};
 
export const getCustomIcon = (name: CustomIconName) => {
  return IconRegistry.custom[name];
};

Semantic Naming Strategy

Map semantic names to actual icon implementations for flexibility:

// ui/foundation/Icon/semanticRegistry.ts
export const SemanticIconMap = {
  // Navigation
  navigation: {
    'home': { library: 'vector', name: 'home' },
    'back': { library: 'vector', name: 'back' },
    'menu': { library: 'vector', name: 'menu' },
    'close': { library: 'vector', name: 'close' },
  },
  
  // Actions
  actions: {
    'edit': { library: 'vector', name: 'edit' },
    'delete': { library: 'vector', name: 'delete' },
    'save': { library: 'vector', name: 'save' },
    'share': { library: 'custom', name: 'share' }, // Brand-specific share icon
  },
  
  // Status & Social
  social: {
    'like': { library: 'custom', name: 'heart' }, // Custom heart animation
    'star': { library: 'vector', name: 'star' },
    'notification': { library: 'vector', name: 'notification' },
  },
  
  // Brand-specific
  brand: {
    'logo': { library: 'custom', name: 'logo' },
    'brand-action': { library: 'custom', name: 'brand-action' },
  }
} as const;
 
export type SemanticCategory = keyof typeof SemanticIconMap;
export type SemanticIconName<T extends SemanticCategory> = keyof typeof SemanticIconMap[T];

Unified Icon Component Patterns

Basic Unified Component

The foundation Icon component that handles all icon types:

// ui/foundation/Icon/Icon.tsx
import React from 'react';
import { View, Image, StyleSheet } from 'react-native';
import { IconRegistry, VectorLibrary, VectorIconName, CustomIconName, ImageIconName } from './registry';
 
export interface IconProps {
  name: VectorIconName | CustomIconName | ImageIconName;
  size?: number;
  color?: string;
  library?: 'vector' | 'custom' | 'images' | 'auto';
  vectorLibrary?: VectorLibrary;
  style?: any;
  testID?: string;
}
 
export const Icon: React.FC<IconProps> = ({
  name,
  size = 24,
  color = 'currentColor',
  library = 'auto',
  vectorLibrary = 'ionicons',
  style,
  testID,
  ...props
}) => {
  // Auto-detection logic
  const detectedLibrary = library === 'auto' 
    ? detectIconLibrary(name)
    : library;
 
  const renderIcon = () => {
    switch (detectedLibrary) {
      case 'vector':
        return renderVectorIcon(name as VectorIconName, vectorLibrary, size, color);
      
      case 'custom':
        return renderCustomIcon(name as CustomIconName, size, color);
      
      case 'images':
        return renderImageIcon(name as ImageIconName, size);
      
      default:
        console.warn(`Unknown icon library: ${detectedLibrary} for icon: ${name}`);
        return null;
    }
  };
 
  return (
    <View style={[{ width: size, height: size }, style]} testID={testID}>
      {renderIcon()}
    </View>
  );
};
 
// Helper functions
const detectIconLibrary = (name: string): 'vector' | 'custom' | 'images' => {
  if (name in IconRegistry.custom) return 'custom';
  if (name in IconRegistry.images) return 'images';
  return 'vector'; // Default fallback
};
 
const renderVectorIcon = (name: VectorIconName, library: VectorLibrary, size: number, color: string) => {
  const config = IconRegistry.vector[library];
  const IconComponent = config.component;
  const iconName = config.map[name];
  
  if (!iconName) {
    console.warn(`Icon "${name}" not found in ${library} library`);
    return null;
  }
  
  return <IconComponent name={iconName} size={size} color={color} />;
};
 
const renderCustomIcon = (name: CustomIconName, size: number, color: string) => {
  const IconComponent = IconRegistry.custom[name];
  
  if (!IconComponent) {
    console.warn(`Custom icon "${name}" not found`);
    return null;
  }
  
  return <IconComponent size={size} color={color} />;
};
 
const renderImageIcon = (name: ImageIconName, size: number) => {
  const imageSource = IconRegistry.images[name];
  
  if (!imageSource) {
    console.warn(`Image icon "${name}" not found`);
    return null;
  }
  
  return (
    <Image 
      source={imageSource} 
      style={{ width: size, height: size }}
      resizeMode="contain"
    />
  );
};

Enhanced Icon Component with Design Tokens

Integration with design system tokens for consistent theming:

// ui/foundation/Icon/ThemedIcon.tsx
import React from 'react';
import { useTheme } from '@/core/theme';
import { Icon, IconProps } from './Icon';
 
interface ThemedIconProps extends Omit<IconProps, 'size' | 'color'> {
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
  variant?: 'primary' | 'secondary' | 'accent' | 'muted' | 'danger' | 'success';
  disabled?: boolean;
}
 
export const ThemedIcon: React.FC<ThemedIconProps> = ({
  size = 'md',
  variant = 'primary',
  disabled = false,
  ...props
}) => {
  const theme = useTheme();
  
  // Size mapping from design tokens
  const sizeMap = {
    xs: theme.iconSizes.xs,    // 12
    sm: theme.iconSizes.sm,    // 16  
    md: theme.iconSizes.md,    // 24
    lg: theme.iconSizes.lg,    // 32
    xl: theme.iconSizes.xl,    // 48
  };
  
  // Color mapping from design tokens
  const colorMap = {
    primary: theme.colors.icon.primary,
    secondary: theme.colors.icon.secondary,
    accent: theme.colors.icon.accent,
    muted: theme.colors.icon.muted,
    danger: theme.colors.icon.danger,
    success: theme.colors.icon.success,
  };
  
  const resolvedSize = typeof size === 'number' ? size : sizeMap[size];
  const resolvedColor = disabled 
    ? theme.colors.icon.disabled 
    : colorMap[variant];
  
  return (
    <Icon
      {...props}
      size={resolvedSize}
      color={resolvedColor}
    />
  );
};

Semantic Icon Component

Component that uses semantic naming for better maintainability:

// ui/foundation/Icon/SemanticIcon.tsx
import React from 'react';
import { SemanticIconMap, SemanticCategory, SemanticIconName } from './semanticRegistry';
import { ThemedIcon, ThemedIconProps } from './ThemedIcon';
 
interface SemanticIconProps<T extends SemanticCategory> extends Omit<ThemedIconProps, 'name'> {
  category: T;
  name: SemanticIconName<T>;
}
 
export const SemanticIcon = <T extends SemanticCategory>({
  category,
  name,
  ...props
}: SemanticIconProps<T>) => {
  const iconConfig = SemanticIconMap[category][name];
  
  if (!iconConfig) {
    console.warn(`Semantic icon "${String(name)}" not found in category "${category}"`);
    return null;
  }
  
  return (
    <ThemedIcon
      {...props}
      name={iconConfig.name}
      library={iconConfig.library}
    />
  );
};
 
// Usage examples with full type safety
// <SemanticIcon category="navigation" name="home" size="md" variant="primary" />
// <SemanticIcon category="actions" name="edit" size="lg" variant="accent" />
// <SemanticIcon category="brand" name="logo" size="xl" />

Automation Workflow Patterns

Continuous Icon Processing

Automated workflow for processing designer exports:

// scripts/icon-automation.js
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const chokidar = require('chokidar');
 
class IconAutomation {
  constructor(config = {}) {
    this.config = {
      inputDir: 'assets/icons/custom-exports',
      optimizedDir: 'assets/icons/optimized', 
      outputDir: 'ui/foundation/icons/custom',
      registryPath: 'ui/foundation/Icon/registry.ts',
      watchMode: false,
      ...config
    };
  }
 
  async processIcons() {
    console.log('🎨 Starting icon processing pipeline...');
    
    try {
      await this.optimizeSVGs();
      await this.generateComponents();
      await this.updateRegistry();
      await this.runTests();
      
      console.log('✅ Icon processing completed successfully!');
    } catch (error) {
      console.error('❌ Icon processing failed:', error.message);
      process.exit(1);
    }
  }
 
  async optimizeSVGs() {
    console.log('📦 Optimizing SVGs...');
    
    const command = `npx svgo \
      --config svgo.config.js \
      --input ${this.config.inputDir} \
      --output ${this.config.optimizedDir} \
      --recursive`;
    
    execSync(command, { stdio: 'inherit' });
  }
 
  async generateComponents() {
    console.log('⚛️ Generating React components...');
    
    const command = `npx @svgr/cli \
      --config-file svgr.config.js \
      --out-dir ${this.config.outputDir} \
      --typescript \
      ${this.config.optimizedDir}`;
    
    execSync(command, { stdio: 'inherit' });
  }
 
  async updateRegistry() {
    console.log('📋 Updating icon registry...');
    
    const iconFiles = this.getIconFiles();
    const registryContent = this.generateRegistryContent(iconFiles);
    
    const indexPath = path.join(this.config.outputDir, 'index.ts');
    fs.writeFileSync(indexPath, registryContent);
    
    console.log(`📊 Updated registry with ${iconFiles.length} icons`);
  }
 
  getIconFiles() {
    const files = fs.readdirSync(this.config.outputDir)
      .filter(file => file.endsWith('.tsx') && file !== 'index.tsx')
      .map(file => file.replace('.tsx', ''));
    
    return files;
  }
 
  generateRegistryContent(iconFiles) {
    const exports = iconFiles
      .map(name => `export { default as ${name} } from './${name}';`)
      .join('\n');
 
    const iconNames = iconFiles
      .map(name => name.replace('Icon', '').toLowerCase())
      .map(name => `'${name}'`)
      .join(' | ');
 
    const iconMapping = iconFiles.map(name => {
      const iconName = name.replace('Icon', '').toLowerCase();
      return `  '${iconName}': ${name},`;
    }).join('\n');
 
    return `// Auto-generated file - do not edit manually
// Generated at: ${new Date().toISOString()}
// Total icons: ${iconFiles.length}
 
${exports}
 
// Type definition for custom icon names
export type CustomIconName = ${iconNames};
 
// Icon component mapping for registry
export const customIcons = {
${iconMapping}
} as const;
 
// Icon metadata
export const iconMetadata = {
  count: ${iconFiles.length},
  lastUpdated: '${new Date().toISOString()}',
  icons: [${iconFiles.map(name => `'${name}'`).join(', ')}],
} as const;
`;
  }
 
  async runTests() {
    console.log('🧪 Running icon tests...');
    
    try {
      execSync('npm run test -- --testPathPattern=icon', { stdio: 'inherit' });
    } catch (error) {
      console.warn('⚠️ Some tests failed, but continuing...');
    }
  }
 
  startWatcher() {
    console.log(`👁️ Watching ${this.config.inputDir} for changes...`);
    
    const watcher = chokidar.watch(`${this.config.inputDir}/**/*.svg`);
    
    let timeout;
    const processWithDebounce = () => {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        this.processIcons();
      }, 1000); // 1 second debounce
    };
    
    watcher
      .on('add', processWithDebounce)
      .on('change', processWithDebounce)
      .on('unlink', processWithDebounce);
  }
}
 
// CLI usage
if (require.main === module) {
  const automation = new IconAutomation();
  
  const args = process.argv.slice(2);
  const watchMode = args.includes('--watch');
  
  if (watchMode) {
    automation.startWatcher();
  } else {
    automation.processIcons();
  }
}
 
module.exports = IconAutomation;

Pre-commit Hook Integration

Ensure icon consistency with Git hooks:

#!/bin/sh
# .husky/pre-commit
 
echo "🔍 Checking for icon changes..."
 
# Check if any SVG files changed
if git diff --cached --name-only | grep -E "\.(svg)$" > /dev/null; then
  echo "📦 SVG files changed, processing icons..."
  
  # Process icons
  npm run icons:process
  
  # Stage the generated files
  git add ui/foundation/icons/custom/
  git add assets/icons/optimized/
  
  echo "✅ Icons processed and staged"
fi
 
# Run standard linting and tests
npm run lint:staged
npm run test:icons

Package.json Scripts Integration

Complete script integration for icon workflows:

{
  "scripts": {
    "icons:process": "node scripts/icon-automation.js",
    "icons:watch": "node scripts/icon-automation.js --watch",
    "icons:optimize": "npx svgo --config svgo.config.js --input assets/icons/custom-exports --output assets/icons/optimized --recursive",
    "icons:generate": "npx @svgr/cli --config-file svgr.config.js --out-dir ui/foundation/icons/custom --typescript assets/icons/optimized",
    "icons:registry": "node scripts/generate-icon-registry.js",
    "icons:clean": "rm -rf ui/foundation/icons/custom/*.tsx && rm -rf assets/icons/optimized/*",
    "icons:validate": "node scripts/validate-icons.js",
    "test:icons": "jest --testPathPattern=icon",
    "lint:icons": "eslint ui/foundation/icons/custom/ --ext .tsx"
  }
}

Advanced Integration Patterns

Theme Integration Pattern

Deep integration with design system theming:

// ui/foundation/Icon/ThemeIntegration.tsx
import React, { createContext, useContext } from 'react';
import { IconProps } from './Icon';
 
interface IconTheme {
  sizes: Record<string, number>;
  colors: Record<string, string>;
  variants: Record<string, Partial<IconProps>>;
}
 
const IconThemeContext = createContext<IconTheme | null>(null);
 
export const IconThemeProvider: React.FC<{ theme: IconTheme; children: React.ReactNode }> = ({
  theme,
  children
}) => (
  <IconThemeContext.Provider value={theme}>
    {children}
  </IconThemeContext.Provider>
);
 
export const useIconTheme = () => {
  const theme = useContext(IconThemeContext);
  if (!theme) {
    throw new Error('useIconTheme must be used within IconThemeProvider');
  }
  return theme;
};
 
// Enhanced themed icon with context
export const ContextThemedIcon: React.FC<IconProps & {
  themeSize?: string;
  themeColor?: string;
  themeVariant?: string;
}> = ({
  themeSize,
  themeColor,
  themeVariant,
  size,
  color,
  ...props
}) => {
  const theme = useIconTheme();
  
  const resolvedSize = size ?? (themeSize ? theme.sizes[themeSize] : undefined);
  const resolvedColor = color ?? (themeColor ? theme.colors[themeColor] : undefined);
  
  const variantProps = themeVariant ? theme.variants[themeVariant] : {};
  
  return (
    <Icon
      {...variantProps}
      {...props}
      size={resolvedSize}
      color={resolvedColor}
    />
  );
};

Performance Optimization Pattern

Lazy loading and caching strategies:

// ui/foundation/Icon/LazyIcon.tsx
import React, { useState, useEffect, useMemo } from 'react';
import { IconProps } from './Icon';
 
interface LazyIconProps extends IconProps {
  fallback?: React.ReactNode;
  timeout?: number;
}
 
// Icon cache for performance
const iconCache = new Map<string, React.ComponentType>();
 
export const LazyIcon: React.FC<LazyIconProps> = ({
  name,
  library = 'auto',
  fallback = null,
  timeout = 5000,
  ...props
}) => {
  const [IconComponent, setIconComponent] = useState<React.ComponentType | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  
  const cacheKey = `${library}-${name}`;
  
  useEffect(() => {
    let isMounted = true;
    
    // Check cache first
    if (iconCache.has(cacheKey)) {
      setIconComponent(() => iconCache.get(cacheKey)!);
      setLoading(false);
      return;
    }
    
    const loadIcon = async () => {
      try {
        setLoading(true);
        setError(null);
        
        let Component: React.ComponentType;
        
        if (library === 'custom') {
          const iconModule = await import(`../icons/custom/${name}Icon`);
          Component = iconModule.default;
        } else {
          // Handle vector icons or other types
          const { Icon } = await import('./Icon');
          Component = (componentProps: any) => <Icon {...componentProps} name={name} library={library} />;
        }
        
        if (isMounted) {
          iconCache.set(cacheKey, Component);
          setIconComponent(() => Component);
          setLoading(false);
        }
      } catch (err) {
        if (isMounted) {
          setError(`Failed to load icon: ${name}`);
          setLoading(false);
          console.warn(`LazyIcon: ${err}`);
        }
      }
    };
    
    // Add timeout
    const timeoutId = setTimeout(() => {
      if (isMounted && loading) {
        setError(`Timeout loading icon: ${name}`);
        setLoading(false);
      }
    }, timeout);
    
    loadIcon();
    
    return () => {
      isMounted = false;
      clearTimeout(timeoutId);
    };
  }, [cacheKey, name, library, timeout, loading]);
  
  if (loading) {
    return fallback;
  }
  
  if (error || !IconComponent) {
    console.warn(`LazyIcon error: ${error}`);
    return fallback;
  }
  
  return <IconComponent {...props} />;
};
 
// Preload utility for critical icons
export const preloadIcons = async (icons: Array<{name: string, library?: string}>) => {
  const promises = icons.map(async ({ name, library = 'custom' }) => {
    const cacheKey = `${library}-${name}`;
    
    if (iconCache.has(cacheKey)) {
      return; // Already cached
    }
    
    try {
      if (library === 'custom') {
        const iconModule = await import(`../icons/custom/${name}Icon`);
        iconCache.set(cacheKey, iconModule.default);
      }
    } catch (error) {
      console.warn(`Failed to preload icon ${name}:`, error);
    }
  });
  
  await Promise.allSettled(promises);
};

Analytics Integration Pattern

Track icon usage for optimization insights:

// ui/foundation/Icon/Analytics.tsx
import React from 'react';
import { Icon, IconProps } from './Icon';
 
interface AnalyticsIconProps extends IconProps {
  trackUsage?: boolean;
  category?: string;
}
 
class IconAnalytics {
  private static instance: IconAnalytics;
  private usageMap = new Map<string, number>();
  private lastReported = Date.now();
  
  static getInstance(): IconAnalytics {
    if (!IconAnalytics.instance) {
      IconAnalytics.instance = new IconAnalytics();
    }
    return IconAnalytics.instance;
  }
  
  trackIconUsage(name: string, library: string, category?: string) {
    const key = `${library}:${name}`;
    const currentCount = this.usageMap.get(key) || 0;
    this.usageMap.set(key, currentCount + 1);
    
    // Report every 10 minutes
    if (Date.now() - this.lastReported > 10 * 60 * 1000) {
      this.reportUsage();
      this.lastReported = Date.now();
    }
  }
  
  private reportUsage() {
    const usageData = Array.from(this.usageMap.entries())
      .map(([key, count]) => ({ icon: key, usage: count }))
      .sort((a, b) => b.usage - a.usage);
    
    // Send to analytics service
    console.log('Icon Usage Analytics:', usageData);
    
    // You could send this to your analytics service:
    // analyticsService.track('icon_usage', { icons: usageData });
  }
  
  getUsageReport() {
    return new Map(this.usageMap);
  }
  
  resetUsage() {
    this.usageMap.clear();
  }
}
 
export const AnalyticsIcon: React.FC<AnalyticsIconProps> = ({
  trackUsage = true,
  category,
  name,
  library = 'auto',
  ...props
}) => {
  React.useEffect(() => {
    if (trackUsage) {
      const analytics = IconAnalytics.getInstance();
      analytics.trackIconUsage(name, library || 'auto', category);
    }
  }, [name, library, trackUsage, category]);
  
  return <Icon {...props} name={name} library={library} />;
};
 
// Usage analytics hook
export const useIconAnalytics = () => {
  const analytics = IconAnalytics.getInstance();
  
  return {
    getUsageReport: () => analytics.getUsageReport(),
    resetUsage: () => analytics.resetUsage(),
  };
};

Testing Patterns

Component Testing Strategy

Comprehensive testing for icon components:

// __tests__/Icon.patterns.test.ts
import React from 'react';
import { render, waitFor } from '@testing-library/react-native';
import { Icon } from '../Icon';
import { LazyIcon } from '../LazyIcon';
import { ThemedIcon } from '../ThemedIcon';
import { SemanticIcon } from '../SemanticIcon';
 
// Mock the icon registry
jest.mock('../registry', () => ({
  IconRegistry: {
    vector: {
      ionicons: {
        component: jest.fn(() => 'MockedVectorIcon'),
        map: { home: 'home-outline', search: 'search-outline' }
      }
    },
    custom: {
      'custom-icon': jest.fn(() => 'MockedCustomIcon')
    }
  }
}));
 
describe('Icon Implementation Patterns', () => {
  describe('Basic Icon Component', () => {
    it('renders vector icons correctly', () => {
      const { getByTestId } = render(
        <Icon name="home" library="vector" testID="vector-icon" />
      );
      
      expect(getByTestId('vector-icon')).toBeTruthy();
    });
    
    it('renders custom icons correctly', () => {
      const { getByTestId } = render(
        <Icon name="custom-icon" library="custom" testID="custom-icon" />
      );
      
      expect(getByTestId('custom-icon')).toBeTruthy();
    });
    
    it('auto-detects icon library', () => {
      const { getByTestId } = render(
        <Icon name="custom-icon" library="auto" testID="auto-icon" />
      );
      
      expect(getByTestId('auto-icon')).toBeTruthy();
    });
  });
  
  describe('Lazy Icon Component', () => {
    it('shows fallback while loading', async () => {
      const fallback = <div testID="fallback">Loading...</div>;
      
      const { getByTestId, queryByTestId } = render(
        <LazyIcon name="custom-icon" fallback={fallback} testID="lazy-icon" />
      );
      
      expect(getByTestId('fallback')).toBeTruthy();
      
      await waitFor(() => {
        expect(queryByTestId('fallback')).toBeNull();
      });
    });
    
    it('handles loading errors gracefully', async () => {
      const fallback = <div testID="error-fallback">Error</div>;
      
      const { getByTestId } = render(
        <LazyIcon name="non-existent" fallback={fallback} timeout={100} />
      );
      
      await waitFor(() => {
        expect(getByTestId('error-fallback')).toBeTruthy();
      }, { timeout: 200 });
    });
  });
  
  describe('Themed Icon Component', () => {
    const mockTheme = {
      iconSizes: { sm: 16, md: 24, lg: 32 },
      colors: {
        icon: {
          primary: '#007AFF',
          secondary: '#666666',
          disabled: '#CCCCCC'
        }
      }
    };
    
    it('applies theme sizes correctly', () => {
      const { getByTestId } = render(
        <ThemedIcon name="home" size="lg" testID="themed-icon" />
      );
      
      const icon = getByTestId('themed-icon');
      expect(icon.props.style).toEqual(
        expect.arrayContaining([
          expect.objectContaining({ width: 32, height: 32 })
        ])
      );
    });
    
    it('applies disabled state correctly', () => {
      const { getByTestId } = render(
        <ThemedIcon name="home" disabled testID="disabled-icon" />
      );
      
      expect(getByTestId('disabled-icon')).toBeTruthy();
    });
  });
});

Registry Testing

Validate icon registry integrity:

// __tests__/registry.test.ts
import { IconRegistry } from '../registry';
import * as customIcons from '../icons/custom';
 
describe('Icon Registry', () => {
  it('has valid vector icon mappings', () => {
    Object.entries(IconRegistry.vector).forEach(([library, config]) => {
      expect(config.component).toBeDefined();
      expect(config.map).toBeDefined();
      expect(Object.keys(config.map).length).toBeGreaterThan(0);
    });
  });
  
  it('has matching custom icon exports', () => {
    const registryIcons = Object.keys(IconRegistry.custom);
    const exportedIcons = Object.keys(customIcons);
    
    expect(registryIcons.sort()).toEqual(exportedIcons.sort());
  });
  
  it('validates semantic mappings', () => {
    // Test that all semantic mappings point to valid icons
    // This would depend on your semantic registry implementation
  });
});

Deployment and Monitoring Patterns

Bundle Analysis

Monitor icon bundle impact:

// scripts/analyze-icon-bundle.js
const fs = require('fs');
const path = require('path');
 
class IconBundleAnalyzer {
  analyzeBundle() {
    const customIconsDir = 'ui/foundation/icons/custom';
    const stats = this.getIconStats(customIconsDir);
    
    console.log('📊 Icon Bundle Analysis');
    console.log('========================');
    console.log(`Total custom icons: ${stats.totalIcons}`);
    console.log(`Total size: ${this.formatBytes(stats.totalSize)}`);
    console.log(`Average size: ${this.formatBytes(stats.averageSize)}`);
    console.log(`Largest icon: ${stats.largestIcon.name} (${this.formatBytes(stats.largestIcon.size)})`);
    console.log(`Smallest icon: ${stats.smallestIcon.name} (${this.formatBytes(stats.smallestIcon.size)})`);
    
    if (stats.recommendationsCount > 0) {
      console.log(`\n💡 ${stats.recommendationsCount} optimization recommendations available`);
    }
  }
  
  getIconStats(dir) {
    const files = fs.readdirSync(dir)
      .filter(file => file.endsWith('.tsx'))
      .map(file => {
        const filePath = path.join(dir, file);
        const size = fs.statSync(filePath).size;
        return { name: file, size };
      });
    
    const totalSize = files.reduce((sum, file) => sum + file.size, 0);
    const largestIcon = files.reduce((max, file) => file.size > max.size ? file : max);
    const smallestIcon = files.reduce((min, file) => file.size < min.size ? file : min);
    
    return {
      totalIcons: files.length,
      totalSize,
      averageSize: totalSize / files.length,
      largestIcon,
      smallestIcon,
      recommendationsCount: files.filter(f => f.size > 10000).length // Files > 10KB
    };
  }
  
  formatBytes(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }
}
 
if (require.main === module) {
  const analyzer = new IconBundleAnalyzer();
  analyzer.analyzeBundle();
}