mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 09:37:15 +00:00
feat(ui-design): add comprehensive UI/UX design plugin v1.0.0
New plugin covering mobile (iOS, Android, React Native) and web applications with modern design patterns, accessibility, and design systems. Components: - 9 skills: design-system-patterns, accessibility-compliance, responsive-design, mobile-ios-design, mobile-android-design, react-native-design, web-component-design, interaction-design, visual-design-foundations - 4 commands: design-review, create-component, accessibility-audit, design-system-setup - 3 agents: ui-designer, accessibility-expert, design-system-architect Marketplace updated: - Version bumped to 1.3.4 - 102 agents (+3), 116 skills (+9)
This commit is contained in:
433
plugins/ui-design/skills/react-native-design/SKILL.md
Normal file
433
plugins/ui-design/skills/react-native-design/SKILL.md
Normal file
@@ -0,0 +1,433 @@
|
||||
---
|
||||
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 (
|
||||
<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;
|
||||
```
|
||||
|
||||
## 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)
|
||||
|
||||
## 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/)
|
||||
Reference in New Issue
Block a user