create-rn-clean-architecture 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/bin/cli.js +8 -0
- package/package.json +42 -0
- package/src/checkDependencies.js +108 -0
- package/src/createDirectories.js +38 -0
- package/src/generateFiles.js +114 -0
- package/src/index.js +125 -0
- package/templates/PROJECT_ARCHITECTURE.md +385 -0
- package/templates/constants/COLORS.ts.template +36 -0
- package/templates/constants/SIZINGS.ts.template +51 -0
- package/templates/constants/splash.constants.ts.template +1 -0
- package/templates/hooks/useSplashNavigation.ts.template +16 -0
- package/templates/navigation/BottomTabNavigation.tsx.template +17 -0
- package/templates/navigation/StackNavigation.tsx.template +18 -0
- package/templates/root/App.tsx.template +13 -0
- package/templates/screens/HomeScreen.tsx.template +18 -0
- package/templates/screens/ProfileScreen.tsx.template +18 -0
- package/templates/screens/SettingsScreen.tsx.template +18 -0
- package/templates/screens/SplashScreen.tsx.template +21 -0
- package/templates/styles/home.styles.ts.template +26 -0
- package/templates/styles/profile.styles.ts.template +26 -0
- package/templates/styles/settings.styles.ts.template +26 -0
- package/templates/styles/splash.styles.ts.template +25 -0
- package/templates/types/Type.ts.template +15 -0
- package/templates/utils/Util.ts.template +6 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const { createDirectories } = require('./createDirectories');
|
|
6
|
+
const { generateFiles } = require('./generateFiles');
|
|
7
|
+
const { checkAndInstallDependencies } = require('./checkDependencies');
|
|
8
|
+
|
|
9
|
+
async function scaffold() {
|
|
10
|
+
console.log(chalk.blue.bold('\nšļø React Native Clean Architecture Scaffold\n'));
|
|
11
|
+
|
|
12
|
+
// Check if in a project directory (has package.json)
|
|
13
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
14
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
15
|
+
console.log(chalk.yellow('ā ļø No package.json found in current directory.'));
|
|
16
|
+
console.log(chalk.gray(' This tool is typically used in a React Native project,'));
|
|
17
|
+
console.log(chalk.gray(' but will continue anyway.\n'));
|
|
18
|
+
} else {
|
|
19
|
+
// Check if it's a React Native or Expo project
|
|
20
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
21
|
+
const isReactNative =
|
|
22
|
+
packageJson.dependencies?.['react-native'] ||
|
|
23
|
+
packageJson.devDependencies?.['react-native'];
|
|
24
|
+
const isExpo =
|
|
25
|
+
packageJson.dependencies?.['expo'] ||
|
|
26
|
+
packageJson.devDependencies?.['expo'];
|
|
27
|
+
|
|
28
|
+
if (!isReactNative && !isExpo) {
|
|
29
|
+
console.log(chalk.yellow('ā ļø This doesn\'t appear to be a React Native or Expo project.'));
|
|
30
|
+
const { continueAnyway } = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'confirm',
|
|
33
|
+
name: 'continueAnyway',
|
|
34
|
+
message: 'Continue anyway?',
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
if (!continueAnyway) {
|
|
40
|
+
console.log(chalk.gray('Cancelled.'));
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
} else if (isExpo) {
|
|
44
|
+
console.log(chalk.cyan('ā Expo project detected!\n'));
|
|
45
|
+
} else if (isReactNative) {
|
|
46
|
+
console.log(chalk.cyan('ā React Native CLI project detected!\n'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if src directory already exists
|
|
51
|
+
const srcPath = path.join(process.cwd(), 'src');
|
|
52
|
+
if (fs.existsSync(srcPath)) {
|
|
53
|
+
const { overwrite } = await inquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'confirm',
|
|
56
|
+
name: 'overwrite',
|
|
57
|
+
message: 'src/ directory already exists. Continue and potentially overwrite files?',
|
|
58
|
+
default: false,
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
if (!overwrite) {
|
|
63
|
+
console.log(chalk.gray('Cancelled.'));
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Ask for confirmation to proceed
|
|
69
|
+
const { confirm } = await inquirer.prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
name: 'confirm',
|
|
73
|
+
message: 'This will create a complete React Native app with navigation and sample screens. Continue?',
|
|
74
|
+
default: true,
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
if (!confirm) {
|
|
79
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Create directories
|
|
85
|
+
console.log(chalk.cyan('\nš Creating directories...'));
|
|
86
|
+
await createDirectories();
|
|
87
|
+
|
|
88
|
+
// Generate all files
|
|
89
|
+
console.log(chalk.cyan('\nš Generating files...'));
|
|
90
|
+
await generateFiles();
|
|
91
|
+
|
|
92
|
+
// Check and install dependencies
|
|
93
|
+
await checkAndInstallDependencies();
|
|
94
|
+
|
|
95
|
+
// Success message
|
|
96
|
+
console.log(chalk.green.bold('\nā
Clean architecture structure created successfully!\n'));
|
|
97
|
+
console.log(chalk.green('š Your app is ready to run!\n'));
|
|
98
|
+
|
|
99
|
+
console.log(chalk.white('š What was created:'));
|
|
100
|
+
console.log(chalk.gray(' ā Complete directory structure'));
|
|
101
|
+
console.log(chalk.gray(' ā 4 sample screens (Splash, Home, Profile, Settings)'));
|
|
102
|
+
console.log(chalk.gray(' ā Navigation setup (Stack + Bottom Tabs)'));
|
|
103
|
+
console.log(chalk.gray(' ā Hooks, styles, and constants'));
|
|
104
|
+
console.log(chalk.gray(' ā Updated App.tsx ready to run'));
|
|
105
|
+
console.log(chalk.gray(' ā PROJECT_ARCHITECTURE.md guide\n'));
|
|
106
|
+
|
|
107
|
+
console.log(chalk.white('š Next steps:'));
|
|
108
|
+
console.log(chalk.gray(' 1. Review PROJECT_ARCHITECTURE.md for complete guidelines'));
|
|
109
|
+
console.log(chalk.gray(' 2. Run: npm start (or yarn start)'));
|
|
110
|
+
console.log(chalk.gray(' 3. Your app now has complete navigation and sample screens!'));
|
|
111
|
+
console.log(chalk.gray(' 4. Customize the screens and styles to match your needs\n'));
|
|
112
|
+
|
|
113
|
+
console.log(chalk.cyan('š Learn more:'));
|
|
114
|
+
console.log(chalk.gray(' - All screens follow clean architecture patterns'));
|
|
115
|
+
console.log(chalk.gray(' - Logic is separated in hooks'));
|
|
116
|
+
console.log(chalk.gray(' - Styles use centralized constants'));
|
|
117
|
+
console.log(chalk.gray(' - Ready to build upon!\n'));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.log(chalk.red('\nā Error creating structure:'), error.message);
|
|
120
|
+
console.error(error);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { scaffold };
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# React Native Clean Architecture Guide
|
|
2
|
+
|
|
3
|
+
## šÆ Architecture Principles
|
|
4
|
+
|
|
5
|
+
This project follows **Clean Architecture** principles to ensure:
|
|
6
|
+
- **Separation of Concerns**: Each part of the code has a single responsibility
|
|
7
|
+
- **Maintainability**: Easy to update and modify code
|
|
8
|
+
- **Testability**: Components and logic can be tested independently
|
|
9
|
+
- **Scalability**: Structure supports growth without refactoring
|
|
10
|
+
|
|
11
|
+
## š Directory Structure
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
src/
|
|
15
|
+
āāā assets/ # Static resources
|
|
16
|
+
ā āāā images/ # Image files
|
|
17
|
+
ā āāā fonts/ # Custom fonts
|
|
18
|
+
ā āāā icons/ # Icon files
|
|
19
|
+
āāā components/ # Reusable UI components
|
|
20
|
+
ā āāā common/ # Shared components across the app
|
|
21
|
+
āāā constants/ # Application constants
|
|
22
|
+
ā āāā sizings/ # Size-related constants
|
|
23
|
+
ā ā āāā SIZINGS.ts
|
|
24
|
+
ā āāā styles/ # Color and theme constants
|
|
25
|
+
ā āāā COLORS.ts
|
|
26
|
+
āāā hooks/ # Custom React hooks (business logic)
|
|
27
|
+
āāā navigation/ # Navigation configuration
|
|
28
|
+
ā āāā stack/ # Stack navigator configurations
|
|
29
|
+
ā āāā bottom/ # Bottom tab navigator configurations
|
|
30
|
+
āāā screens/ # Screen components (UI only)
|
|
31
|
+
āāā state/ # State management
|
|
32
|
+
ā āāā zustand/ # Zustand stores
|
|
33
|
+
ā āāā context/ # React Context providers
|
|
34
|
+
āāā styles/ # StyleSheet definitions
|
|
35
|
+
āāā types/ # TypeScript type definitions
|
|
36
|
+
ā āāā Type.ts
|
|
37
|
+
āāā utils/ # Utility functions
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## šļø Architecture Patterns
|
|
41
|
+
|
|
42
|
+
### 1. Screen Component Pattern
|
|
43
|
+
|
|
44
|
+
**Screens are PRESENTATION ONLY** - they only render UI and handle user interactions by calling hook functions.
|
|
45
|
+
|
|
46
|
+
**File Structure:**
|
|
47
|
+
```
|
|
48
|
+
src/
|
|
49
|
+
āāā screens/
|
|
50
|
+
ā āāā home/
|
|
51
|
+
ā āāā HomeScreen.tsx
|
|
52
|
+
āāā hooks/
|
|
53
|
+
ā āāā home/
|
|
54
|
+
ā āāā useHomeLogic.ts
|
|
55
|
+
āāā styles/
|
|
56
|
+
ā āāā home/
|
|
57
|
+
ā āāā home.styles.ts
|
|
58
|
+
āāā constants/
|
|
59
|
+
āāā home/
|
|
60
|
+
āāā home.constants.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Example HomeScreen.tsx:**
|
|
64
|
+
```typescript
|
|
65
|
+
import React from 'react';
|
|
66
|
+
import { View, Text, TouchableOpacity } from 'react-native';
|
|
67
|
+
import { useHomeLogic } from '../../hooks/home/useHomeLogic';
|
|
68
|
+
import { styles } from '../../styles/home/home.styles';
|
|
69
|
+
|
|
70
|
+
export const HomeScreen: React.FC = () => {
|
|
71
|
+
const { data, loading, handleRefresh } = useHomeLogic();
|
|
72
|
+
|
|
73
|
+
if (loading) {
|
|
74
|
+
return <Text>Loading...</Text>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<View style={styles.container}>
|
|
79
|
+
<Text style={styles.title}>{data.title}</Text>
|
|
80
|
+
<TouchableOpacity onPress={handleRefresh}>
|
|
81
|
+
<Text>Refresh</Text>
|
|
82
|
+
</TouchableOpacity>
|
|
83
|
+
</View>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Example useHomeLogic.ts:**
|
|
89
|
+
```typescript
|
|
90
|
+
import { useState, useEffect } from 'react';
|
|
91
|
+
import { HOME_CONSTANTS } from '../../constants/home/home.constants';
|
|
92
|
+
|
|
93
|
+
export const useHomeLogic = () => {
|
|
94
|
+
const [data, setData] = useState({ title: HOME_CONSTANTS.DEFAULT_TITLE });
|
|
95
|
+
const [loading, setLoading] = useState(false);
|
|
96
|
+
|
|
97
|
+
const handleRefresh = async () => {
|
|
98
|
+
setLoading(true);
|
|
99
|
+
// Business logic here
|
|
100
|
+
setLoading(false);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
// Initial data fetch
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
data,
|
|
109
|
+
loading,
|
|
110
|
+
handleRefresh,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Example home.styles.ts:**
|
|
116
|
+
```typescript
|
|
117
|
+
import { StyleSheet } from 'react-native';
|
|
118
|
+
import { COLORS } from '../../constants/styles/COLORS';
|
|
119
|
+
import { SIZINGS } from '../../constants/sizings/SIZINGS';
|
|
120
|
+
|
|
121
|
+
export const styles = StyleSheet.create({
|
|
122
|
+
container: {
|
|
123
|
+
flex: 1,
|
|
124
|
+
backgroundColor: COLORS.colors.background,
|
|
125
|
+
padding: SIZINGS.spacing.md,
|
|
126
|
+
},
|
|
127
|
+
title: {
|
|
128
|
+
fontSize: SIZINGS.fontSizings.large_3,
|
|
129
|
+
color: COLORS.colors.text,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Example home.constants.ts:**
|
|
135
|
+
```typescript
|
|
136
|
+
export const HOME_CONSTANTS = {
|
|
137
|
+
DEFAULT_TITLE: 'Welcome Home',
|
|
138
|
+
MAX_ITEMS: 10,
|
|
139
|
+
REFRESH_INTERVAL: 5000,
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2. Component Pattern
|
|
144
|
+
|
|
145
|
+
**Components should be PURE and REUSABLE**.
|
|
146
|
+
|
|
147
|
+
**File Structure:**
|
|
148
|
+
```
|
|
149
|
+
src/
|
|
150
|
+
āāā components/
|
|
151
|
+
ā āāā button/
|
|
152
|
+
ā āāā CustomButton.tsx
|
|
153
|
+
āāā styles/
|
|
154
|
+
āāā components/
|
|
155
|
+
āāā button.styles.ts
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Example CustomButton.tsx:**
|
|
159
|
+
```typescript
|
|
160
|
+
import React from 'react';
|
|
161
|
+
import { TouchableOpacity, Text } from 'react-native';
|
|
162
|
+
import { styles } from '../../styles/components/button.styles';
|
|
163
|
+
|
|
164
|
+
interface CustomButtonProps {
|
|
165
|
+
title: string;
|
|
166
|
+
onPress: () => void;
|
|
167
|
+
variant?: 'primary' | 'secondary';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const CustomButton: React.FC<CustomButtonProps> = ({
|
|
171
|
+
title,
|
|
172
|
+
onPress,
|
|
173
|
+
variant = 'primary',
|
|
174
|
+
}) => {
|
|
175
|
+
return (
|
|
176
|
+
<TouchableOpacity
|
|
177
|
+
style={[styles.button, styles[variant]]}
|
|
178
|
+
onPress={onPress}
|
|
179
|
+
>
|
|
180
|
+
<Text style={styles.text}>{title}</Text>
|
|
181
|
+
</TouchableOpacity>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 3. State Management Pattern
|
|
187
|
+
|
|
188
|
+
Use **Zustand** for global state and **React Context** for feature-specific state.
|
|
189
|
+
|
|
190
|
+
**Zustand Store Example:**
|
|
191
|
+
```typescript
|
|
192
|
+
// src/state/zustand/authStore.ts
|
|
193
|
+
import { create } from 'zustand';
|
|
194
|
+
|
|
195
|
+
interface AuthState {
|
|
196
|
+
user: User | null;
|
|
197
|
+
isAuthenticated: boolean;
|
|
198
|
+
login: (user: User) => void;
|
|
199
|
+
logout: () => void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const useAuthStore = create<AuthState>((set) => ({
|
|
203
|
+
user: null,
|
|
204
|
+
isAuthenticated: false,
|
|
205
|
+
login: (user) => set({ user, isAuthenticated: true }),
|
|
206
|
+
logout: () => set({ user: null, isAuthenticated: false }),
|
|
207
|
+
}));
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 4. Navigation Pattern
|
|
211
|
+
|
|
212
|
+
**Stack Navigation Example:**
|
|
213
|
+
```typescript
|
|
214
|
+
// src/navigation/stack/MainStack.tsx
|
|
215
|
+
import { createStackNavigator } from '@react-navigation/stack';
|
|
216
|
+
import { HomeScreen } from '../../screens/home/HomeScreen';
|
|
217
|
+
import { RootStackParamList } from '../../types/Type';
|
|
218
|
+
|
|
219
|
+
const Stack = createStackNavigator<RootStackParamList>();
|
|
220
|
+
|
|
221
|
+
export const MainStack = () => {
|
|
222
|
+
return (
|
|
223
|
+
<Stack.Navigator>
|
|
224
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
225
|
+
</Stack.Navigator>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## š« NO Barrel Exports
|
|
231
|
+
|
|
232
|
+
**NEVER use index.ts files for re-exporting.**
|
|
233
|
+
|
|
234
|
+
ā **Bad:**
|
|
235
|
+
```typescript
|
|
236
|
+
// src/components/index.ts
|
|
237
|
+
export { CustomButton } from './button/CustomButton';
|
|
238
|
+
export { CustomInput } from './input/CustomInput';
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
ā
**Good:**
|
|
242
|
+
```typescript
|
|
243
|
+
// Direct imports
|
|
244
|
+
import { CustomButton } from '../../components/button/CustomButton';
|
|
245
|
+
import { CustomInput } from '../../components/input/CustomInput';
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## š Naming Conventions
|
|
249
|
+
|
|
250
|
+
### Files
|
|
251
|
+
- **Screens**: `PascalCase` + `Screen` suffix ā `HomeScreen.tsx`
|
|
252
|
+
- **Components**: `PascalCase` ā `CustomButton.tsx`
|
|
253
|
+
- **Hooks**: `camelCase` + `use` prefix ā `useHomeLogic.ts`
|
|
254
|
+
- **Styles**: `camelCase` + `.styles` suffix ā `home.styles.ts`
|
|
255
|
+
- **Constants**: `camelCase` + `.constants` suffix ā `home.constants.ts`
|
|
256
|
+
- **Types**: `PascalCase` ā `Type.ts`
|
|
257
|
+
|
|
258
|
+
### Variables
|
|
259
|
+
- **Components**: `PascalCase` ā `CustomButton`
|
|
260
|
+
- **Hooks**: `camelCase` + `use` prefix ā `useHomeLogic`
|
|
261
|
+
- **Constants**: `SCREAMING_SNAKE_CASE` ā `API_BASE_URL`
|
|
262
|
+
- **Functions**: `camelCase` ā `handlePress`
|
|
263
|
+
- **Variables**: `camelCase` ā `userData`
|
|
264
|
+
|
|
265
|
+
## šØ Styling Guidelines
|
|
266
|
+
|
|
267
|
+
1. **Always use constants** from `COLORS.ts` and `SIZINGS.ts`
|
|
268
|
+
2. **Create separate style files** for each screen/component
|
|
269
|
+
3. **Use StyleSheet.create()** for performance
|
|
270
|
+
4. **Group related styles** together
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
export const styles = StyleSheet.create({
|
|
274
|
+
// Container styles
|
|
275
|
+
container: { ... },
|
|
276
|
+
wrapper: { ... },
|
|
277
|
+
|
|
278
|
+
// Text styles
|
|
279
|
+
title: { ... },
|
|
280
|
+
subtitle: { ... },
|
|
281
|
+
|
|
282
|
+
// Button styles
|
|
283
|
+
button: { ... },
|
|
284
|
+
buttonText: { ... },
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## š§ Best Practices
|
|
289
|
+
|
|
290
|
+
### 1. Keep Screens Simple
|
|
291
|
+
- Screens should ONLY contain JSX and call hook functions
|
|
292
|
+
- No business logic in screens
|
|
293
|
+
- No direct API calls in screens
|
|
294
|
+
- No complex calculations in screens
|
|
295
|
+
|
|
296
|
+
### 2. Use Custom Hooks
|
|
297
|
+
- All business logic goes in custom hooks
|
|
298
|
+
- Hooks handle state, side effects, and data fetching
|
|
299
|
+
- Hooks are reusable and testable
|
|
300
|
+
|
|
301
|
+
### 3. Separate Concerns
|
|
302
|
+
- **UI**: Screens and Components
|
|
303
|
+
- **Logic**: Hooks
|
|
304
|
+
- **Styles**: StyleSheets
|
|
305
|
+
- **Constants**: Constant files
|
|
306
|
+
- **Types**: Type definition files
|
|
307
|
+
|
|
308
|
+
### 4. Import Organization
|
|
309
|
+
```typescript
|
|
310
|
+
// 1. React and React Native imports
|
|
311
|
+
import React, { useState, useEffect } from 'react';
|
|
312
|
+
import { View, Text } from 'react-native';
|
|
313
|
+
|
|
314
|
+
// 2. Third-party libraries
|
|
315
|
+
import { useNavigation } from '@react-navigation/native';
|
|
316
|
+
|
|
317
|
+
// 3. Hooks
|
|
318
|
+
import { useHomeLogic } from '../../hooks/home/useHomeLogic';
|
|
319
|
+
|
|
320
|
+
// 4. Components
|
|
321
|
+
import { CustomButton } from '../../components/button/CustomButton';
|
|
322
|
+
|
|
323
|
+
// 5. Styles
|
|
324
|
+
import { styles } from '../../styles/home/home.styles';
|
|
325
|
+
|
|
326
|
+
// 6. Constants and Types
|
|
327
|
+
import { HOME_CONSTANTS } from '../../constants/home/home.constants';
|
|
328
|
+
import { Navigation } from '../../types/Type';
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 5. Type Safety
|
|
332
|
+
- Always define TypeScript types and interfaces
|
|
333
|
+
- Use type inference when possible
|
|
334
|
+
- Avoid `any` type
|
|
335
|
+
- Define navigation types in `Type.ts`
|
|
336
|
+
|
|
337
|
+
## š§Ŗ Testing Strategy
|
|
338
|
+
|
|
339
|
+
### Unit Tests
|
|
340
|
+
- Test hooks independently
|
|
341
|
+
- Test utility functions
|
|
342
|
+
- Mock API calls
|
|
343
|
+
|
|
344
|
+
### Component Tests
|
|
345
|
+
- Test component rendering
|
|
346
|
+
- Test user interactions
|
|
347
|
+
- Mock hook return values
|
|
348
|
+
|
|
349
|
+
### Integration Tests
|
|
350
|
+
- Test screen flows
|
|
351
|
+
- Test navigation
|
|
352
|
+
- Test state management
|
|
353
|
+
|
|
354
|
+
## š¦ Recommended Packages
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"dependencies": {
|
|
359
|
+
"zustand": "^4.x.x",
|
|
360
|
+
"@react-navigation/native": "^6.x.x",
|
|
361
|
+
"@react-navigation/stack": "^6.x.x",
|
|
362
|
+
"@react-navigation/bottom-tabs": "^6.x.x"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## š Getting Started
|
|
368
|
+
|
|
369
|
+
1. Create a new screen following the pattern above
|
|
370
|
+
2. Implement the custom hook with business logic
|
|
371
|
+
3. Create styles using constants
|
|
372
|
+
4. Define any constants needed
|
|
373
|
+
5. Update navigation types in `Type.ts`
|
|
374
|
+
6. Add the screen to your navigator
|
|
375
|
+
|
|
376
|
+
## š Additional Resources
|
|
377
|
+
|
|
378
|
+
- [React Native Documentation](https://reactnative.dev/)
|
|
379
|
+
- [Zustand Documentation](https://docs.pmnd.rs/zustand/)
|
|
380
|
+
- [React Navigation Documentation](https://reactnavigation.org/)
|
|
381
|
+
- [Clean Architecture Principles](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
**Remember**: The goal is to keep code organized, maintainable, and scalable. Always follow these patterns and principles!
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const COLORS = {
|
|
2
|
+
colors: {
|
|
3
|
+
// Primary Colors
|
|
4
|
+
primary: '#007AFF',
|
|
5
|
+
secondary: '#5856D6',
|
|
6
|
+
|
|
7
|
+
// Neutral Colors
|
|
8
|
+
white: '#ffffff',
|
|
9
|
+
white_50: '#f5f5f5',
|
|
10
|
+
black: '#000000',
|
|
11
|
+
|
|
12
|
+
// Gray Scale
|
|
13
|
+
gray_900: '#111111',
|
|
14
|
+
gray_800: '#1f1f1f',
|
|
15
|
+
gray_700: '#444444',
|
|
16
|
+
gray_600: '#555555',
|
|
17
|
+
gray_500: '#6b6b6b',
|
|
18
|
+
gray_400: '#9e9e9e',
|
|
19
|
+
gray_300: '#c7c7c7',
|
|
20
|
+
gray_200: '#e0e0e0',
|
|
21
|
+
gray_100: '#f0f0f0',
|
|
22
|
+
|
|
23
|
+
// Status Colors
|
|
24
|
+
success: '#34C759',
|
|
25
|
+
warning: '#FF9500',
|
|
26
|
+
error: '#FF3B30',
|
|
27
|
+
info: '#5AC8FA',
|
|
28
|
+
|
|
29
|
+
// Semantic Colors
|
|
30
|
+
background: '#ffffff',
|
|
31
|
+
surface: '#f5f5f5',
|
|
32
|
+
text: '#111111',
|
|
33
|
+
textSecondary: '#6b6b6b',
|
|
34
|
+
border: '#e0e0e0',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const SIZINGS = {
|
|
2
|
+
borderSizings: {
|
|
3
|
+
extra_small: 1,
|
|
4
|
+
small: 2,
|
|
5
|
+
medium: 3,
|
|
6
|
+
large_1: 4,
|
|
7
|
+
large_2: 5,
|
|
8
|
+
large_3: 6,
|
|
9
|
+
large_4: 7,
|
|
10
|
+
large_5: 8,
|
|
11
|
+
large_6: 9,
|
|
12
|
+
large_7: 10,
|
|
13
|
+
large_8: 11,
|
|
14
|
+
large_9: 12,
|
|
15
|
+
large_10: 13,
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
fontSizings: {
|
|
19
|
+
extra_small: 12,
|
|
20
|
+
small: 14,
|
|
21
|
+
medium: 16,
|
|
22
|
+
large: 18,
|
|
23
|
+
large_1: 19,
|
|
24
|
+
large_2: 20,
|
|
25
|
+
large_3: 21,
|
|
26
|
+
large_4: 22,
|
|
27
|
+
large_5: 23,
|
|
28
|
+
large_6: 24,
|
|
29
|
+
large_7: 25,
|
|
30
|
+
large_8: 26,
|
|
31
|
+
large_9: 27,
|
|
32
|
+
large_10: 28,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
spacing: {
|
|
36
|
+
xs: 4,
|
|
37
|
+
sm: 8,
|
|
38
|
+
md: 16,
|
|
39
|
+
lg: 24,
|
|
40
|
+
xl: 32,
|
|
41
|
+
xxl: 48,
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
borderRadius: {
|
|
45
|
+
sm: 4,
|
|
46
|
+
md: 8,
|
|
47
|
+
lg: 12,
|
|
48
|
+
xl: 16,
|
|
49
|
+
full: 9999,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SPLASH_DURATION = 2000;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useNavigation } from '@react-navigation/native';
|
|
3
|
+
import { Navigation } from '../../types/Type';
|
|
4
|
+
import { SPLASH_DURATION } from '../../constants/splash/splash.constants';
|
|
5
|
+
|
|
6
|
+
export const useSplashNavigation = () => {
|
|
7
|
+
const navigation = useNavigation<Navigation>();
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const timer = setTimeout(() => {
|
|
11
|
+
navigation.navigate('BottomTab');
|
|
12
|
+
}, SPLASH_DURATION);
|
|
13
|
+
|
|
14
|
+
return () => clearTimeout(timer);
|
|
15
|
+
}, [navigation]);
|
|
16
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tab } from '../../utils/Util';
|
|
3
|
+
import HomeScreen from '../../screens/home/HomeScreen';
|
|
4
|
+
import ProfileScreen from '../../screens/profile/ProfileScreen';
|
|
5
|
+
import SettingsScreen from '../../screens/settings/SettingsScreen';
|
|
6
|
+
|
|
7
|
+
const BottomTabNavigation = () => {
|
|
8
|
+
return (
|
|
9
|
+
<Tab.Navigator>
|
|
10
|
+
<Tab.Screen name="Home" component={HomeScreen} />
|
|
11
|
+
<Tab.Screen name="Profile" component={ProfileScreen} />
|
|
12
|
+
<Tab.Screen name="Settings" component={SettingsScreen} />
|
|
13
|
+
</Tab.Navigator>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default BottomTabNavigation;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
3
|
+
import { Stack } from '../../utils/Util';
|
|
4
|
+
import SplashScreen from '../../screens/splash/SplashScreen';
|
|
5
|
+
import BottomTabNavigation from '../bottom/BottomTabNavigation';
|
|
6
|
+
|
|
7
|
+
const StackNavigation = () => {
|
|
8
|
+
return (
|
|
9
|
+
<NavigationContainer>
|
|
10
|
+
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
|
11
|
+
<Stack.Screen name="Splash" component={SplashScreen} />
|
|
12
|
+
<Stack.Screen name="BottomTab" component={BottomTabNavigation} />
|
|
13
|
+
</Stack.Navigator>
|
|
14
|
+
</NavigationContainer>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default StackNavigation;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StatusBar } from 'react-native';
|
|
3
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
4
|
+
import StackNavigation from './src/navigation/stack/StackNavigation';
|
|
5
|
+
|
|
6
|
+
export default function App() {
|
|
7
|
+
return (
|
|
8
|
+
<SafeAreaProvider>
|
|
9
|
+
<StackNavigation />
|
|
10
|
+
<StatusBar hidden={true} />
|
|
11
|
+
</SafeAreaProvider>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, View } from 'react-native';
|
|
3
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
4
|
+
|
|
5
|
+
import { styles } from '../../styles/home/home.styles';
|
|
6
|
+
|
|
7
|
+
const HomeScreen = () => {
|
|
8
|
+
return (
|
|
9
|
+
<SafeAreaView style={styles.container}>
|
|
10
|
+
<View style={styles.content}>
|
|
11
|
+
<Text style={styles.title}>Home Screen</Text>
|
|
12
|
+
<Text style={styles.subtitle}>Welcome to your app!</Text>
|
|
13
|
+
</View>
|
|
14
|
+
</SafeAreaView>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default HomeScreen;
|