Claude Cursor Skill

react-native-design

Master React Native styling, navigation, and Reanimated animations for cross-platform mobile development. Use when building React Native apps, implementing navigation patterns, or creating performant animations.

LLM Mart · 0 points · 11 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wshobson-agents-plugins_ui-design_skills_react-native-design-554237f.zip · 18 KB
Part of wshobson/agents — 170 skills

Install

skills CLI npx skills add https://github.com/wshobson/agents/tree/main/plugins/ui-design/skills/react-native-design
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wshobson-agents@llmmart
Git git clone https://github.com/wshobson/agents.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wshobson/agents collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

React Native Design

Master React Native styling patterns, React Navigation, and Reanimated 3 to build performant, cross-platform mobile applications with native-quality user experiences.

When to Use This Skill

  • Building cross-platform mobile apps with React Native
  • Implementing navigation with React Navigation 6+
  • Creating performant animations with Reanimated 3
  • Styling components with StyleSheet and styled-components
  • Building responsive layouts for different screen sizes
  • Implementing platform-specific designs (iOS/Android)
  • Creating gesture-driven interactions with Gesture Handler
  • Optimizing React Native performance

Detailed section: Core Concepts

Originally a 6471-byte section in this SKILL.md. Moved to references/details.md to fit Codex's 8 KB skill body cap.

Quick Start Component

import React from 'react';
import {
  View,
  Text,
  StyleSheet,
  Pressable,
  Image,
} from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

interface ItemCardProps {
  title: string;
  subtitle: string;
  imageUrl: string;
  onPress: () => void;
}

const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

export function ItemCard({ title, subtitle, imageUrl, onPress }: ItemCardProps) {
  const scale = useSharedValue(1);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  return (
    <AnimatedPressable
      style={[styles.card, animatedStyle]}
      onPress={onPress}
      onPressIn={() => { scale.value = withSpring(0.97); }}
      onPressOut={() => { scale.value = withSpring(1); }}
    >
      <Image source={{ uri: imageUrl }} style={styles.image} />
      <View style={styles.content}>
        <Text style={styles.title} numberOfLines={1}>
          {title}
        </Text>
        <Text style={styles.subtitle} numberOfLines={2}>
          {subtitle}
        </Text>
      </View>
    </AnimatedPressable>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderRadius: 16,
    overflow: 'hidden',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 4,
  },
  image: {
    width: '100%',
    height: 160,
    backgroundColor: '#f3f4f6',
  },
  content: {
    padding: 16,
    gap: 4,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1f2937',
  },
  subtitle: {
    fontSize: 14,
    color: '#6b7280',
    lineHeight: 20,
  },
});

Best Practices

  1. Use TypeScript: Define navigation and prop types for type safety
  2. Memoize Components: Use React.memo and useCallback to prevent unnecessary rerenders
  3. Run Animations on UI Thread: Use Reanimated worklets for 60fps animations
  4. Avoid Inline Styles: Use StyleSheet.create for performance
  5. Handle Safe Areas: Use SafeAreaView or useSafeAreaInsets
  6. Test on Real Devices: Simulator/emulator performance differs from real devices
  7. Use FlatList for Lists: Never use ScrollView with map for long lists
  8. Platform-Specific Code: Use Platform.select for iOS/Android differences

Common Issues

  • Gesture Conflicts: Wrap gestures with GestureDetector and use simultaneousHandlers
  • Navigation Type Errors: Define ParamList types for all navigators
  • Animation Jank: Move animations to UI thread with runOnUI worklets
  • Memory Leaks: Cancel animations and cleanup in useEffect
  • Font Loading: Use expo-font or react-native-asset for custom fonts
  • Safe Area Issues: Test on notched devices (iPhone, Android with cutouts)
