From 837e3f4646f471b7d25e96b76f635c4c94764b61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”>
Date: Sun, 22 Feb 2026 23:07:15 +0300
Subject: [PATCH] -
---
.agents/skills/react-native-design/SKILL.md | 440 +++++++++
.../references/navigation-patterns.md | 832 +++++++++++++++++
.../references/reanimated-patterns.md | 775 ++++++++++++++++
.../references/styling-patterns.md | 871 ++++++++++++++++++
skills-lock.json | 10 +
5 files changed, 2928 insertions(+)
create mode 100644 .agents/skills/react-native-design/SKILL.md
create mode 100644 .agents/skills/react-native-design/references/navigation-patterns.md
create mode 100644 .agents/skills/react-native-design/references/reanimated-patterns.md
create mode 100644 .agents/skills/react-native-design/references/styling-patterns.md
create mode 100644 skills-lock.json
diff --git a/.agents/skills/react-native-design/SKILL.md b/.agents/skills/react-native-design/SKILL.md
new file mode 100644
index 0000000..f4b0a82
--- /dev/null
+++ b/.agents/skills/react-native-design/SKILL.md
@@ -0,0 +1,440 @@
+---
+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
+
+## 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 (
+
+ Title
+ Subtitle text
+
+ );
+}
+```
+
+**Dynamic Styles:**
+
+```typescript
+interface CardProps {
+ variant: 'primary' | 'secondary';
+ disabled?: boolean;
+}
+
+function Card({ variant, disabled }: CardProps) {
+ return (
+
+ Content
+
+ );
+}
+
+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();
+
+function AppNavigator() {
+ return (
+
+
+
+ ({
+ title: `Item ${route.params.itemId}`,
+ })}
+ />
+
+
+
+ );
+}
+```
+
+**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();
+
+function TabNavigator() {
+ return (
+ ({
+ tabBarIcon: ({ focused, color, size }) => {
+ const icons: Record = {
+ Home: focused ? 'home' : 'home-outline',
+ Search: focused ? 'search' : 'search-outline',
+ Profile: focused ? 'person' : 'person-outline',
+ };
+ return ;
+ },
+ tabBarActiveTintColor: '#6366f1',
+ tabBarInactiveTintColor: '#9ca3af',
+ })}
+ >
+
+
+
+
+ );
+}
+```
+
+### 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 (
+
+
+
+ );
+}
+```
+
+**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 (
+
+
+ Drag me!
+
+
+ );
+}
+```
+
+### 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;
+```
+
+## 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 (
+ { scale.value = withSpring(0.97); }}
+ onPressOut={() => { scale.value = withSpring(1); }}
+ >
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+
+ );
+}
+
+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)
+
+## Resources
+
+- [React Native Documentation](https://reactnative.dev/)
+- [React Navigation](https://reactnavigation.org/)
+- [Reanimated Documentation](https://docs.swmansion.com/react-native-reanimated/)
+- [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/)
+- [Expo Documentation](https://docs.expo.dev/)
diff --git a/.agents/skills/react-native-design/references/navigation-patterns.md b/.agents/skills/react-native-design/references/navigation-patterns.md
new file mode 100644
index 0000000..bafc27c
--- /dev/null
+++ b/.agents/skills/react-native-design/references/navigation-patterns.md
@@ -0,0 +1,832 @@
+# 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;
+ Modal: { title: string };
+ Auth: NavigatorScreenParams;
+};
+
+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 =
+ NativeStackScreenProps;
+
+export type MainTabScreenProps =
+ CompositeScreenProps<
+ BottomTabScreenProps,
+ RootStackScreenProps
+ >;
+
+// 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>();
+}
+
+export function useTypedRoute() {
+ return useRoute>();
+}
+```
+
+## Stack Navigation
+
+### Basic Stack Navigator
+
+```typescript
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { RootStackParamList } from './types';
+
+const Stack = createNativeStackNavigator();
+
+function RootNavigator() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+```
+
+### Screen with Dynamic Options
+
+```typescript
+function DetailScreen({ route, navigation }: DetailScreenProps) {
+ const { itemId } = route.params;
+ const [item, setItem] = useState- (null);
+
+ useEffect(() => {
+ // Update header when data loads
+ if (item) {
+ navigation.setOptions({
+ title: item.title,
+ headerRight: () => (
+ shareItem(item)}>
+
+
+ ),
+ });
+ }
+ }, [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 {/* Content */};
+}
+```
+
+## 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();
+
+function MainTabNavigator() {
+ return (
+ ({
+ tabBarIcon: ({ focused, color, size }) => {
+ const icons: Record = {
+ Home: focused ? 'home' : 'home-outline',
+ Search: focused ? 'search' : 'search-outline',
+ Profile: focused ? 'person' : 'person-outline',
+ };
+ return (
+
+ );
+ },
+ 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' },
+ })}
+ >
+
+
+
+
+ );
+}
+```
+
+### 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 (
+
+ {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 (
+
+ );
+ })}
+
+ );
+}
+
+function TabBarButton({ label, isFocused, onPress, icon }) {
+ const scale = useSharedValue(1);
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ transform: [{ scale: scale.value }],
+ }));
+
+ return (
+ { scale.value = withSpring(0.9); }}
+ onPressOut={() => { scale.value = withSpring(1); }}
+ style={styles.tabButton}
+ >
+
+ {icon?.({
+ focused: isFocused,
+ color: isFocused ? '#6366f1' : '#9ca3af',
+ size: 24,
+ })}
+
+ {label}
+
+
+
+ );
+}
+
+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
+ }>
+ {/* screens */}
+
+```
+
+## Drawer Navigation
+
+```typescript
+import {
+ createDrawerNavigator,
+ DrawerContentScrollView,
+ DrawerItemList,
+ DrawerContentComponentProps,
+} from '@react-navigation/drawer';
+
+const Drawer = createDrawerNavigator();
+
+function CustomDrawerContent(props: DrawerContentComponentProps) {
+ return (
+
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+ Log Out
+
+
+
+ );
+}
+
+function DrawerNavigator() {
+ return (
+ }
+ screenOptions={{
+ drawerActiveBackgroundColor: '#ede9fe',
+ drawerActiveTintColor: '#6366f1',
+ drawerInactiveTintColor: '#4b5563',
+ drawerLabelStyle: { marginLeft: -20, fontSize: 15, fontWeight: '500' },
+ drawerStyle: { width: 280 },
+ }}
+ >
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+
+ );
+}
+```
+
+## Deep Linking
+
+### Configuration
+
+```typescript
+// navigation/linking.ts
+import { LinkingOptions } from '@react-navigation/native';
+import { RootStackParamList } from './types';
+
+export const linking: LinkingOptions = {
+ 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 (
+ }>
+
+
+ );
+}
+```
+
+### 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;
+ signOut: () => Promise;
+}
+
+const AuthContext = createContext(null!);
+
+function AuthProvider({ children }: { children: React.ReactNode }) {
+ const [user, setUser] = useState(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 (
+
+ {children}
+
+ );
+}
+
+function RootNavigator() {
+ const { user, isLoading } = useAuth();
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ {user ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+```
+
+### 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();
+
+ 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 ;
+ }
+
+ return (
+ {
+ AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
+ }}
+ >
+
+
+ );
+}
+```
+
+## Screen Transitions
+
+### Custom Animations
+
+```typescript
+import { TransitionPresets } from '@react-navigation/native-stack';
+
+
+ {/* Standard slide transition */}
+
+
+ {/* Modal with custom animation */}
+
+
+ {/* Full screen modal */}
+
+
+```
+
+### 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 (
+ (
+ navigation.navigate('Detail', { item })}>
+
+
+
+
+ {item.title}
+
+
+ )}
+ />
+ );
+}
+
+function DetailScreen({ route }) {
+ const { item } = route.params;
+
+ return (
+
+
+
+
+
+ {item.title}
+
+
+ );
+}
+
+// Navigator configuration
+
+
+ {
+ const { item } = route.params;
+ return [
+ { id: `item.${item.id}.photo`, animation: 'move' },
+ { id: `item.${item.id}.title`, animation: 'fade' },
+ ];
+ }}
+ />
+
+```
+
+## 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 (
+
+ {back && (
+
+
+
+ )}
+ {title}
+ {options.headerRight && (
+
+ {options.headerRight({ canGoBack: !!back })}
+
+ )}
+
+ );
+}
+
+// Usage
+ ,
+ }}
+>
+ {/* screens */}
+
+```
+
+### 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 (
+
+
+
+ Title
+
+
+
+
+ {/* Content */}
+
+
+ );
+}
+```
diff --git a/.agents/skills/react-native-design/references/reanimated-patterns.md b/.agents/skills/react-native-design/references/reanimated-patterns.md
new file mode 100644
index 0000000..7af151d
--- /dev/null
+++ b/.agents/skills/react-native-design/references/reanimated-patterns.md
@@ -0,0 +1,775 @@
+# 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 (
+
+ );
+}
+```
+
+### 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 (
+ ({
+ 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 (
+
+
+
+ );
+}
+```
+
+### 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 (
+
+
+
+ );
+}
+```
+
+### 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 (
+
+
+ {children}
+
+
+ );
+}
+```
+
+## 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 (
+
+ {children}
+
+ );
+}
+```
+
+### 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 (
+
+ {children}
+
+ );
+}
+```
+
+### Staggered List Animation
+
+```typescript
+function StaggeredList({ items }: { items: Item[] }) {
+ return (
+ item.id}
+ renderItem={({ item, 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 (
+
+ {item.title}
+
+ );
+}
+```
+
+### 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 (
+
+ {children}
+
+ );
+}
+```
+
+### 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 (
+
+ {children}
+
+ );
+}
+```
+
+## 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 (
+
+
+ Header
+
+ {
+ scrollY.value = event.contentOffset.y;
+ },
+ })}
+ scrollEventThrottle={16}
+ >
+ {/* Content */}
+
+
+ );
+}
+```
+
+### 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 (
+
+
+
+ );
+}
+```
+
+### 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 (
+
+ );
+}
+```
+
+### 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 (
+
+
+ {items.map((item) => (
+
+ Item {item}
+ removeItem(item)}>
+ Remove
+
+
+ ))}
+
+ );
+}
+```
+
+### 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 (
+
+
+
+ LIKE
+
+
+ NOPE
+
+ {/* Card content */}
+
+
+ );
+}
+```
+
+### 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 (
+ <>
+
+
+
+
+ {children}
+
+
+ >
+ );
+}
+```
+
+## 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 ;
+}
+```
diff --git a/.agents/skills/react-native-design/references/styling-patterns.md b/.agents/skills/react-native-design/references/styling-patterns.md
new file mode 100644
index 0000000..0b9c697
--- /dev/null
+++ b/.agents/skills/react-native-design/references/styling-patterns.md
@@ -0,0 +1,871 @@
+# 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({
+ 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;
+ variant?: 'default' | 'primary' | 'danger';
+}
+
+function Box({ style, variant = 'default' }: BoxProps) {
+ return (
+
+ );
+}
+
+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(lightTheme);
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const colorScheme = useColorScheme();
+ const theme = useMemo(
+ () => (colorScheme === 'dark' ? darkTheme : lightTheme),
+ [colorScheme]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ return useContext(ThemeContext);
+}
+```
+
+### Using Theme
+
+```typescript
+import { useTheme } from './theme';
+
+function ThemedCard() {
+ const theme = useTheme();
+
+ return (
+
+
+ Card Title
+
+
+ Card description text
+
+
+ );
+}
+```
+
+## 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 (
+
+ {/* Content */}
+
+ );
+}
+```
+
+### 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(values: Partial>): 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 (
+
+ {items.map((item) => (
+
+
+
+ ))}
+
+ );
+}
+```
+
+## 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;
+ edges?: ('top' | 'bottom' | 'left' | 'right')[];
+}
+
+export function Container({ children, style, edges = ['top', 'bottom'] }: ContainerProps) {
+ const insets = useSafeAreaInsets();
+ const theme = useTheme();
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+### Stack Components
+
+```typescript
+import { View, ViewStyle, StyleProp } from 'react-native';
+
+interface StackProps {
+ children: React.ReactNode;
+ spacing?: number;
+ style?: StyleProp;
+}
+
+export function VStack({ children, spacing = 8, style }: StackProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function HStack({ children, spacing = 8, style }: StackProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+// Usage
+function Example() {
+ return (
+
+
+
+
+ John Doe
+ john@example.com
+
+
+
+
+ );
+}
+```
+
+### Spacer
+
+```typescript
+import { View } from 'react-native';
+
+interface SpacerProps {
+ size?: number;
+ flex?: number;
+}
+
+export function Spacer({ size, flex }: SpacerProps) {
+ if (flex) {
+ return ;
+ }
+ return ;
+}
+
+// Usage
+
+ Left
+
+ Right
+
+```
+
+## 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 = {
+ 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 = {
+ 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 = {
+ 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 (
+
+ );
+}
+
+// Usage
+Heading
+Body text
+Label
+```
+
+## 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 (
+ { scale.value = withSpring(0.97); }}
+ onPressOut={() => { scale.value = withSpring(1); }}
+ disabled={disabled || loading}
+ >
+ {loading ? (
+
+ ) : (
+ <>
+ {leftIcon}
+
+ {title}
+
+ {rightIcon}
+ >
+ )}
+
+ );
+}
+
+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 (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ {leftIcon && {leftIcon}}
+ setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ {...props}
+ />
+ {rightIcon && {rightIcon}}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+}
+
+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 (
+ item.id}
+ renderItem={({ item, index }) => (
+
+ {item.title}
+ {item.subtitle}
+
+ )}
+ ItemSeparatorComponent={() => }
+ ListHeaderComponent={() => (
+ List Header
+ )}
+ ListEmptyComponent={() => (
+
+ No items found
+
+ )}
+ 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,
+ },
+});
+```
diff --git a/skills-lock.json b/skills-lock.json
new file mode 100644
index 0000000..96142d8
--- /dev/null
+++ b/skills-lock.json
@@ -0,0 +1,10 @@
+{
+ "version": 1,
+ "skills": {
+ "react-native-design": {
+ "source": "wshobson/agents",
+ "sourceType": "github",
+ "computedHash": "26c9e2198b8534330ff7862a6c33d2eee07e2302810b80473deaced469db1b12"
+ }
+ }
+}