Initial commit
This commit is contained in:
233
commands/app-scaffold.md
Normal file
233
commands/app-scaffold.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# React Native App Scaffolding
|
||||
|
||||
Generate production-ready React Native application structure.
|
||||
|
||||
## Task
|
||||
|
||||
You are a React Native expert. Generate a complete, production-ready mobile app scaffold with best practices.
|
||||
|
||||
### Steps:
|
||||
|
||||
1. **Ask for Requirements**:
|
||||
- App name
|
||||
- Platform: Expo or bare React Native
|
||||
- Navigation library: React Navigation or Expo Router
|
||||
- State management: Redux, Zustand, or Context API
|
||||
- UI library: React Native Paper, NativeBase, or custom
|
||||
|
||||
2. **Generate Project Structure**:
|
||||
|
||||
```
|
||||
my-app/
|
||||
├── app.json / package.json
|
||||
├── babel.config.js
|
||||
├── tsconfig.json
|
||||
├── App.tsx
|
||||
├── src/
|
||||
│ ├── screens/
|
||||
│ │ ├── HomeScreen.tsx
|
||||
│ │ ├── ProfileScreen.tsx
|
||||
│ │ └── SettingsScreen.tsx
|
||||
│ ├── components/
|
||||
│ │ ├── common/
|
||||
│ │ │ ├── Button.tsx
|
||||
│ │ │ ├── Input.tsx
|
||||
│ │ │ └── Card.tsx
|
||||
│ │ └── specific/
|
||||
│ ├── navigation/
|
||||
│ │ ├── AppNavigator.tsx
|
||||
│ │ ├── AuthNavigator.tsx
|
||||
│ │ └── types.ts
|
||||
│ ├── store/ # Redux/Zustand
|
||||
│ │ ├── slices/
|
||||
│ │ ├── hooks.ts
|
||||
│ │ └── index.ts
|
||||
│ ├── services/
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── client.ts
|
||||
│ │ │ └── endpoints/
|
||||
│ │ └── storage/
|
||||
│ ├── hooks/
|
||||
│ │ ├── useAuth.ts
|
||||
│ │ ├── useAsync.ts
|
||||
│ │ └── useDebounce.ts
|
||||
│ ├── utils/
|
||||
│ │ ├── validation.ts
|
||||
│ │ └── formatting.ts
|
||||
│ ├── constants/
|
||||
│ │ ├── colors.ts
|
||||
│ │ ├── sizes.ts
|
||||
│ │ └── api.ts
|
||||
│ ├── types/
|
||||
│ │ └── index.ts
|
||||
│ └── assets/
|
||||
│ ├── images/
|
||||
│ └── fonts/
|
||||
├── __tests__/
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
3. **Generate App Entry Point** (Expo):
|
||||
|
||||
```typescript
|
||||
import 'react-native-gesture-handler';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { Provider } from 'react-redux';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { store } from './src/store';
|
||||
import AppNavigator from './src/navigation/AppNavigator';
|
||||
import { ErrorBoundary } from './src/components/ErrorBoundary';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SafeAreaProvider>
|
||||
<NavigationContainer>
|
||||
<AppNavigator />
|
||||
<StatusBar style="auto" />
|
||||
</NavigationContainer>
|
||||
</SafeAreaProvider>
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
4. **Generate Navigation**:
|
||||
|
||||
```typescript
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { HomeScreen, ProfileScreen, SettingsScreen } from '../screens';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
const Tab = createBottomTabNavigator();
|
||||
|
||||
function TabNavigator() {
|
||||
return (
|
||||
<Tab.Navigator screenOptions={{ headerShown: false }}>
|
||||
<Tab.Screen name="Home" component={HomeScreen} />
|
||||
<Tab.Screen name="Profile" component={ProfileScreen} />
|
||||
<Tab.Screen name="Settings" component={SettingsScreen} />
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AppNavigator() {
|
||||
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
|
||||
|
||||
return (
|
||||
<Stack.Navigator>
|
||||
{isAuthenticated ? (
|
||||
<Stack.Screen name="Main" component={TabNavigator} />
|
||||
) : (
|
||||
<Stack.Screen name="Auth" component={AuthScreen} />
|
||||
)}
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
5. **Generate API Client**:
|
||||
|
||||
```typescript
|
||||
import axios from 'axios';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { API_BASE_URL } from '../constants/api';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Request interceptor
|
||||
api.interceptors.request.use(async (config) => {
|
||||
const token = await AsyncStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
if (error.response?.status === 401) {
|
||||
await AsyncStorage.removeItem('auth_token');
|
||||
// Navigate to login
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
```
|
||||
|
||||
6. **Generate package.json**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-app",
|
||||
"version": "1.0.0",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest",
|
||||
"lint": "eslint .",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "~49.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.0",
|
||||
"@react-navigation/native": "^6.1.0",
|
||||
"@react-navigation/native-stack": "^6.9.0",
|
||||
"@react-navigation/bottom-tabs": "^6.5.0",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "~3.22.0",
|
||||
"@reduxjs/toolkit": "^1.9.0",
|
||||
"react-redux": "^8.1.0",
|
||||
"@tanstack/react-query": "^4.35.0",
|
||||
"axios": "^1.5.0",
|
||||
"@react-native-async-storage/async-storage": "1.18.2",
|
||||
"react-native-gesture-handler": "~2.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~18.2.14",
|
||||
"typescript": "^5.1.3",
|
||||
"@testing-library/react-native": "^12.3.0",
|
||||
"jest": "^29.2.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices Included:
|
||||
|
||||
- TypeScript configuration
|
||||
- Navigation setup
|
||||
- State management
|
||||
- API client with interceptors
|
||||
- Error boundaries
|
||||
- Proper folder structure
|
||||
- AsyncStorage for persistence
|
||||
- Testing setup
|
||||
- ESLint and TypeScript
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```
|
||||
User: "Scaffold Expo app with Redux and React Navigation"
|
||||
Result: Complete Expo project with all configurations
|
||||
```
|
||||
256
commands/build-config.md
Normal file
256
commands/build-config.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Build Configuration
|
||||
|
||||
Generate build configurations for iOS and Android.
|
||||
|
||||
## Task
|
||||
|
||||
You are a React Native build expert. Generate complete build configurations for production releases.
|
||||
|
||||
### Steps:
|
||||
|
||||
1. **Ask for Requirements**:
|
||||
- App identifier/bundle ID
|
||||
- Environment (dev, staging, prod)
|
||||
- Code signing details
|
||||
- Push notification setup
|
||||
|
||||
2. **Generate app.json** (Expo):
|
||||
|
||||
```json
|
||||
{
|
||||
"expo": {
|
||||
"name": "My App",
|
||||
"slug": "my-app",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.company.myapp",
|
||||
"buildNumber": "1",
|
||||
"infoPlist": {
|
||||
"NSCameraUsageDescription": "This app uses the camera to...",
|
||||
"NSPhotoLibraryUsageDescription": "This app accesses photos to...",
|
||||
"NSLocationWhenInUseUsageDescription": "This app uses location to..."
|
||||
},
|
||||
"config": {
|
||||
"googleMapsApiKey": "YOUR_KEY_HERE"
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.company.myapp",
|
||||
"versionCode": 1,
|
||||
"permissions": [
|
||||
"CAMERA",
|
||||
"READ_EXTERNAL_STORAGE",
|
||||
"WRITE_EXTERNAL_STORAGE",
|
||||
"ACCESS_FINE_LOCATION"
|
||||
],
|
||||
"config": {
|
||||
"googleMaps": {
|
||||
"apiKey": "YOUR_KEY_HERE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"expo-camera",
|
||||
"expo-location",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
"icon": "./assets/notification-icon.png",
|
||||
"color": "#ffffff"
|
||||
}
|
||||
]
|
||||
],
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "your-project-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Generate eas.json** (Expo EAS Build):
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 5.0.0"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal",
|
||||
"ios": {
|
||||
"simulator": false,
|
||||
"resourceClass": "m-medium"
|
||||
},
|
||||
"android": {
|
||||
"buildType": "apk",
|
||||
"gradleCommand": ":app:assembleRelease"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"ios": {
|
||||
"resourceClass": "m-medium",
|
||||
"autoIncrement BuildNumber": true
|
||||
},
|
||||
"android": {
|
||||
"buildType": "app-bundle",
|
||||
"autoIncrement VersionCode": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {
|
||||
"ios": {
|
||||
"appleId": "your@email.com",
|
||||
"ascAppId": "1234567890",
|
||||
"appleTeamId": "ABCD1234"
|
||||
},
|
||||
"android": {
|
||||
"serviceAccountKeyPath": "./service-account.json",
|
||||
"track": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Generate Environment Variables**:
|
||||
|
||||
```typescript
|
||||
// config/env.ts
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
const ENV = {
|
||||
dev: {
|
||||
apiUrl: 'http://localhost:3000',
|
||||
environment: 'development',
|
||||
},
|
||||
staging: {
|
||||
apiUrl: 'https://staging-api.example.com',
|
||||
environment: 'staging',
|
||||
},
|
||||
prod: {
|
||||
apiUrl: 'https://api.example.com',
|
||||
environment: 'production',
|
||||
},
|
||||
};
|
||||
|
||||
const getEnvVars = (env = Constants.manifest?.releaseChannel) => {
|
||||
if (__DEV__) return ENV.dev;
|
||||
if (env === 'staging') return ENV.staging;
|
||||
return ENV.prod;
|
||||
};
|
||||
|
||||
export default getEnvVars();
|
||||
```
|
||||
|
||||
5. **Generate Build Scripts**:
|
||||
|
||||
```json
|
||||
// package.json scripts
|
||||
{
|
||||
"scripts": {
|
||||
"build:dev:ios": "eas build --profile development --platform ios",
|
||||
"build:dev:android": "eas build --profile development --platform android",
|
||||
"build:preview:ios": "eas build --profile preview --platform ios",
|
||||
"build:preview:android": "eas build --profile preview --platform android",
|
||||
"build:prod:ios": "eas build --profile production --platform ios",
|
||||
"build:prod:android": "eas build --profile production --platform android",
|
||||
"build:prod:all": "eas build --profile production --platform all",
|
||||
"submit:ios": "eas submit --platform ios",
|
||||
"submit:android": "eas submit --platform android"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. **Generate CI/CD Configuration** (GitHub Actions):
|
||||
|
||||
```yaml
|
||||
name: EAS Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
expo-version: latest
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build iOS
|
||||
run: eas build --profile production --platform ios --non-interactive
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
- name: Build Android
|
||||
run: eas build --profile production --platform android --non-interactive
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
- name: Submit to stores
|
||||
run: |
|
||||
eas submit --platform ios --non-interactive
|
||||
eas submit --platform android --non-interactive
|
||||
if: github.ref == 'refs/heads/main'
|
||||
```
|
||||
|
||||
### Best Practices Included:
|
||||
|
||||
- Multi-environment configuration
|
||||
- Proper permissions setup
|
||||
- Code signing automation
|
||||
- CI/CD integration
|
||||
- Auto-increment version numbers
|
||||
- Proper asset management
|
||||
- Push notification setup
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```
|
||||
User: "Set up production build for iOS and Android"
|
||||
Result: Complete build configuration with EAS, CI/CD, environment management
|
||||
```
|
||||
289
commands/screen-generate.md
Normal file
289
commands/screen-generate.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Screen Generator
|
||||
|
||||
Generate React Native screens with navigation integration.
|
||||
|
||||
## Task
|
||||
|
||||
You are a React Native expert. Generate complete, production-ready screens with proper typing and navigation.
|
||||
|
||||
### Steps:
|
||||
|
||||
1. **Ask for Requirements**:
|
||||
- Screen name and purpose
|
||||
- Required data/API calls
|
||||
- Form inputs (if any)
|
||||
- Navigation params
|
||||
|
||||
2. **Generate Screen Component**:
|
||||
|
||||
```typescript
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
FlatList,
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '../../services/api';
|
||||
import { Product } from '../../types';
|
||||
import { ProductCard } from '../../components/ProductCard';
|
||||
import { RootStackParamList } from '../../navigation/types';
|
||||
|
||||
type Props = NativeStackScreenProps<RootStackParamList, 'ProductList'>;
|
||||
|
||||
export function ProductListScreen({ navigation, route }: Props) {
|
||||
const { category } = route.params;
|
||||
|
||||
// Fetch data with React Query
|
||||
const {
|
||||
data: products,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
isRefetching,
|
||||
} = useQuery({
|
||||
queryKey: ['products', category],
|
||||
queryFn: () => api.getProducts({ category }),
|
||||
});
|
||||
|
||||
// Handle item press
|
||||
const handleProductPress = (productId: string) => {
|
||||
navigation.navigate('ProductDetail', { productId });
|
||||
};
|
||||
|
||||
// Render loading state
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Render error state
|
||||
if (error) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<Text style={styles.errorText}>
|
||||
Failed to load products
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Render list
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<FlatList
|
||||
data={products}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<ProductCard
|
||||
product={item}
|
||||
onPress={() => handleProductPress(item.id)}
|
||||
/>
|
||||
)}
|
||||
contentContainerStyle={styles.listContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefetching}
|
||||
onRefresh={refetch}
|
||||
/>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
<Text style={styles.emptyText}>
|
||||
No products found
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
centerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
listContent: {
|
||||
padding: 16,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 16,
|
||||
color: 'red',
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
marginTop: 32,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
3. **Generate Form Screen**:
|
||||
|
||||
```typescript
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { Input } from '../../components/Input';
|
||||
import { Button } from '../../components/Button';
|
||||
import { api } from '../../services/api';
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
email: z.string().email('Invalid email'),
|
||||
phone: z.string().regex(/^\d{10}$/, 'Invalid phone number'),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export function ProfileEditScreen({ navigation }: Props) {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
try {
|
||||
await api.updateProfile(data);
|
||||
navigation.goBack();
|
||||
} catch (error) {
|
||||
console.error('Failed to update profile', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.content}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
label="Name"
|
||||
value={value}
|
||||
onChangeText={onChange}
|
||||
error={errors.name?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="email"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
label="Email"
|
||||
value={value}
|
||||
onChangeText={onChange}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
error={errors.email?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="phone"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Input
|
||||
label="Phone"
|
||||
value={value}
|
||||
onChangeText={onChange}
|
||||
keyboardType="phone-pad"
|
||||
error={errors.phone?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Save"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
/>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
content: {
|
||||
padding: 16,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
4. **Generate Navigation Types**:
|
||||
|
||||
```typescript
|
||||
// navigation/types.ts
|
||||
export type RootStackParamList = {
|
||||
ProductList: { category: string };
|
||||
ProductDetail: { productId: string };
|
||||
ProfileEdit: undefined;
|
||||
};
|
||||
|
||||
declare global {
|
||||
namespace ReactNavigation {
|
||||
interface RootParamList extends RootStackParamList {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices Included:
|
||||
|
||||
- TypeScript types
|
||||
- React Query for data fetching
|
||||
- React Hook Form for forms
|
||||
- Zod validation
|
||||
- Proper error handling
|
||||
- Loading states
|
||||
- Pull-to-refresh
|
||||
- Keyboard handling
|
||||
- Responsive styling
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```
|
||||
User: "Generate product list screen with pull-to-refresh"
|
||||
Result: Complete screen with navigation, data fetching, error handling
|
||||
```
|
||||
Reference in New Issue
Block a user