Files (agents)
  • references
    • details.md 6.4 KB
      # react-native-design — detailed sections
      
      ## Core Concepts
      
      ### 1. StyleSheet and Styling
      
      **Basic StyleSheet:**
      
      ```typescript
      import { StyleSheet, View, Text } from 'react-native';
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          padding: 16,
          backgroundColor: '#ffffff',
        },
        title: {
          fontSize: 24,
          fontWeight: '600',
          color: '#1a1a1a',
          marginBottom: 8,
        },
        subtitle: {
          fontSize: 16,
          color: '#666666',
          lineHeight: 24,
        },
      });
      
      function Card() {
        return (
          <View style={styles.container}>
            <Text style={styles.title}>Title</Text>
            <Text style={styles.subtitle}>Subtitle text</Text>
          </View>
        );
      }
      ```
      
      **Dynamic Styles:**
      
      ```typescript
      interface CardProps {
        variant: 'primary' | 'secondary';
        disabled?: boolean;
      }
      
      function Card({ variant, disabled }: CardProps) {
        return (
          <View style={[
            styles.card,
            variant === 'primary' ? styles.primary : styles.secondary,
            disabled && styles.disabled,
          ]}>
            <Text style={styles.text}>Content</Text>
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        card: {
          padding: 16,
          borderRadius: 12,
        },
        primary: {
          backgroundColor: '#6366f1',
        },
        secondary: {
          backgroundColor: '#f3f4f6',
          borderWidth: 1,
          borderColor: '#e5e7eb',
        },
        disabled: {
          opacity: 0.5,
        },
        text: {
          fontSize: 16,
        },
      });
      ```
      
      ### 2. Flexbox Layout
      
      **Row and Column Layouts:**
      
      ```typescript
      const styles = StyleSheet.create({
        // Vertical stack (column)
        column: {
          flexDirection: "column",
          gap: 12,
        },
        // Horizontal stack (row)
        row: {
          flexDirection: "row",
          alignItems: "center",
          gap: 8,
        },
        // Space between items
        spaceBetween: {
          flexDirection: "row",
          justifyContent: "space-between",
          alignItems: "center",
        },
        // Centered content
        centered: {
          flex: 1,
          justifyContent: "center",
          alignItems: "center",
        },
        // Fill remaining space
        fill: {
          flex: 1,
        },
      });
      ```
      
      ### 3. React Navigation Setup
      
      **Stack Navigator:**
      
      ```typescript
      import { NavigationContainer } from '@react-navigation/native';
      import { createNativeStackNavigator } from '@react-navigation/native-stack';
      
      type RootStackParamList = {
        Home: undefined;
        Detail: { itemId: string };
        Settings: undefined;
      };
      
      const Stack = createNativeStackNavigator<RootStackParamList>();
      
      function AppNavigator() {
        return (
          <NavigationContainer>
            <Stack.Navigator
              initialRouteName="Home"
              screenOptions={{
                headerStyle: { backgroundColor: '#6366f1' },
                headerTintColor: '#ffffff',
                headerTitleStyle: { fontWeight: '600' },
              }}
            >
              <Stack.Screen
                name="Home"
                component={HomeScreen}
                options={{ title: 'Home' }}
              />
              <Stack.Screen
                name="Detail"
                component={DetailScreen}
                options={({ route }) => ({
                  title: `Item ${route.params.itemId}`,
                })}
              />
              <Stack.Screen name="Settings" component={SettingsScreen} />
            </Stack.Navigator>
          </NavigationContainer>
        );
      }
      ```
      
      **Tab Navigator:**
      
      ```typescript
      import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
      import { Ionicons } from '@expo/vector-icons';
      
      type TabParamList = {
        Home: undefined;
        Search: undefined;
        Profile: undefined;
      };
      
      const Tab = createBottomTabNavigator<TabParamList>();
      
      function TabNavigator() {
        return (
          <Tab.Navigator
            screenOptions={({ route }) => ({
              tabBarIcon: ({ focused, color, size }) => {
                const icons: Record<string, keyof typeof Ionicons.glyphMap> = {
                  Home: focused ? 'home' : 'home-outline',
                  Search: focused ? 'search' : 'search-outline',
                  Profile: focused ? 'person' : 'person-outline',
                };
                return <Ionicons name={icons[route.name]} size={size} color={color} />;
              },
              tabBarActiveTintColor: '#6366f1',
              tabBarInactiveTintColor: '#9ca3af',
            })}
          >
            <Tab.Screen name="Home" component={HomeScreen} />
            <Tab.Screen name="Search" component={SearchScreen} />
            <Tab.Screen name="Profile" component={ProfileScreen} />
          </Tab.Navigator>
        );
      }
      ```
      
      ### 4. Reanimated 3 Basics
      
      **Animated Values:**
      
      ```typescript
      import Animated, {
        useSharedValue,
        useAnimatedStyle,
        withSpring,
        withTiming,
      } from 'react-native-reanimated';
      
      function AnimatedBox() {
        const scale = useSharedValue(1);
        const opacity = useSharedValue(1);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ scale: scale.value }],
          opacity: opacity.value,
        }));
      
        const handlePress = () => {
          scale.value = withSpring(1.2, {}, () => {
            scale.value = withSpring(1);
          });
        };
      
        return (
          <Pressable onPress={handlePress}>
            <Animated.View style={[styles.box, animatedStyle]} />
          </Pressable>
        );
      }
      ```
      
      **Gesture Handler Integration:**
      
      ```typescript
      import { Gesture, GestureDetector } from 'react-native-gesture-handler';
      import Animated, {
        useSharedValue,
        useAnimatedStyle,
        withSpring,
      } from 'react-native-reanimated';
      
      function DraggableCard() {
        const translateX = useSharedValue(0);
        const translateY = useSharedValue(0);
      
        const gesture = Gesture.Pan()
          .onUpdate((event) => {
            translateX.value = event.translationX;
            translateY.value = event.translationY;
          })
          .onEnd(() => {
            translateX.value = withSpring(0);
            translateY.value = withSpring(0);
          });
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [
            { translateX: translateX.value },
            { translateY: translateY.value },
          ],
        }));
      
        return (
          <GestureDetector gesture={gesture}>
            <Animated.View style={[styles.card, animatedStyle]}>
              <Text>Drag me!</Text>
            </Animated.View>
          </GestureDetector>
        );
      }
      ```
      
      ### 5. Platform-Specific Styling
      
      ```typescript
      import { Platform, StyleSheet } from "react-native";
      
      const styles = StyleSheet.create({
        container: {
          padding: 16,
          ...Platform.select({
            ios: {
              shadowColor: "#000",
              shadowOffset: { width: 0, height: 2 },
              shadowOpacity: 0.1,
              shadowRadius: 4,
            },
            android: {
              elevation: 4,
            },
          }),
        },
        text: {
          fontFamily: Platform.OS === "ios" ? "SF Pro Text" : "Roboto",
          fontSize: 16,
        },
      });
      
      // Platform-specific components
      import { Platform } from "react-native";
      const StatusBarHeight = Platform.OS === "ios" ? 44 : 0;
      ```
      
    • navigation-patterns.md 19.7 KB
      # React Navigation Patterns
      
      ## Setup and Configuration
      
      ### Installation
      
      ```bash
      # Core packages
      npm install @react-navigation/native
      npm install @react-navigation/native-stack
      npm install @react-navigation/bottom-tabs
      
      # Required peer dependencies
      npm install react-native-screens react-native-safe-area-context
      ```
      
      ### Type-Safe Navigation Setup
      
      ```typescript
      // navigation/types.ts
      import { NativeStackScreenProps } from "@react-navigation/native-stack";
      import { BottomTabScreenProps } from "@react-navigation/bottom-tabs";
      import {
        CompositeScreenProps,
        NavigatorScreenParams,
      } from "@react-navigation/native";
      
      // Define param lists for each navigator
      export type RootStackParamList = {
        Main: NavigatorScreenParams<MainTabParamList>;
        Modal: { title: string };
        Auth: NavigatorScreenParams<AuthStackParamList>;
      };
      
      export type MainTabParamList = {
        Home: undefined;
        Search: { query?: string };
        Profile: { userId: string };
      };
      
      export type AuthStackParamList = {
        Login: undefined;
        Register: undefined;
        ForgotPassword: { email?: string };
      };
      
      // Screen props helpers
      export type RootStackScreenProps<T extends keyof RootStackParamList> =
        NativeStackScreenProps<RootStackParamList, T>;
      
      export type MainTabScreenProps<T extends keyof MainTabParamList> =
        CompositeScreenProps<
          BottomTabScreenProps<MainTabParamList, T>,
          RootStackScreenProps<keyof RootStackParamList>
        >;
      
      // Global type declaration
      declare global {
        namespace ReactNavigation {
          interface RootParamList extends RootStackParamList {}
        }
      }
      ```
      
      ### Navigation Hooks
      
      ```typescript
      // hooks/useAppNavigation.ts
      import { useNavigation, useRoute, RouteProp } from "@react-navigation/native";
      import { NativeStackNavigationProp } from "@react-navigation/native-stack";
      import { RootStackParamList } from "./types";
      
      export function useAppNavigation() {
        return useNavigation<NativeStackNavigationProp<RootStackParamList>>();
      }
      
      export function useTypedRoute<T extends keyof RootStackParamList>() {
        return useRoute<RouteProp<RootStackParamList, T>>();
      }
      ```
      
      ## Stack Navigation
      
      ### Basic Stack Navigator
      
      ```typescript
      import { createNativeStackNavigator } from '@react-navigation/native-stack';
      import { RootStackParamList } from './types';
      
      const Stack = createNativeStackNavigator<RootStackParamList>();
      
      function RootNavigator() {
        return (
          <Stack.Navigator
            initialRouteName="Main"
            screenOptions={{
              headerStyle: { backgroundColor: '#6366f1' },
              headerTintColor: '#ffffff',
              headerTitleStyle: { fontWeight: '600' },
              headerBackTitleVisible: false,
              animation: 'slide_from_right',
            }}
          >
            <Stack.Screen
              name="Main"
              component={MainTabNavigator}
              options={{ headerShown: false }}
            />
            <Stack.Screen
              name="Modal"
              component={ModalScreen}
              options={{
                presentation: 'modal',
                animation: 'slide_from_bottom',
              }}
            />
            <Stack.Group screenOptions={{ presentation: 'fullScreenModal' }}>
              <Stack.Screen
                name="Auth"
                component={AuthNavigator}
                options={{ headerShown: false }}
              />
            </Stack.Group>
          </Stack.Navigator>
        );
      }
      ```
      
      ### Screen with Dynamic Options
      
      ```typescript
      function DetailScreen({ route, navigation }: DetailScreenProps) {
        const { itemId } = route.params;
        const [item, setItem] = useState<Item | null>(null);
      
        useEffect(() => {
          // Update header when data loads
          if (item) {
            navigation.setOptions({
              title: item.title,
              headerRight: () => (
                <TouchableOpacity onPress={() => shareItem(item)}>
                  <Ionicons name="share-outline" size={24} color="#ffffff" />
                </TouchableOpacity>
              ),
            });
          }
        }, [item, navigation]);
      
        // Prevent going back with unsaved changes
        useEffect(() => {
          const unsubscribe = navigation.addListener('beforeRemove', (e) => {
            if (!hasUnsavedChanges) return;
      
            e.preventDefault();
            Alert.alert(
              'Discard changes?',
              'You have unsaved changes. Are you sure you want to leave?',
              [
                { text: "Don't leave", style: 'cancel' },
                {
                  text: 'Discard',
                  style: 'destructive',
                  onPress: () => navigation.dispatch(e.data.action),
                },
              ]
            );
          });
      
          return unsubscribe;
        }, [navigation, hasUnsavedChanges]);
      
        return <View>{/* Content */}</View>;
      }
      ```
      
      ## Tab Navigation
      
      ### Bottom Tab Navigator
      
      ```typescript
      import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
      import { MainTabParamList } from './types';
      import { Ionicons } from '@expo/vector-icons';
      
      const Tab = createBottomTabNavigator<MainTabParamList>();
      
      function MainTabNavigator() {
        return (
          <Tab.Navigator
            screenOptions={({ route }) => ({
              tabBarIcon: ({ focused, color, size }) => {
                const icons: Record<keyof MainTabParamList, string> = {
                  Home: focused ? 'home' : 'home-outline',
                  Search: focused ? 'search' : 'search-outline',
                  Profile: focused ? 'person' : 'person-outline',
                };
                return (
                  <Ionicons
                    name={icons[route.name] as any}
                    size={size}
                    color={color}
                  />
                );
              },
              tabBarActiveTintColor: '#6366f1',
              tabBarInactiveTintColor: '#9ca3af',
              tabBarStyle: {
                backgroundColor: '#ffffff',
                borderTopWidth: 1,
                borderTopColor: '#e5e7eb',
                paddingBottom: 8,
                paddingTop: 8,
                height: 60,
              },
              tabBarLabelStyle: {
                fontSize: 12,
                fontWeight: '500',
              },
              headerStyle: { backgroundColor: '#ffffff' },
              headerTitleStyle: { fontWeight: '600' },
            })}
          >
            <Tab.Screen
              name="Home"
              component={HomeScreen}
              options={{
                tabBarLabel: 'Home',
                tabBarBadge: 3,
              }}
            />
            <Tab.Screen
              name="Search"
              component={SearchScreen}
              options={{ tabBarLabel: 'Search' }}
            />
            <Tab.Screen
              name="Profile"
              component={ProfileScreen}
              options={{ tabBarLabel: 'Profile' }}
            />
          </Tab.Navigator>
        );
      }
      ```
      
      ### Custom Tab Bar
      
      ```typescript
      import { View, Pressable, StyleSheet } from 'react-native';
      import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
      import Animated, {
        useSharedValue,
        useAnimatedStyle,
        withSpring,
      } from 'react-native-reanimated';
      
      function CustomTabBar({ state, descriptors, navigation }: BottomTabBarProps) {
        return (
          <View style={styles.tabBar}>
            {state.routes.map((route, index) => {
              const { options } = descriptors[route.key];
              const label = options.tabBarLabel ?? route.name;
              const isFocused = state.index === index;
      
              const onPress = () => {
                const event = navigation.emit({
                  type: 'tabPress',
                  target: route.key,
                  canPreventDefault: true,
                });
      
                if (!isFocused && !event.defaultPrevented) {
                  navigation.navigate(route.name);
                }
              };
      
              return (
                <TabBarButton
                  key={route.key}
                  label={label as string}
                  isFocused={isFocused}
                  onPress={onPress}
                  icon={options.tabBarIcon}
                />
              );
            })}
          </View>
        );
      }
      
      function TabBarButton({ label, isFocused, onPress, icon }) {
        const scale = useSharedValue(1);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ scale: scale.value }],
        }));
      
        return (
          <Pressable
            onPress={onPress}
            onPressIn={() => { scale.value = withSpring(0.9); }}
            onPressOut={() => { scale.value = withSpring(1); }}
            style={styles.tabButton}
          >
            <Animated.View style={animatedStyle}>
              {icon?.({
                focused: isFocused,
                color: isFocused ? '#6366f1' : '#9ca3af',
                size: 24,
              })}
              <Text
                style={[
                  styles.tabLabel,
                  { color: isFocused ? '#6366f1' : '#9ca3af' },
                ]}
              >
                {label}
              </Text>
            </Animated.View>
          </Pressable>
        );
      }
      
      const styles = StyleSheet.create({
        tabBar: {
          flexDirection: 'row',
          backgroundColor: '#ffffff',
          paddingBottom: 20,
          paddingTop: 12,
          borderTopWidth: 1,
          borderTopColor: '#e5e7eb',
        },
        tabButton: {
          flex: 1,
          alignItems: 'center',
        },
        tabLabel: {
          fontSize: 12,
          marginTop: 4,
          fontWeight: '500',
        },
      });
      
      // Usage
      <Tab.Navigator tabBar={(props) => <CustomTabBar {...props} />}>
        {/* screens */}
      </Tab.Navigator>
      ```
      
      ## Drawer Navigation
      
      ```typescript
      import {
        createDrawerNavigator,
        DrawerContentScrollView,
        DrawerItemList,
        DrawerContentComponentProps,
      } from '@react-navigation/drawer';
      
      const Drawer = createDrawerNavigator();
      
      function CustomDrawerContent(props: DrawerContentComponentProps) {
        return (
          <DrawerContentScrollView {...props}>
            <View style={styles.drawerHeader}>
              <Image source={{ uri: user.avatar }} style={styles.avatar} />
              <Text style={styles.userName}>{user.name}</Text>
              <Text style={styles.userEmail}>{user.email}</Text>
            </View>
            <DrawerItemList {...props} />
            <View style={styles.drawerFooter}>
              <TouchableOpacity
                onPress={handleLogout}
                style={styles.logoutButton}
              >
                <Ionicons name="log-out-outline" size={24} color="#ef4444" />
                <Text style={styles.logoutText}>Log Out</Text>
              </TouchableOpacity>
            </View>
          </DrawerContentScrollView>
        );
      }
      
      function DrawerNavigator() {
        return (
          <Drawer.Navigator
            drawerContent={(props) => <CustomDrawerContent {...props} />}
            screenOptions={{
              drawerActiveBackgroundColor: '#ede9fe',
              drawerActiveTintColor: '#6366f1',
              drawerInactiveTintColor: '#4b5563',
              drawerLabelStyle: { marginLeft: -20, fontSize: 15, fontWeight: '500' },
              drawerStyle: { width: 280 },
            }}
          >
            <Drawer.Screen
              name="Home"
              component={HomeScreen}
              options={{
                drawerIcon: ({ color }) => (
                  <Ionicons name="home-outline" size={22} color={color} />
                ),
              }}
            />
            <Drawer.Screen
              name="Settings"
              component={SettingsScreen}
              options={{
                drawerIcon: ({ color }) => (
                  <Ionicons name="settings-outline" size={22} color={color} />
                ),
              }}
            />
          </Drawer.Navigator>
        );
      }
      ```
      
      ## Deep Linking
      
      ### Configuration
      
      ```typescript
      // navigation/linking.ts
      import { LinkingOptions } from '@react-navigation/native';
      import { RootStackParamList } from './types';
      
      export const linking: LinkingOptions<RootStackParamList> = {
        prefixes: ['myapp://', 'https://myapp.com'],
        config: {
          screens: {
            Main: {
              screens: {
                Home: 'home',
                Search: 'search',
                Profile: 'profile/:userId',
              },
            },
            Modal: 'modal/:title',
            Auth: {
              screens: {
                Login: 'login',
                Register: 'register',
                ForgotPassword: 'forgot-password',
              },
            },
          },
        },
        // Custom URL parsing
        getStateFromPath: (path, config) => {
          // Handle custom URL patterns
          return getStateFromPath(path, config);
        },
      };
      
      // App.tsx
      function App() {
        return (
          <NavigationContainer linking={linking} fallback={<LoadingScreen />}>
            <RootNavigator />
          </NavigationContainer>
        );
      }
      ```
      
      ### Handling Deep Links
      
      ```typescript
      import { useEffect } from "react";
      import { Linking } from "react-native";
      import { useNavigation } from "@react-navigation/native";
      
      function useDeepLinkHandler() {
        const navigation = useNavigation();
      
        useEffect(() => {
          // Handle initial URL
          const handleInitialUrl = async () => {
            const url = await Linking.getInitialURL();
            if (url) {
              handleDeepLink(url);
            }
          };
      
          // Handle URL changes
          const subscription = Linking.addEventListener("url", ({ url }) => {
            handleDeepLink(url);
          });
      
          handleInitialUrl();
      
          return () => subscription.remove();
        }, []);
      
        const handleDeepLink = (url: string) => {
          // Parse URL and navigate
          const route = parseUrl(url);
          if (route) {
            navigation.navigate(route.name, route.params);
          }
        };
      }
      ```
      
      ## Navigation State Management
      
      ### Auth Flow
      
      ```typescript
      import { createContext, useContext, useState, useEffect } from 'react';
      
      interface AuthContextType {
        user: User | null;
        isLoading: boolean;
        signIn: (email: string, password: string) => Promise<void>;
        signOut: () => Promise<void>;
      }
      
      const AuthContext = createContext<AuthContextType>(null!);
      
      function AuthProvider({ children }: { children: React.ReactNode }) {
        const [user, setUser] = useState<User | null>(null);
        const [isLoading, setIsLoading] = useState(true);
      
        useEffect(() => {
          // Check for existing session
          checkAuthState();
        }, []);
      
        const checkAuthState = async () => {
          try {
            const token = await AsyncStorage.getItem('token');
            if (token) {
              const user = await fetchUser(token);
              setUser(user);
            }
          } catch (error) {
            console.error(error);
          } finally {
            setIsLoading(false);
          }
        };
      
        const signIn = async (email: string, password: string) => {
          const { user, token } = await loginApi(email, password);
          await AsyncStorage.setItem('token', token);
          setUser(user);
        };
      
        const signOut = async () => {
          await AsyncStorage.removeItem('token');
          setUser(null);
        };
      
        return (
          <AuthContext.Provider value={{ user, isLoading, signIn, signOut }}>
            {children}
          </AuthContext.Provider>
        );
      }
      
      function RootNavigator() {
        const { user, isLoading } = useAuth();
      
        if (isLoading) {
          return <SplashScreen />;
        }
      
        return (
          <Stack.Navigator screenOptions={{ headerShown: false }}>
            {user ? (
              <Stack.Screen name="Main" component={MainNavigator} />
            ) : (
              <Stack.Screen name="Auth" component={AuthNavigator} />
            )}
          </Stack.Navigator>
        );
      }
      ```
      
      ### Navigation State Persistence
      
      ```typescript
      import AsyncStorage from '@react-native-async-storage/async-storage';
      import { NavigationContainer, NavigationState } from '@react-navigation/native';
      
      const PERSISTENCE_KEY = 'NAVIGATION_STATE';
      
      function App() {
        const [isReady, setIsReady] = useState(false);
        const [initialState, setInitialState] = useState<NavigationState | undefined>();
      
        useEffect(() => {
          const restoreState = async () => {
            try {
              const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY);
              if (savedState) {
                setInitialState(JSON.parse(savedState));
              }
            } catch (e) {
              console.error('Failed to restore navigation state:', e);
            } finally {
              setIsReady(true);
            }
          };
      
          if (!isReady) {
            restoreState();
          }
        }, [isReady]);
      
        if (!isReady) {
          return <SplashScreen />;
        }
      
        return (
          <NavigationContainer
            initialState={initialState}
            onStateChange={(state) => {
              AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
            }}
          >
            <RootNavigator />
          </NavigationContainer>
        );
      }
      ```
      
      ## Screen Transitions
      
      ### Custom Animations
      
      ```typescript
      import { TransitionPresets } from '@react-navigation/native-stack';
      
      <Stack.Navigator
        screenOptions={{
          ...TransitionPresets.SlideFromRightIOS,
          gestureEnabled: true,
          gestureDirection: 'horizontal',
        }}
      >
        {/* Standard slide transition */}
        <Stack.Screen name="List" component={ListScreen} />
      
        {/* Modal with custom animation */}
        <Stack.Screen
          name="Modal"
          component={ModalScreen}
          options={{
            presentation: 'transparentModal',
            animation: 'fade',
            cardOverlayEnabled: true,
          }}
        />
      
        {/* Full screen modal */}
        <Stack.Screen
          name="FullScreenModal"
          component={FullScreenModalScreen}
          options={{
            presentation: 'fullScreenModal',
            animation: 'slide_from_bottom',
          }}
        />
      </Stack.Navigator>
      ```
      
      ### Shared Element Transitions
      
      ```typescript
      import { SharedElement } from 'react-navigation-shared-element';
      import { createSharedElementStackNavigator } from 'react-navigation-shared-element';
      
      const Stack = createSharedElementStackNavigator();
      
      function ListScreen({ navigation }) {
        return (
          <FlatList
            data={items}
            renderItem={({ item }) => (
              <Pressable onPress={() => navigation.navigate('Detail', { item })}>
                <SharedElement id={`item.${item.id}.photo`}>
                  <Image source={{ uri: item.imageUrl }} style={styles.image} />
                </SharedElement>
                <SharedElement id={`item.${item.id}.title`}>
                  <Text style={styles.title}>{item.title}</Text>
                </SharedElement>
              </Pressable>
            )}
          />
        );
      }
      
      function DetailScreen({ route }) {
        const { item } = route.params;
      
        return (
          <View>
            <SharedElement id={`item.${item.id}.photo`}>
              <Image source={{ uri: item.imageUrl }} style={styles.heroImage} />
            </SharedElement>
            <SharedElement id={`item.${item.id}.title`}>
              <Text style={styles.title}>{item.title}</Text>
            </SharedElement>
          </View>
        );
      }
      
      // Navigator configuration
      <Stack.Navigator>
        <Stack.Screen name="List" component={ListScreen} />
        <Stack.Screen
          name="Detail"
          component={DetailScreen}
          sharedElements={(route) => {
            const { item } = route.params;
            return [
              { id: `item.${item.id}.photo`, animation: 'move' },
              { id: `item.${item.id}.title`, animation: 'fade' },
            ];
          }}
        />
      </Stack.Navigator>
      ```
      
      ## Header Customization
      
      ### Custom Header Component
      
      ```typescript
      import { getHeaderTitle } from '@react-navigation/elements';
      import { NativeStackHeaderProps } from '@react-navigation/native-stack';
      
      function CustomHeader({ navigation, route, options, back }: NativeStackHeaderProps) {
        const title = getHeaderTitle(options, route.name);
      
        return (
          <View style={styles.header}>
            {back && (
              <TouchableOpacity
                onPress={navigation.goBack}
                style={styles.backButton}
              >
                <Ionicons name="arrow-back" size={24} color="#1f2937" />
              </TouchableOpacity>
            )}
            <Text style={styles.title}>{title}</Text>
            {options.headerRight && (
              <View style={styles.rightActions}>
                {options.headerRight({ canGoBack: !!back })}
              </View>
            )}
          </View>
        );
      }
      
      // Usage
      <Stack.Navigator
        screenOptions={{
          header: (props) => <CustomHeader {...props} />,
        }}
      >
        {/* screens */}
      </Stack.Navigator>
      ```
      
      ### Collapsible Header
      
      ```typescript
      import Animated, {
        useSharedValue,
        useAnimatedScrollHandler,
        useAnimatedStyle,
        interpolate,
        Extrapolation,
      } from 'react-native-reanimated';
      
      const HEADER_HEIGHT = 200;
      const COLLAPSED_HEIGHT = 60;
      
      function CollapsibleHeaderScreen() {
        const scrollY = useSharedValue(0);
      
        const scrollHandler = useAnimatedScrollHandler({
          onScroll: (event) => {
            scrollY.value = event.contentOffset.y;
          },
        });
      
        const headerStyle = useAnimatedStyle(() => {
          const height = interpolate(
            scrollY.value,
            [0, HEADER_HEIGHT - COLLAPSED_HEIGHT],
            [HEADER_HEIGHT, COLLAPSED_HEIGHT],
            Extrapolation.CLAMP
          );
      
          return { height };
        });
      
        const titleStyle = useAnimatedStyle(() => {
          const fontSize = interpolate(
            scrollY.value,
            [0, HEADER_HEIGHT - COLLAPSED_HEIGHT],
            [32, 18],
            Extrapolation.CLAMP
          );
      
          return { fontSize };
        });
      
        return (
          <View style={styles.container}>
            <Animated.View style={[styles.header, headerStyle]}>
              <Animated.Text style={[styles.title, titleStyle]}>
                Title
              </Animated.Text>
            </Animated.View>
      
            <Animated.ScrollView
              onScroll={scrollHandler}
              scrollEventThrottle={16}
              contentContainerStyle={{ paddingTop: HEADER_HEIGHT }}
            >
              {/* Content */}
            </Animated.ScrollView>
          </View>
        );
      }
      ```
      
    • reanimated-patterns.md 17 KB
      # React Native Reanimated 3 Patterns
      
      ## Core Concepts
      
      ### Shared Values and Animated Styles
      
      ```typescript
      import Animated, {
        useSharedValue,
        useAnimatedStyle,
        withSpring,
        withTiming,
        withDelay,
        withSequence,
        withRepeat,
        Easing,
      } from 'react-native-reanimated';
      
      function BasicAnimations() {
        // Shared value - can be modified from JS or UI thread
        const opacity = useSharedValue(0);
        const scale = useSharedValue(1);
        const rotation = useSharedValue(0);
      
        // Animated style - runs on UI thread
        const animatedStyle = useAnimatedStyle(() => ({
          opacity: opacity.value,
          transform: [
            { scale: scale.value },
            { rotate: `${rotation.value}deg` },
          ],
        }));
      
        const animate = () => {
          // Spring animation
          scale.value = withSpring(1.2, {
            damping: 10,
            stiffness: 100,
          });
      
          // Timing animation with easing
          opacity.value = withTiming(1, {
            duration: 500,
            easing: Easing.bezier(0.25, 0.1, 0.25, 1),
          });
      
          // Sequence of animations
          rotation.value = withSequence(
            withTiming(15, { duration: 100 }),
            withTiming(-15, { duration: 100 }),
            withTiming(0, { duration: 100 })
          );
        };
      
        return (
          <Animated.View style={[styles.box, animatedStyle]} />
        );
      }
      ```
      
      ### Animation Callbacks
      
      ```typescript
      import { runOnJS, runOnUI } from 'react-native-reanimated';
      
      function AnimationWithCallbacks() {
        const translateX = useSharedValue(0);
        const [status, setStatus] = useState('idle');
      
        const updateStatus = (newStatus: string) => {
          setStatus(newStatus);
        };
      
        const animate = () => {
          translateX.value = withTiming(
            200,
            { duration: 1000 },
            (finished) => {
              'worklet';
              if (finished) {
                // Call JS function from worklet
                runOnJS(updateStatus)('completed');
              }
            }
          );
        };
      
        return (
          <Animated.View
            style={[
              styles.box,
              useAnimatedStyle(() => ({
                transform: [{ translateX: translateX.value }],
              })),
            ]}
          />
        );
      }
      ```
      
      ## Gesture Handler Integration
      
      ### Pan Gesture
      
      ```typescript
      import { Gesture, GestureDetector } from 'react-native-gesture-handler';
      import Animated, {
        useSharedValue,
        useAnimatedStyle,
        withSpring,
        clamp,
      } from 'react-native-reanimated';
      
      function DraggableBox() {
        const translateX = useSharedValue(0);
        const translateY = useSharedValue(0);
        const context = useSharedValue({ x: 0, y: 0 });
      
        const panGesture = Gesture.Pan()
          .onStart(() => {
            context.value = { x: translateX.value, y: translateY.value };
          })
          .onUpdate((event) => {
            translateX.value = event.translationX + context.value.x;
            translateY.value = event.translationY + context.value.y;
          })
          .onEnd((event) => {
            // Apply velocity decay
            translateX.value = withSpring(
              clamp(translateX.value + event.velocityX / 10, -100, 100)
            );
            translateY.value = withSpring(
              clamp(translateY.value + event.velocityY / 10, -100, 100)
            );
          });
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [
            { translateX: translateX.value },
            { translateY: translateY.value },
          ],
        }));
      
        return (
          <GestureDetector gesture={panGesture}>
            <Animated.View style={[styles.box, animatedStyle]} />
          </GestureDetector>
        );
      }
      ```
      
      ### Pinch and Rotate Gestures
      
      ```typescript
      function ZoomableImage() {
        const scale = useSharedValue(1);
        const rotation = useSharedValue(0);
        const savedScale = useSharedValue(1);
        const savedRotation = useSharedValue(0);
      
        const pinchGesture = Gesture.Pinch()
          .onUpdate((event) => {
            scale.value = savedScale.value * event.scale;
          })
          .onEnd(() => {
            savedScale.value = scale.value;
            // Snap back if too small
            if (scale.value < 1) {
              scale.value = withSpring(1);
              savedScale.value = 1;
            }
          });
      
        const rotateGesture = Gesture.Rotation()
          .onUpdate((event) => {
            rotation.value = savedRotation.value + event.rotation;
          })
          .onEnd(() => {
            savedRotation.value = rotation.value;
          });
      
        const composed = Gesture.Simultaneous(pinchGesture, rotateGesture);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [
            { scale: scale.value },
            { rotate: `${rotation.value}rad` },
          ],
        }));
      
        return (
          <GestureDetector gesture={composed}>
            <Animated.Image
              source={{ uri: imageUrl }}
              style={[styles.image, animatedStyle]}
            />
          </GestureDetector>
        );
      }
      ```
      
      ### Tap Gesture with Feedback
      
      ```typescript
      function TappableCard({ onPress, children }: TappableCardProps) {
        const scale = useSharedValue(1);
        const opacity = useSharedValue(1);
      
        const tapGesture = Gesture.Tap()
          .onBegin(() => {
            scale.value = withSpring(0.97);
            opacity.value = withTiming(0.8, { duration: 100 });
          })
          .onFinalize(() => {
            scale.value = withSpring(1);
            opacity.value = withTiming(1, { duration: 100 });
          })
          .onEnd(() => {
            runOnJS(onPress)();
          });
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ scale: scale.value }],
          opacity: opacity.value,
        }));
      
        return (
          <GestureDetector gesture={tapGesture}>
            <Animated.View style={[styles.card, animatedStyle]}>
              {children}
            </Animated.View>
          </GestureDetector>
        );
      }
      ```
      
      ## Common Animation Patterns
      
      ### Fade In/Out
      
      ```typescript
      function FadeInView({ visible, children }: FadeInViewProps) {
        const opacity = useSharedValue(0);
      
        useEffect(() => {
          opacity.value = withTiming(visible ? 1 : 0, { duration: 300 });
        }, [visible]);
      
        const animatedStyle = useAnimatedStyle(() => ({
          opacity: opacity.value,
          display: opacity.value === 0 ? 'none' : 'flex',
        }));
      
        return (
          <Animated.View style={animatedStyle}>
            {children}
          </Animated.View>
        );
      }
      ```
      
      ### Slide In/Out
      
      ```typescript
      function SlideInView({ visible, direction = 'right', children }) {
        const translateX = useSharedValue(direction === 'right' ? 100 : -100);
        const opacity = useSharedValue(0);
      
        useEffect(() => {
          if (visible) {
            translateX.value = withSpring(0);
            opacity.value = withTiming(1);
          } else {
            translateX.value = withSpring(direction === 'right' ? 100 : -100);
            opacity.value = withTiming(0);
          }
        }, [visible, direction]);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ translateX: translateX.value }],
          opacity: opacity.value,
        }));
      
        return (
          <Animated.View style={animatedStyle}>
            {children}
          </Animated.View>
        );
      }
      ```
      
      ### Staggered List Animation
      
      ```typescript
      function StaggeredList({ items }: { items: Item[] }) {
        return (
          <FlatList
            data={items}
            keyExtractor={(item) => item.id}
            renderItem={({ item, index }) => (
              <StaggeredItem item={item} index={index} />
            )}
          />
        );
      }
      
      function StaggeredItem({ item, index }: { item: Item; index: number }) {
        const translateY = useSharedValue(50);
        const opacity = useSharedValue(0);
      
        useEffect(() => {
          translateY.value = withDelay(
            index * 100,
            withSpring(0, { damping: 15 })
          );
          opacity.value = withDelay(
            index * 100,
            withTiming(1, { duration: 300 })
          );
        }, [index]);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ translateY: translateY.value }],
          opacity: opacity.value,
        }));
      
        return (
          <Animated.View style={[styles.listItem, animatedStyle]}>
            <Text>{item.title}</Text>
          </Animated.View>
        );
      }
      ```
      
      ### Pulse Animation
      
      ```typescript
      function PulseView({ children }: { children: React.ReactNode }) {
        const scale = useSharedValue(1);
      
        useEffect(() => {
          scale.value = withRepeat(
            withSequence(
              withTiming(1.05, { duration: 500 }),
              withTiming(1, { duration: 500 })
            ),
            -1, // infinite
            false // no reverse
          );
      
          return () => {
            cancelAnimation(scale);
          };
        }, []);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ scale: scale.value }],
        }));
      
        return (
          <Animated.View style={animatedStyle}>
            {children}
          </Animated.View>
        );
      }
      ```
      
      ### Shake Animation
      
      ```typescript
      function ShakeView({ trigger, children }) {
        const translateX = useSharedValue(0);
      
        useEffect(() => {
          if (trigger) {
            translateX.value = withSequence(
              withTiming(-10, { duration: 50 }),
              withTiming(10, { duration: 50 }),
              withTiming(-10, { duration: 50 }),
              withTiming(10, { duration: 50 }),
              withTiming(0, { duration: 50 })
            );
          }
        }, [trigger]);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ translateX: translateX.value }],
        }));
      
        return (
          <Animated.View style={animatedStyle}>
            {children}
          </Animated.View>
        );
      }
      ```
      
      ## Advanced Patterns
      
      ### Interpolation
      
      ```typescript
      import { interpolate, Extrapolation } from 'react-native-reanimated';
      
      function ParallaxHeader() {
        const scrollY = useSharedValue(0);
      
        const headerStyle = useAnimatedStyle(() => {
          const height = interpolate(
            scrollY.value,
            [0, 200],
            [300, 100],
            Extrapolation.CLAMP
          );
      
          const opacity = interpolate(
            scrollY.value,
            [0, 150, 200],
            [1, 0.5, 0],
            Extrapolation.CLAMP
          );
      
          const translateY = interpolate(
            scrollY.value,
            [0, 200],
            [0, -50],
            Extrapolation.CLAMP
          );
      
          return {
            height,
            opacity,
            transform: [{ translateY }],
          };
        });
      
        return (
          <View style={styles.container}>
            <Animated.View style={[styles.header, headerStyle]}>
              <Text style={styles.headerTitle}>Header</Text>
            </Animated.View>
            <Animated.ScrollView
              onScroll={useAnimatedScrollHandler({
                onScroll: (event) => {
                  scrollY.value = event.contentOffset.y;
                },
              })}
              scrollEventThrottle={16}
            >
              {/* Content */}
            </Animated.ScrollView>
          </View>
        );
      }
      ```
      
      ### Color Interpolation
      
      ```typescript
      import { interpolateColor } from 'react-native-reanimated';
      
      function ColorTransition() {
        const progress = useSharedValue(0);
      
        const animatedStyle = useAnimatedStyle(() => {
          const backgroundColor = interpolateColor(
            progress.value,
            [0, 0.5, 1],
            ['#6366f1', '#8b5cf6', '#ec4899']
          );
      
          return { backgroundColor };
        });
      
        const toggleColor = () => {
          progress.value = withTiming(progress.value === 0 ? 1 : 0, {
            duration: 1000,
          });
        };
      
        return (
          <Pressable onPress={toggleColor}>
            <Animated.View style={[styles.box, animatedStyle]} />
          </Pressable>
        );
      }
      ```
      
      ### Derived Values
      
      ```typescript
      import { useDerivedValue } from 'react-native-reanimated';
      
      function DerivedValueExample() {
        const x = useSharedValue(0);
        const y = useSharedValue(0);
      
        // Derived value computed from other shared values
        const distance = useDerivedValue(() => {
          return Math.sqrt(x.value ** 2 + y.value ** 2);
        });
      
        const angle = useDerivedValue(() => {
          return Math.atan2(y.value, x.value) * (180 / Math.PI);
        });
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [
            { translateX: x.value },
            { translateY: y.value },
            { rotate: `${angle.value}deg` },
          ],
          opacity: interpolate(distance.value, [0, 200], [1, 0.5]),
        }));
      
        return (
          <Animated.View style={[styles.box, animatedStyle]} />
        );
      }
      ```
      
      ### Layout Animations
      
      ```typescript
      import Animated, {
        Layout,
        FadeIn,
        FadeOut,
        SlideInLeft,
        SlideOutRight,
        ZoomIn,
        ZoomOut,
      } from 'react-native-reanimated';
      
      function AnimatedList() {
        const [items, setItems] = useState([1, 2, 3, 4, 5]);
      
        const addItem = () => {
          setItems([...items, items.length + 1]);
        };
      
        const removeItem = (id: number) => {
          setItems(items.filter((item) => item !== id));
        };
      
        return (
          <View style={styles.container}>
            <Button title="Add Item" onPress={addItem} />
            {items.map((item) => (
              <Animated.View
                key={item}
                style={styles.item}
                entering={FadeIn.duration(300).springify()}
                exiting={SlideOutRight.duration(300)}
                layout={Layout.springify()}
              >
                <Text>Item {item}</Text>
                <Pressable onPress={() => removeItem(item)}>
                  <Text>Remove</Text>
                </Pressable>
              </Animated.View>
            ))}
          </View>
        );
      }
      ```
      
      ### Swipeable Card
      
      ```typescript
      function SwipeableCard({ onSwipeLeft, onSwipeRight }) {
        const translateX = useSharedValue(0);
        const rotateZ = useSharedValue(0);
        const context = useSharedValue({ x: 0 });
      
        const SWIPE_THRESHOLD = 120;
      
        const panGesture = Gesture.Pan()
          .onStart(() => {
            context.value = { x: translateX.value };
          })
          .onUpdate((event) => {
            translateX.value = event.translationX + context.value.x;
            rotateZ.value = interpolate(
              translateX.value,
              [-200, 0, 200],
              [-15, 0, 15]
            );
          })
          .onEnd((event) => {
            if (translateX.value > SWIPE_THRESHOLD) {
              translateX.value = withTiming(500, { duration: 200 }, () => {
                runOnJS(onSwipeRight)();
              });
            } else if (translateX.value < -SWIPE_THRESHOLD) {
              translateX.value = withTiming(-500, { duration: 200 }, () => {
                runOnJS(onSwipeLeft)();
              });
            } else {
              translateX.value = withSpring(0);
              rotateZ.value = withSpring(0);
            }
          });
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [
            { translateX: translateX.value },
            { rotate: `${rotateZ.value}deg` },
          ],
        }));
      
        const leftIndicatorStyle = useAnimatedStyle(() => ({
          opacity: interpolate(
            translateX.value,
            [0, SWIPE_THRESHOLD],
            [0, 1],
            Extrapolation.CLAMP
          ),
        }));
      
        const rightIndicatorStyle = useAnimatedStyle(() => ({
          opacity: interpolate(
            translateX.value,
            [-SWIPE_THRESHOLD, 0],
            [1, 0],
            Extrapolation.CLAMP
          ),
        }));
      
        return (
          <GestureDetector gesture={panGesture}>
            <Animated.View style={[styles.card, animatedStyle]}>
              <Animated.View style={[styles.likeIndicator, leftIndicatorStyle]}>
                <Text>LIKE</Text>
              </Animated.View>
              <Animated.View style={[styles.nopeIndicator, rightIndicatorStyle]}>
                <Text>NOPE</Text>
              </Animated.View>
              {/* Card content */}
            </Animated.View>
          </GestureDetector>
        );
      }
      ```
      
      ### Bottom Sheet
      
      ```typescript
      const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50;
      const MIN_TRANSLATE_Y = 0;
      const SNAP_POINTS = [-SCREEN_HEIGHT * 0.5, -SCREEN_HEIGHT * 0.25, 0];
      
      function BottomSheet({ children }) {
        const translateY = useSharedValue(0);
        const context = useSharedValue({ y: 0 });
      
        const panGesture = Gesture.Pan()
          .onStart(() => {
            context.value = { y: translateY.value };
          })
          .onUpdate((event) => {
            translateY.value = clamp(
              context.value.y + event.translationY,
              MAX_TRANSLATE_Y,
              MIN_TRANSLATE_Y
            );
          })
          .onEnd((event) => {
            // Find closest snap point
            const destination = SNAP_POINTS.reduce((prev, curr) =>
              Math.abs(curr - translateY.value) < Math.abs(prev - translateY.value)
                ? curr
                : prev
            );
      
            translateY.value = withSpring(destination, {
              damping: 50,
              stiffness: 300,
            });
          });
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ translateY: translateY.value }],
        }));
      
        const backdropStyle = useAnimatedStyle(() => ({
          opacity: interpolate(
            translateY.value,
            [MIN_TRANSLATE_Y, MAX_TRANSLATE_Y],
            [0, 0.5]
          ),
          pointerEvents: translateY.value < -50 ? 'auto' : 'none',
        }));
      
        return (
          <>
            <Animated.View style={[styles.backdrop, backdropStyle]} />
            <GestureDetector gesture={panGesture}>
              <Animated.View style={[styles.bottomSheet, animatedStyle]}>
                <View style={styles.handle} />
                {children}
              </Animated.View>
            </GestureDetector>
          </>
        );
      }
      ```
      
      ## Performance Tips
      
      ### Memoization
      
      ```typescript
      // Memoize animated style when dependencies don't change
      const animatedStyle = useAnimatedStyle(
        () => ({
          transform: [{ translateX: translateX.value }],
        }),
        [],
      ); // Empty deps if no external dependencies
      
      // Use useMemo for complex calculations outside worklets
      const threshold = useMemo(() => calculateThreshold(screenWidth), [screenWidth]);
      ```
      
      ### Worklet Best Practices
      
      ```typescript
      // Do: Keep worklets simple
      const simpleWorklet = () => {
        "worklet";
        return scale.value * 2;
      };
      
      // Don't: Complex logic in worklets
      // Move complex logic to JS with runOnJS
      
      // Do: Use runOnJS for callbacks
      const onComplete = () => {
        setIsAnimating(false);
      };
      
      opacity.value = withTiming(1, {}, (finished) => {
        "worklet";
        if (finished) {
          runOnJS(onComplete)();
        }
      });
      ```
      
      ### Cancel Animations
      
      ```typescript
      import { cancelAnimation } from 'react-native-reanimated';
      
      function AnimatedComponent() {
        const translateX = useSharedValue(0);
      
        useEffect(() => {
          // Start animation
          translateX.value = withRepeat(
            withTiming(100, { duration: 1000 }),
            -1,
            true
          );
      
          // Cleanup: cancel animation on unmount
          return () => {
            cancelAnimation(translateX);
          };
        }, []);
      
        return <Animated.View style={animatedStyle} />;
      }
      ```
      
    • styling-patterns.md 18.4 KB
      # React Native Styling Patterns
      
      ## StyleSheet Fundamentals
      
      ### Creating Styles
      
      ```typescript
      import { StyleSheet, ViewStyle, TextStyle, ImageStyle } from "react-native";
      
      // Typed styles for better IDE support
      interface Styles {
        container: ViewStyle;
        title: TextStyle;
        image: ImageStyle;
      }
      
      const styles = StyleSheet.create<Styles>({
        container: {
          flex: 1,
          padding: 16,
          backgroundColor: "#ffffff",
        },
        title: {
          fontSize: 24,
          fontWeight: "700",
          color: "#1f2937",
        },
        image: {
          width: 100,
          height: 100,
          borderRadius: 8,
        },
      });
      ```
      
      ### Combining Styles
      
      ```typescript
      import { StyleProp, ViewStyle } from 'react-native';
      
      interface BoxProps {
        style?: StyleProp<ViewStyle>;
        variant?: 'default' | 'primary' | 'danger';
      }
      
      function Box({ style, variant = 'default' }: BoxProps) {
        return (
          <View
            style={[
              styles.base,
              variant === 'primary' && styles.primary,
              variant === 'danger' && styles.danger,
              style, // Allow external style overrides
            ]}
          />
        );
      }
      
      const styles = StyleSheet.create({
        base: {
          padding: 16,
          borderRadius: 8,
          backgroundColor: '#f3f4f6',
        },
        primary: {
          backgroundColor: '#6366f1',
        },
        danger: {
          backgroundColor: '#ef4444',
        },
      });
      ```
      
      ## Theme System
      
      ### Theme Context
      
      ```typescript
      import React, { createContext, useContext, useMemo } from 'react';
      import { useColorScheme } from 'react-native';
      
      interface Theme {
        colors: {
          primary: string;
          secondary: string;
          background: string;
          surface: string;
          text: string;
          textSecondary: string;
          border: string;
          error: string;
          success: string;
        };
        spacing: {
          xs: number;
          sm: number;
          md: number;
          lg: number;
          xl: number;
        };
        borderRadius: {
          sm: number;
          md: number;
          lg: number;
          full: number;
        };
        typography: {
          h1: { fontSize: number; fontWeight: string; lineHeight: number };
          h2: { fontSize: number; fontWeight: string; lineHeight: number };
          body: { fontSize: number; fontWeight: string; lineHeight: number };
          caption: { fontSize: number; fontWeight: string; lineHeight: number };
        };
      }
      
      const lightTheme: Theme = {
        colors: {
          primary: '#6366f1',
          secondary: '#8b5cf6',
          background: '#ffffff',
          surface: '#f9fafb',
          text: '#1f2937',
          textSecondary: '#6b7280',
          border: '#e5e7eb',
          error: '#ef4444',
          success: '#10b981',
        },
        spacing: {
          xs: 4,
          sm: 8,
          md: 16,
          lg: 24,
          xl: 32,
        },
        borderRadius: {
          sm: 4,
          md: 8,
          lg: 16,
          full: 9999,
        },
        typography: {
          h1: { fontSize: 32, fontWeight: '700', lineHeight: 40 },
          h2: { fontSize: 24, fontWeight: '600', lineHeight: 32 },
          body: { fontSize: 16, fontWeight: '400', lineHeight: 24 },
          caption: { fontSize: 12, fontWeight: '400', lineHeight: 16 },
        },
      };
      
      const darkTheme: Theme = {
        ...lightTheme,
        colors: {
          primary: '#818cf8',
          secondary: '#a78bfa',
          background: '#111827',
          surface: '#1f2937',
          text: '#f9fafb',
          textSecondary: '#9ca3af',
          border: '#374151',
          error: '#f87171',
          success: '#34d399',
        },
      };
      
      const ThemeContext = createContext<Theme>(lightTheme);
      
      export function ThemeProvider({ children }: { children: React.ReactNode }) {
        const colorScheme = useColorScheme();
        const theme = useMemo(
          () => (colorScheme === 'dark' ? darkTheme : lightTheme),
          [colorScheme]
        );
      
        return (
          <ThemeContext.Provider value={theme}>
            {children}
          </ThemeContext.Provider>
        );
      }
      
      export function useTheme() {
        return useContext(ThemeContext);
      }
      ```
      
      ### Using Theme
      
      ```typescript
      import { useTheme } from './theme';
      
      function ThemedCard() {
        const theme = useTheme();
      
        return (
          <View
            style={{
              backgroundColor: theme.colors.surface,
              padding: theme.spacing.md,
              borderRadius: theme.borderRadius.lg,
              borderWidth: 1,
              borderColor: theme.colors.border,
            }}
          >
            <Text
              style={{
                ...theme.typography.h2,
                color: theme.colors.text,
                marginBottom: theme.spacing.sm,
              }}
            >
              Card Title
            </Text>
            <Text
              style={{
                ...theme.typography.body,
                color: theme.colors.textSecondary,
              }}
            >
              Card description text
            </Text>
          </View>
        );
      }
      ```
      
      ## Responsive Design
      
      ### Screen Dimensions
      
      ```typescript
      import { Dimensions, useWindowDimensions, PixelRatio } from 'react-native';
      
      // Get dimensions once (may be stale after rotation)
      const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
      
      // Responsive scaling
      const guidelineBaseWidth = 375;
      const guidelineBaseHeight = 812;
      
      export const scale = (size: number) =>
        (SCREEN_WIDTH / guidelineBaseWidth) * size;
      
      export const verticalScale = (size: number) =>
        (SCREEN_HEIGHT / guidelineBaseHeight) * size;
      
      export const moderateScale = (size: number, factor = 0.5) =>
        size + (scale(size) - size) * factor;
      
      // Hook for dynamic dimensions (handles rotation)
      function ResponsiveComponent() {
        const { width, height } = useWindowDimensions();
        const isLandscape = width > height;
        const isTablet = width >= 768;
      
        return (
          <View style={{ flexDirection: isLandscape ? 'row' : 'column' }}>
            {/* Content */}
          </View>
        );
      }
      ```
      
      ### Breakpoint System
      
      ```typescript
      import { useWindowDimensions } from 'react-native';
      
      type Breakpoint = 'sm' | 'md' | 'lg' | 'xl';
      
      const breakpoints = {
        sm: 0,
        md: 768,
        lg: 1024,
        xl: 1280,
      };
      
      export function useBreakpoint(): Breakpoint {
        const { width } = useWindowDimensions();
      
        if (width >= breakpoints.xl) return 'xl';
        if (width >= breakpoints.lg) return 'lg';
        if (width >= breakpoints.md) return 'md';
        return 'sm';
      }
      
      export function useResponsiveValue<T>(values: Partial<Record<Breakpoint, T>>): T | undefined {
        const breakpoint = useBreakpoint();
        const breakpointOrder: Breakpoint[] = ['xl', 'lg', 'md', 'sm'];
        const currentIndex = breakpointOrder.indexOf(breakpoint);
      
        for (let i = currentIndex; i < breakpointOrder.length; i++) {
          const bp = breakpointOrder[i];
          if (values[bp] !== undefined) {
            return values[bp];
          }
        }
      
        return undefined;
      }
      
      // Usage
      function ResponsiveGrid() {
        const columns = useResponsiveValue({ sm: 1, md: 2, lg: 3, xl: 4 }) ?? 1;
      
        return (
          <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
            {items.map((item) => (
              <View key={item.id} style={{ width: `${100 / columns}%` }}>
                <ItemCard item={item} />
              </View>
            ))}
          </View>
        );
      }
      ```
      
      ## Layout Components
      
      ### Container
      
      ```typescript
      import { View, ViewStyle, StyleProp } from 'react-native';
      import { useSafeAreaInsets } from 'react-native-safe-area-context';
      import { useTheme } from './theme';
      
      interface ContainerProps {
        children: React.ReactNode;
        style?: StyleProp<ViewStyle>;
        edges?: ('top' | 'bottom' | 'left' | 'right')[];
      }
      
      export function Container({ children, style, edges = ['top', 'bottom'] }: ContainerProps) {
        const insets = useSafeAreaInsets();
        const theme = useTheme();
      
        return (
          <View
            style={[
              {
                flex: 1,
                backgroundColor: theme.colors.background,
                paddingTop: edges.includes('top') ? insets.top : 0,
                paddingBottom: edges.includes('bottom') ? insets.bottom : 0,
                paddingLeft: edges.includes('left') ? insets.left : 0,
                paddingRight: edges.includes('right') ? insets.right : 0,
              },
              style,
            ]}
          >
            {children}
          </View>
        );
      }
      ```
      
      ### Stack Components
      
      ```typescript
      import { View, ViewStyle, StyleProp } from 'react-native';
      
      interface StackProps {
        children: React.ReactNode;
        spacing?: number;
        style?: StyleProp<ViewStyle>;
      }
      
      export function VStack({ children, spacing = 8, style }: StackProps) {
        return (
          <View style={[{ gap: spacing }, style]}>
            {children}
          </View>
        );
      }
      
      export function HStack({ children, spacing = 8, style }: StackProps) {
        return (
          <View style={[{ flexDirection: 'row', alignItems: 'center', gap: spacing }, style]}>
            {children}
          </View>
        );
      }
      
      // Usage
      function Example() {
        return (
          <VStack spacing={16}>
            <HStack spacing={8}>
              <Avatar />
              <VStack spacing={2}>
                <Text style={styles.name}>John Doe</Text>
                <Text style={styles.email}>john@example.com</Text>
              </VStack>
            </HStack>
            <Button title="Edit Profile" />
          </VStack>
        );
      }
      ```
      
      ### Spacer
      
      ```typescript
      import { View } from 'react-native';
      
      interface SpacerProps {
        size?: number;
        flex?: number;
      }
      
      export function Spacer({ size, flex }: SpacerProps) {
        if (flex) {
          return <View style={{ flex }} />;
        }
        return <View style={{ height: size, width: size }} />;
      }
      
      // Usage
      <HStack>
        <Text>Left</Text>
        <Spacer flex={1} />
        <Text>Right</Text>
      </HStack>
      ```
      
      ## Shadow Styles
      
      ### Cross-Platform Shadows
      
      ```typescript
      import { Platform, ViewStyle } from "react-native";
      
      export function createShadow(elevation: number, color = "#000000"): ViewStyle {
        if (Platform.OS === "android") {
          return { elevation };
        }
      
        // iOS shadow mapping based on elevation
        const shadowMap: Record<number, ViewStyle> = {
          1: {
            shadowColor: color,
            shadowOffset: { width: 0, height: 1 },
            shadowOpacity: 0.18,
            shadowRadius: 1,
          },
          2: {
            shadowColor: color,
            shadowOffset: { width: 0, height: 2 },
            shadowOpacity: 0.2,
            shadowRadius: 2,
          },
          4: {
            shadowColor: color,
            shadowOffset: { width: 0, height: 2 },
            shadowOpacity: 0.22,
            shadowRadius: 4,
          },
          8: {
            shadowColor: color,
            shadowOffset: { width: 0, height: 4 },
            shadowOpacity: 0.25,
            shadowRadius: 8,
          },
          16: {
            shadowColor: color,
            shadowOffset: { width: 0, height: 8 },
            shadowOpacity: 0.3,
            shadowRadius: 16,
          },
        };
      
        return shadowMap[elevation] || shadowMap[4];
      }
      
      // Predefined shadow styles
      export const shadows = {
        sm: createShadow(2),
        md: createShadow(4),
        lg: createShadow(8),
        xl: createShadow(16),
      };
      
      // Usage
      const styles = StyleSheet.create({
        card: {
          backgroundColor: "#ffffff",
          borderRadius: 12,
          padding: 16,
          ...shadows.md,
        },
      });
      ```
      
      ## Typography System
      
      ### Text Components
      
      ```typescript
      import { Text as RNText, TextStyle, StyleProp, TextProps as RNTextProps } from 'react-native';
      import { useTheme } from './theme';
      
      type Variant = 'h1' | 'h2' | 'h3' | 'body' | 'bodySmall' | 'caption' | 'label';
      type Color = 'primary' | 'secondary' | 'text' | 'textSecondary' | 'error' | 'success';
      
      interface TextProps extends RNTextProps {
        variant?: Variant;
        color?: Color;
        weight?: 'normal' | 'medium' | 'semibold' | 'bold';
        align?: 'left' | 'center' | 'right';
      }
      
      const variantStyles: Record<Variant, TextStyle> = {
        h1: { fontSize: 32, lineHeight: 40, fontWeight: '700' },
        h2: { fontSize: 24, lineHeight: 32, fontWeight: '600' },
        h3: { fontSize: 20, lineHeight: 28, fontWeight: '600' },
        body: { fontSize: 16, lineHeight: 24, fontWeight: '400' },
        bodySmall: { fontSize: 14, lineHeight: 20, fontWeight: '400' },
        caption: { fontSize: 12, lineHeight: 16, fontWeight: '400' },
        label: { fontSize: 14, lineHeight: 20, fontWeight: '500' },
      };
      
      const weightStyles: Record<string, TextStyle> = {
        normal: { fontWeight: '400' },
        medium: { fontWeight: '500' },
        semibold: { fontWeight: '600' },
        bold: { fontWeight: '700' },
      };
      
      export function Text({
        variant = 'body',
        color = 'text',
        weight,
        align,
        style,
        ...props
      }: TextProps) {
        const theme = useTheme();
      
        return (
          <RNText
            style={[
              variantStyles[variant],
              { color: theme.colors[color] },
              weight && weightStyles[weight],
              align && { textAlign: align },
              style,
            ]}
            {...props}
          />
        );
      }
      
      // Usage
      <Text variant="h1">Heading</Text>
      <Text variant="body" color="textSecondary">Body text</Text>
      <Text variant="label" weight="semibold">Label</Text>
      ```
      
      ## Button Styles
      
      ### Customizable Button
      
      ```typescript
      import { Pressable, StyleSheet, ActivityIndicator } from 'react-native';
      import Animated, {
        useSharedValue,
        useAnimatedStyle,
        withSpring,
      } from 'react-native-reanimated';
      import { useTheme } from './theme';
      
      type Variant = 'filled' | 'outlined' | 'ghost';
      type Size = 'sm' | 'md' | 'lg';
      
      interface ButtonProps {
        title: string;
        onPress: () => void;
        variant?: Variant;
        size?: Size;
        disabled?: boolean;
        loading?: boolean;
        leftIcon?: React.ReactNode;
        rightIcon?: React.ReactNode;
      }
      
      const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
      
      export function Button({
        title,
        onPress,
        variant = 'filled',
        size = 'md',
        disabled = false,
        loading = false,
        leftIcon,
        rightIcon,
      }: ButtonProps) {
        const theme = useTheme();
        const scale = useSharedValue(1);
      
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [{ scale: scale.value }],
        }));
      
        const sizeStyles = {
          sm: { paddingVertical: 8, paddingHorizontal: 12, fontSize: 14 },
          md: { paddingVertical: 12, paddingHorizontal: 16, fontSize: 16 },
          lg: { paddingVertical: 16, paddingHorizontal: 24, fontSize: 18 },
        };
      
        const variantStyles = {
          filled: {
            backgroundColor: theme.colors.primary,
            textColor: '#ffffff',
          },
          outlined: {
            backgroundColor: 'transparent',
            borderWidth: 1,
            borderColor: theme.colors.primary,
            textColor: theme.colors.primary,
          },
          ghost: {
            backgroundColor: 'transparent',
            textColor: theme.colors.primary,
          },
        };
      
        const currentVariant = variantStyles[variant];
        const currentSize = sizeStyles[size];
      
        return (
          <AnimatedPressable
            style={[
              styles.base,
              {
                backgroundColor: currentVariant.backgroundColor,
                borderWidth: currentVariant.borderWidth,
                borderColor: currentVariant.borderColor,
                paddingVertical: currentSize.paddingVertical,
                paddingHorizontal: currentSize.paddingHorizontal,
                opacity: disabled ? 0.5 : 1,
              },
              animatedStyle,
            ]}
            onPress={onPress}
            onPressIn={() => { scale.value = withSpring(0.97); }}
            onPressOut={() => { scale.value = withSpring(1); }}
            disabled={disabled || loading}
          >
            {loading ? (
              <ActivityIndicator color={currentVariant.textColor} />
            ) : (
              <>
                {leftIcon}
                <Text
                  style={{
                    color: currentVariant.textColor,
                    fontSize: currentSize.fontSize,
                    fontWeight: '600',
                    marginHorizontal: leftIcon || rightIcon ? 8 : 0,
                  }}
                >
                  {title}
                </Text>
                {rightIcon}
              </>
            )}
          </AnimatedPressable>
        );
      }
      
      const styles = StyleSheet.create({
        base: {
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'center',
          borderRadius: 8,
        },
      });
      ```
      
      ## Form Styles
      
      ### Input Component
      
      ```typescript
      import { useState } from 'react';
      import {
        View,
        TextInput,
        StyleSheet,
        TextInputProps,
        Pressable,
      } from 'react-native';
      import { useTheme } from './theme';
      
      interface InputProps extends TextInputProps {
        label?: string;
        error?: string;
        leftIcon?: React.ReactNode;
        rightIcon?: React.ReactNode;
      }
      
      export function Input({
        label,
        error,
        leftIcon,
        rightIcon,
        style,
        ...props
      }: InputProps) {
        const theme = useTheme();
        const [isFocused, setIsFocused] = useState(false);
      
        const borderColor = error
          ? theme.colors.error
          : isFocused
          ? theme.colors.primary
          : theme.colors.border;
      
        return (
          <View style={styles.container}>
            {label && (
              <Text style={[styles.label, { color: theme.colors.text }]}>
                {label}
              </Text>
            )}
            <View
              style={[
                styles.inputContainer,
                {
                  borderColor,
                  backgroundColor: theme.colors.surface,
                },
              ]}
            >
              {leftIcon && <View style={styles.icon}>{leftIcon}</View>}
              <TextInput
                style={[
                  styles.input,
                  { color: theme.colors.text },
                  style,
                ]}
                placeholderTextColor={theme.colors.textSecondary}
                onFocus={() => setIsFocused(true)}
                onBlur={() => setIsFocused(false)}
                {...props}
              />
              {rightIcon && <View style={styles.icon}>{rightIcon}</View>}
            </View>
            {error && (
              <Text style={[styles.error, { color: theme.colors.error }]}>
                {error}
              </Text>
            )}
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        container: {
          marginBottom: 16,
        },
        label: {
          fontSize: 14,
          fontWeight: '500',
          marginBottom: 6,
        },
        inputContainer: {
          flexDirection: 'row',
          alignItems: 'center',
          borderWidth: 1,
          borderRadius: 8,
          paddingHorizontal: 12,
        },
        input: {
          flex: 1,
          paddingVertical: 12,
          fontSize: 16,
        },
        icon: {
          marginHorizontal: 4,
        },
        error: {
          fontSize: 12,
          marginTop: 4,
        },
      });
      ```
      
      ## List Styles
      
      ### FlatList with Styling
      
      ```typescript
      import { FlatList, View, StyleSheet } from 'react-native';
      
      interface Item {
        id: string;
        title: string;
        subtitle: string;
      }
      
      function StyledList({ items }: { items: Item[] }) {
        return (
          <FlatList
            data={items}
            keyExtractor={(item) => item.id}
            renderItem={({ item, index }) => (
              <View
                style={[
                  styles.item,
                  index === 0 && styles.firstItem,
                  index === items.length - 1 && styles.lastItem,
                ]}
              >
                <Text style={styles.itemTitle}>{item.title}</Text>
                <Text style={styles.itemSubtitle}>{item.subtitle}</Text>
              </View>
            )}
            ItemSeparatorComponent={() => <View style={styles.separator} />}
            ListHeaderComponent={() => (
              <Text style={styles.header}>List Header</Text>
            )}
            ListEmptyComponent={() => (
              <View style={styles.empty}>
                <Text>No items found</Text>
              </View>
            )}
            contentContainerStyle={styles.listContent}
          />
        );
      }
      
      const styles = StyleSheet.create({
        listContent: {
          padding: 16,
        },
        item: {
          backgroundColor: '#ffffff',
          padding: 16,
        },
        firstItem: {
          borderTopLeftRadius: 12,
          borderTopRightRadius: 12,
        },
        lastItem: {
          borderBottomLeftRadius: 12,
          borderBottomRightRadius: 12,
        },
        itemTitle: {
          fontSize: 16,
          fontWeight: '600',
          marginBottom: 4,
        },
        itemSubtitle: {
          fontSize: 14,
          color: '#6b7280',
        },
        separator: {
          height: 1,
          backgroundColor: '#e5e7eb',
        },
        header: {
          fontSize: 20,
          fontWeight: '700',
          marginBottom: 16,
        },
        empty: {
          alignItems: 'center',
          padding: 32,
        },
      });
      ```
      
  • SKILL.md 3.9 KB
    ---
    name: react-native-design
    description: Master React Native styling, navigation, and Reanimated animations for cross-platform mobile development. Use when building React Native apps, implementing navigation patterns, or creating performant animations.
    ---
    
    # React Native Design
    
    Master React Native styling patterns, React Navigation, and Reanimated 3 to build performant, cross-platform mobile applications with native-quality user experiences.
    
    ## When to Use This Skill
    
    - Building cross-platform mobile apps with React Native
    - Implementing navigation with React Navigation 6+
    - Creating performant animations with Reanimated 3
    - Styling components with StyleSheet and styled-components
    - Building responsive layouts for different screen sizes
    - Implementing platform-specific designs (iOS/Android)
    - Creating gesture-driven interactions with Gesture Handler
    - Optimizing React Native performance
    
    ## Detailed section: Core Concepts
    
    Originally a 6471-byte section in this SKILL.md. Moved to `references/details.md` to fit Codex's 8 KB skill body cap.
    
    ## Quick Start Component
    
    ```typescript
    import React from 'react';
    import {
      View,
      Text,
      StyleSheet,
      Pressable,
      Image,
    } from 'react-native';
    import Animated, {
      useSharedValue,
      useAnimatedStyle,
      withSpring,
    } from 'react-native-reanimated';
    
    interface ItemCardProps {
      title: string;
      subtitle: string;
      imageUrl: string;
      onPress: () => void;
    }
    
    const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
    
    export function ItemCard({ title, subtitle, imageUrl, onPress }: ItemCardProps) {
      const scale = useSharedValue(1);
    
      const animatedStyle = useAnimatedStyle(() => ({
        transform: [{ scale: scale.value }],
      }));
    
      return (
        <AnimatedPressable
          style={[styles.card, animatedStyle]}
          onPress={onPress}
          onPressIn={() => { scale.value = withSpring(0.97); }}
          onPressOut={() => { scale.value = withSpring(1); }}
        >
          <Image source={{ uri: imageUrl }} style={styles.image} />
          <View style={styles.content}>
            <Text style={styles.title} numberOfLines={1}>
              {title}
            </Text>
            <Text style={styles.subtitle} numberOfLines={2}>
              {subtitle}
            </Text>
          </View>
        </AnimatedPressable>
      );
    }
    
    const styles = StyleSheet.create({
      card: {
        backgroundColor: '#ffffff',
        borderRadius: 16,
        overflow: 'hidden',
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 8,
        elevation: 4,
      },
      image: {
        width: '100%',
        height: 160,
        backgroundColor: '#f3f4f6',
      },
      content: {
        padding: 16,
        gap: 4,
      },
      title: {
        fontSize: 18,
        fontWeight: '600',
        color: '#1f2937',
      },
      subtitle: {
        fontSize: 14,
        color: '#6b7280',
        lineHeight: 20,
      },
    });
    ```
    
    ## Best Practices
    
    1. **Use TypeScript**: Define navigation and prop types for type safety
    2. **Memoize Components**: Use `React.memo` and `useCallback` to prevent unnecessary rerenders
    3. **Run Animations on UI Thread**: Use Reanimated worklets for 60fps animations
    4. **Avoid Inline Styles**: Use StyleSheet.create for performance
    5. **Handle Safe Areas**: Use `SafeAreaView` or `useSafeAreaInsets`
    6. **Test on Real Devices**: Simulator/emulator performance differs from real devices
    7. **Use FlatList for Lists**: Never use ScrollView with map for long lists
    8. **Platform-Specific Code**: Use Platform.select for iOS/Android differences
    
    ## Common Issues
    
    - **Gesture Conflicts**: Wrap gestures with `GestureDetector` and use `simultaneousHandlers`
    - **Navigation Type Errors**: Define `ParamList` types for all navigators
    - **Animation Jank**: Move animations to UI thread with `runOnUI` worklets
    - **Memory Leaks**: Cancel animations and cleanup in useEffect
    - **Font Loading**: Use `expo-font` or `react-native-asset` for custom fonts
    - **Safe Area Issues**: Test on notched devices (iPhone, Android with cutouts)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related