@ranimontagna/agent-toolkit 0.1.5 → 0.1.7
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/README.md +68 -14
- package/package.json +1 -1
- package/skills/backend/go/golang-patterns/LICENSE +21 -0
- package/skills/backend/go/golang-patterns/NOTICE.md +10 -0
- package/skills/backend/go/golang-patterns/SKILL.md +674 -0
- package/skills/backend/go/golang-testing/LICENSE +21 -0
- package/skills/backend/go/golang-testing/NOTICE.md +10 -0
- package/skills/backend/go/golang-testing/SKILL.md +329 -0
- package/skills/backend/java/java-coding-standards/LICENSE +21 -0
- package/skills/backend/java/java-coding-standards/NOTICE.md +10 -0
- package/skills/backend/java/java-coding-standards/SKILL.md +383 -0
- package/skills/backend/java/java-junit/LICENSE +21 -0
- package/skills/backend/java/java-junit/NOTICE.md +10 -0
- package/skills/backend/java/java-junit/SKILL.md +64 -0
- package/skills/frontend/react/react-patterns/LICENSE +21 -0
- package/skills/frontend/react/react-patterns/NOTICE.md +11 -0
- package/skills/frontend/react/react-patterns/SKILL.md +341 -0
- package/skills/frontend/react/react-patterns/rules/react/LICENSE +21 -0
- package/skills/frontend/react/react-patterns/rules/react/NOTICE.md +11 -0
- package/skills/frontend/react/react-patterns/rules/react/coding-style.md +109 -0
- package/skills/frontend/react/react-patterns/rules/react/hooks.md +187 -0
- package/skills/frontend/react/react-patterns/rules/react/patterns.md +194 -0
- package/skills/frontend/react/react-patterns/rules/react/security.md +180 -0
- package/skills/frontend/react/react-patterns/rules/react/testing.md +208 -0
- package/skills/frontend/react/react-performance/LICENSE +21 -0
- package/skills/frontend/react/react-performance/NOTICE.md +11 -0
- package/skills/frontend/react/react-performance/SKILL.md +574 -0
- package/skills/frontend/react/react-performance/rules/react/LICENSE +21 -0
- package/skills/frontend/react/react-performance/rules/react/NOTICE.md +11 -0
- package/skills/frontend/react/react-performance/rules/react/coding-style.md +109 -0
- package/skills/frontend/react/react-performance/rules/react/hooks.md +187 -0
- package/skills/frontend/react/react-performance/rules/react/patterns.md +194 -0
- package/skills/frontend/react/react-performance/rules/react/security.md +180 -0
- package/skills/frontend/react/react-performance/rules/react/testing.md +208 -0
- package/skills/frontend/react/react-testing/LICENSE +21 -0
- package/skills/frontend/react/react-testing/NOTICE.md +11 -0
- package/skills/frontend/react/react-testing/SKILL.md +423 -0
- package/skills/frontend/react/react-testing/rules/react/LICENSE +21 -0
- package/skills/frontend/react/react-testing/rules/react/NOTICE.md +11 -0
- package/skills/frontend/react/react-testing/rules/react/coding-style.md +109 -0
- package/skills/frontend/react/react-testing/rules/react/hooks.md +187 -0
- package/skills/frontend/react/react-testing/rules/react/patterns.md +194 -0
- package/skills/frontend/react/react-testing/rules/react/security.md +180 -0
- package/skills/frontend/react/react-testing/rules/react/testing.md +208 -0
- package/skills/frontend/react-native/react-native-expert/LICENSE +21 -0
- package/skills/frontend/react-native/react-native-expert/NOTICE.md +11 -0
- package/skills/frontend/react-native/react-native-expert/SKILL.md +187 -0
- package/skills/frontend/react-native/react-native-expert/references/expo-router.md +187 -0
- package/skills/frontend/react-native/react-native-expert/references/list-optimization.md +204 -0
- package/skills/frontend/react-native/react-native-expert/references/platform-handling.md +188 -0
- package/skills/frontend/react-native/react-native-expert/references/project-structure.md +171 -0
- package/skills/frontend/react-native/react-native-expert/references/storage-hooks.md +173 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/LICENSE +21 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/NOTICE.md +11 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/SKILL.md +159 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/api-reference.md +495 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/common-issues.md +389 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/setup-guide.md +217 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/styling-patterns.md +705 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/third-party-integration.md +318 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# List Optimization
|
|
2
|
+
|
|
3
|
+
## Optimized FlatList
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { FlatList, ListRenderItem } from 'react-native';
|
|
7
|
+
import { memo, useCallback } from 'react';
|
|
8
|
+
|
|
9
|
+
interface Item {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Memoized list item
|
|
16
|
+
const ListItem = memo(function ListItem({
|
|
17
|
+
item,
|
|
18
|
+
onPress
|
|
19
|
+
}: {
|
|
20
|
+
item: Item;
|
|
21
|
+
onPress: (id: string) => void;
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<Pressable onPress={() => onPress(item.id)} style={styles.item}>
|
|
25
|
+
<Text style={styles.title}>{item.title}</Text>
|
|
26
|
+
<Text style={styles.subtitle}>{item.subtitle}</Text>
|
|
27
|
+
</Pressable>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function OptimizedList({ data }: { data: Item[] }) {
|
|
32
|
+
// Memoize callbacks
|
|
33
|
+
const handlePress = useCallback((id: string) => {
|
|
34
|
+
console.log('Selected:', id);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const renderItem: ListRenderItem<Item> = useCallback(
|
|
38
|
+
({ item }) => <ListItem item={item} onPress={handlePress} />,
|
|
39
|
+
[handlePress]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const keyExtractor = useCallback((item: Item) => item.id, []);
|
|
43
|
+
|
|
44
|
+
// Fixed height for getItemLayout
|
|
45
|
+
const getItemLayout = useCallback(
|
|
46
|
+
(_: any, index: number) => ({
|
|
47
|
+
length: ITEM_HEIGHT,
|
|
48
|
+
offset: ITEM_HEIGHT * index,
|
|
49
|
+
index,
|
|
50
|
+
}),
|
|
51
|
+
[]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<FlatList
|
|
56
|
+
data={data}
|
|
57
|
+
renderItem={renderItem}
|
|
58
|
+
keyExtractor={keyExtractor}
|
|
59
|
+
getItemLayout={getItemLayout}
|
|
60
|
+
// Performance props
|
|
61
|
+
removeClippedSubviews
|
|
62
|
+
maxToRenderPerBatch={10}
|
|
63
|
+
windowSize={5}
|
|
64
|
+
initialNumToRender={10}
|
|
65
|
+
updateCellsBatchingPeriod={50}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const ITEM_HEIGHT = 72;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## SectionList
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { SectionList } from 'react-native';
|
|
77
|
+
|
|
78
|
+
interface Section {
|
|
79
|
+
title: string;
|
|
80
|
+
data: Item[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function GroupedList({ sections }: { sections: Section[] }) {
|
|
84
|
+
const renderSectionHeader = useCallback(
|
|
85
|
+
({ section }: { section: Section }) => (
|
|
86
|
+
<View style={styles.sectionHeader}>
|
|
87
|
+
<Text style={styles.sectionTitle}>{section.title}</Text>
|
|
88
|
+
</View>
|
|
89
|
+
),
|
|
90
|
+
[]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<SectionList
|
|
95
|
+
sections={sections}
|
|
96
|
+
renderItem={renderItem}
|
|
97
|
+
renderSectionHeader={renderSectionHeader}
|
|
98
|
+
keyExtractor={keyExtractor}
|
|
99
|
+
stickySectionHeadersEnabled
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Pull to Refresh
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
function RefreshableList({ data, onRefresh }: Props) {
|
|
109
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
110
|
+
|
|
111
|
+
const handleRefresh = useCallback(async () => {
|
|
112
|
+
setRefreshing(true);
|
|
113
|
+
await onRefresh();
|
|
114
|
+
setRefreshing(false);
|
|
115
|
+
}, [onRefresh]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<FlatList
|
|
119
|
+
data={data}
|
|
120
|
+
renderItem={renderItem}
|
|
121
|
+
refreshControl={
|
|
122
|
+
<RefreshControl
|
|
123
|
+
refreshing={refreshing}
|
|
124
|
+
onRefresh={handleRefresh}
|
|
125
|
+
tintColor="#007AFF"
|
|
126
|
+
/>
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Infinite Scroll
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
function InfiniteList() {
|
|
137
|
+
const [data, setData] = useState<Item[]>([]);
|
|
138
|
+
const [loading, setLoading] = useState(false);
|
|
139
|
+
const [hasMore, setHasMore] = useState(true);
|
|
140
|
+
|
|
141
|
+
const loadMore = useCallback(async () => {
|
|
142
|
+
if (loading || !hasMore) return;
|
|
143
|
+
|
|
144
|
+
setLoading(true);
|
|
145
|
+
const newItems = await fetchMoreItems(data.length);
|
|
146
|
+
|
|
147
|
+
if (newItems.length === 0) {
|
|
148
|
+
setHasMore(false);
|
|
149
|
+
} else {
|
|
150
|
+
setData(prev => [...prev, ...newItems]);
|
|
151
|
+
}
|
|
152
|
+
setLoading(false);
|
|
153
|
+
}, [data.length, loading, hasMore]);
|
|
154
|
+
|
|
155
|
+
const renderFooter = useCallback(() => {
|
|
156
|
+
if (!loading) return null;
|
|
157
|
+
return <ActivityIndicator style={styles.loader} />;
|
|
158
|
+
}, [loading]);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<FlatList
|
|
162
|
+
data={data}
|
|
163
|
+
renderItem={renderItem}
|
|
164
|
+
onEndReached={loadMore}
|
|
165
|
+
onEndReachedThreshold={0.5}
|
|
166
|
+
ListFooterComponent={renderFooter}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## FlashList (Alternative)
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { FlashList } from '@shopify/flash-list';
|
|
176
|
+
|
|
177
|
+
function FastList({ data }: { data: Item[] }) {
|
|
178
|
+
return (
|
|
179
|
+
<FlashList
|
|
180
|
+
data={data}
|
|
181
|
+
renderItem={renderItem}
|
|
182
|
+
estimatedItemSize={72}
|
|
183
|
+
keyExtractor={keyExtractor}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Quick Reference
|
|
190
|
+
|
|
191
|
+
| Prop | Purpose |
|
|
192
|
+
|------|---------|
|
|
193
|
+
| `removeClippedSubviews` | Unmount off-screen items |
|
|
194
|
+
| `maxToRenderPerBatch` | Items per render batch |
|
|
195
|
+
| `windowSize` | Render window multiplier |
|
|
196
|
+
| `initialNumToRender` | Initial items to render |
|
|
197
|
+
| `getItemLayout` | Skip measurement (fixed height) |
|
|
198
|
+
|
|
199
|
+
| Optimization | When |
|
|
200
|
+
|--------------|------|
|
|
201
|
+
| `memo()` | All list items |
|
|
202
|
+
| `useCallback` | renderItem, keyExtractor |
|
|
203
|
+
| `getItemLayout` | Fixed height items |
|
|
204
|
+
| `FlashList` | Very large lists |
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Platform Handling
|
|
2
|
+
|
|
3
|
+
## Platform.select
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Platform, StyleSheet } from 'react-native';
|
|
7
|
+
|
|
8
|
+
const styles = StyleSheet.create({
|
|
9
|
+
card: {
|
|
10
|
+
padding: 16,
|
|
11
|
+
borderRadius: 12,
|
|
12
|
+
backgroundColor: '#fff',
|
|
13
|
+
...Platform.select({
|
|
14
|
+
ios: {
|
|
15
|
+
shadowColor: '#000',
|
|
16
|
+
shadowOffset: { width: 0, height: 2 },
|
|
17
|
+
shadowOpacity: 0.1,
|
|
18
|
+
shadowRadius: 8,
|
|
19
|
+
},
|
|
20
|
+
android: {
|
|
21
|
+
elevation: 4,
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
text: {
|
|
26
|
+
fontFamily: Platform.select({
|
|
27
|
+
ios: 'Helvetica Neue',
|
|
28
|
+
android: 'Roboto',
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Platform.OS
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Platform } from 'react-native';
|
|
38
|
+
|
|
39
|
+
function MyComponent() {
|
|
40
|
+
const isIOS = Platform.OS === 'ios';
|
|
41
|
+
const isAndroid = Platform.OS === 'android';
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<View>
|
|
45
|
+
{isIOS && <IOSOnlyComponent />}
|
|
46
|
+
<Text>{isAndroid ? 'Android' : 'iOS'}</Text>
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Platform-Specific Files
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
components/
|
|
56
|
+
├── Button.tsx # Shared logic
|
|
57
|
+
├── Button.ios.tsx # iOS-specific
|
|
58
|
+
└── Button.android.tsx # Android-specific
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Import resolves to correct platform file
|
|
63
|
+
import Button from './components/Button';
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## SafeAreaView
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { SafeAreaView, StyleSheet } from 'react-native';
|
|
70
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
71
|
+
|
|
72
|
+
// Method 1: SafeAreaView component
|
|
73
|
+
function Screen() {
|
|
74
|
+
return (
|
|
75
|
+
<SafeAreaView style={styles.container}>
|
|
76
|
+
<Content />
|
|
77
|
+
</SafeAreaView>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Method 2: useSafeAreaInsets hook (more control)
|
|
82
|
+
function CustomHeader() {
|
|
83
|
+
const insets = useSafeAreaInsets();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<View style={[styles.header, { paddingTop: insets.top }]}>
|
|
87
|
+
<Text>Header</Text>
|
|
88
|
+
</View>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Method 3: SafeAreaProvider context
|
|
93
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
94
|
+
|
|
95
|
+
function App() {
|
|
96
|
+
return (
|
|
97
|
+
<SafeAreaProvider>
|
|
98
|
+
<Navigation />
|
|
99
|
+
</SafeAreaProvider>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## KeyboardAvoidingView
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { KeyboardAvoidingView, Platform } from 'react-native';
|
|
108
|
+
|
|
109
|
+
function FormScreen() {
|
|
110
|
+
return (
|
|
111
|
+
<KeyboardAvoidingView
|
|
112
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
113
|
+
style={{ flex: 1 }}
|
|
114
|
+
keyboardVerticalOffset={Platform.select({ ios: 88, android: 0 })}
|
|
115
|
+
>
|
|
116
|
+
<ScrollView>
|
|
117
|
+
<TextInput placeholder="Name" />
|
|
118
|
+
<TextInput placeholder="Email" />
|
|
119
|
+
</ScrollView>
|
|
120
|
+
</KeyboardAvoidingView>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## StatusBar
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { StatusBar, Platform } from 'react-native';
|
|
129
|
+
|
|
130
|
+
function Screen() {
|
|
131
|
+
return (
|
|
132
|
+
<>
|
|
133
|
+
<StatusBar
|
|
134
|
+
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'light-content'}
|
|
135
|
+
backgroundColor={Platform.OS === 'android' ? '#000' : undefined}
|
|
136
|
+
/>
|
|
137
|
+
<Content />
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Android Back Button
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { useEffect } from 'react';
|
|
147
|
+
import { BackHandler, Platform } from 'react-native';
|
|
148
|
+
|
|
149
|
+
function useBackHandler(handler: () => boolean) {
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (Platform.OS !== 'android') return;
|
|
152
|
+
|
|
153
|
+
const subscription = BackHandler.addEventListener(
|
|
154
|
+
'hardwareBackPress',
|
|
155
|
+
handler
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return () => subscription.remove();
|
|
159
|
+
}, [handler]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Usage
|
|
163
|
+
function Screen() {
|
|
164
|
+
useBackHandler(() => {
|
|
165
|
+
if (hasUnsavedChanges) {
|
|
166
|
+
showDiscardAlert();
|
|
167
|
+
return true; // Prevent default back
|
|
168
|
+
}
|
|
169
|
+
return false; // Allow default back
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Quick Reference
|
|
175
|
+
|
|
176
|
+
| API | Purpose |
|
|
177
|
+
|-----|---------|
|
|
178
|
+
| `Platform.OS` | Get platform ('ios' / 'android') |
|
|
179
|
+
| `Platform.select()` | Platform-specific values |
|
|
180
|
+
| `Platform.Version` | OS version number |
|
|
181
|
+
| `.ios.tsx` / `.android.tsx` | Platform-specific files |
|
|
182
|
+
|
|
183
|
+
| Component | Purpose |
|
|
184
|
+
|-----------|---------|
|
|
185
|
+
| `SafeAreaView` | Avoid notch/home indicator |
|
|
186
|
+
| `KeyboardAvoidingView` | Keyboard handling |
|
|
187
|
+
| `StatusBar` | Status bar styling |
|
|
188
|
+
| `BackHandler` | Android back button |
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Project Structure
|
|
2
|
+
|
|
3
|
+
## Expo Router Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
my-app/
|
|
7
|
+
├── app/ # File-based routing
|
|
8
|
+
│ ├── _layout.tsx # Root layout
|
|
9
|
+
│ ├── index.tsx # Home screen
|
|
10
|
+
│ ├── +not-found.tsx # 404 screen
|
|
11
|
+
│ ├── (tabs)/ # Tab navigator group
|
|
12
|
+
│ │ ├── _layout.tsx
|
|
13
|
+
│ │ ├── index.tsx
|
|
14
|
+
│ │ ├── search.tsx
|
|
15
|
+
│ │ └── profile.tsx
|
|
16
|
+
│ ├── (auth)/ # Auth screens (no tabs)
|
|
17
|
+
│ │ ├── _layout.tsx
|
|
18
|
+
│ │ ├── login.tsx
|
|
19
|
+
│ │ └── register.tsx
|
|
20
|
+
│ └── [id].tsx # Dynamic route
|
|
21
|
+
├── components/
|
|
22
|
+
│ ├── ui/ # Reusable UI components
|
|
23
|
+
│ │ ├── Button.tsx
|
|
24
|
+
│ │ ├── Card.tsx
|
|
25
|
+
│ │ └── Input.tsx
|
|
26
|
+
│ └── features/ # Feature-specific components
|
|
27
|
+
│ ├── ProductCard.tsx
|
|
28
|
+
│ └── UserAvatar.tsx
|
|
29
|
+
├── hooks/
|
|
30
|
+
│ ├── useAuth.ts
|
|
31
|
+
│ ├── useStorage.ts
|
|
32
|
+
│ └── useApi.ts
|
|
33
|
+
├── services/
|
|
34
|
+
│ ├── api.ts # API client
|
|
35
|
+
│ └── auth.ts # Auth service
|
|
36
|
+
├── stores/
|
|
37
|
+
│ └── useUserStore.ts # Zustand stores
|
|
38
|
+
├── constants/
|
|
39
|
+
│ ├── colors.ts
|
|
40
|
+
│ └── layout.ts
|
|
41
|
+
├── types/
|
|
42
|
+
│ └── index.ts
|
|
43
|
+
├── utils/
|
|
44
|
+
│ └── helpers.ts
|
|
45
|
+
├── assets/
|
|
46
|
+
│ ├── images/
|
|
47
|
+
│ └── fonts/
|
|
48
|
+
├── app.json
|
|
49
|
+
├── babel.config.js
|
|
50
|
+
└── tsconfig.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## app.json Configuration
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"expo": {
|
|
58
|
+
"name": "My App",
|
|
59
|
+
"slug": "my-app",
|
|
60
|
+
"version": "1.0.0",
|
|
61
|
+
"scheme": "myapp",
|
|
62
|
+
"orientation": "portrait",
|
|
63
|
+
"icon": "./assets/images/icon.png",
|
|
64
|
+
"splash": {
|
|
65
|
+
"image": "./assets/images/splash.png",
|
|
66
|
+
"resizeMode": "contain",
|
|
67
|
+
"backgroundColor": "#ffffff"
|
|
68
|
+
},
|
|
69
|
+
"ios": {
|
|
70
|
+
"supportsTablet": true,
|
|
71
|
+
"bundleIdentifier": "com.company.myapp"
|
|
72
|
+
},
|
|
73
|
+
"android": {
|
|
74
|
+
"adaptiveIcon": {
|
|
75
|
+
"foregroundImage": "./assets/images/adaptive-icon.png",
|
|
76
|
+
"backgroundColor": "#ffffff"
|
|
77
|
+
},
|
|
78
|
+
"package": "com.company.myapp"
|
|
79
|
+
},
|
|
80
|
+
"plugins": [
|
|
81
|
+
"expo-router"
|
|
82
|
+
],
|
|
83
|
+
"experiments": {
|
|
84
|
+
"typedRoutes": true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## tsconfig.json
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"extends": "expo/tsconfig.base",
|
|
95
|
+
"compilerOptions": {
|
|
96
|
+
"strict": true,
|
|
97
|
+
"baseUrl": ".",
|
|
98
|
+
"paths": {
|
|
99
|
+
"@/*": ["./*"],
|
|
100
|
+
"@/components/*": ["components/*"],
|
|
101
|
+
"@/hooks/*": ["hooks/*"],
|
|
102
|
+
"@/services/*": ["services/*"],
|
|
103
|
+
"@/stores/*": ["stores/*"],
|
|
104
|
+
"@/types/*": ["types/*"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## babel.config.js
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
module.exports = function (api) {
|
|
115
|
+
api.cache(true);
|
|
116
|
+
return {
|
|
117
|
+
presets: ['babel-preset-expo'],
|
|
118
|
+
plugins: [
|
|
119
|
+
[
|
|
120
|
+
'module-resolver',
|
|
121
|
+
{
|
|
122
|
+
root: ['.'],
|
|
123
|
+
alias: {
|
|
124
|
+
'@': '.',
|
|
125
|
+
'@/components': './components',
|
|
126
|
+
'@/hooks': './hooks',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
'react-native-reanimated/plugin', // Must be last
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Essential Dependencies
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"dependencies": {
|
|
141
|
+
"expo": "~50.0.0",
|
|
142
|
+
"expo-router": "~3.4.0",
|
|
143
|
+
"react-native-safe-area-context": "4.8.2",
|
|
144
|
+
"react-native-screens": "~3.29.0",
|
|
145
|
+
"@react-navigation/native": "^6.1.0",
|
|
146
|
+
"react-native-reanimated": "~3.6.0",
|
|
147
|
+
"react-native-gesture-handler": "~2.14.0",
|
|
148
|
+
"zustand": "^4.5.0",
|
|
149
|
+
"@tanstack/react-query": "^5.0.0",
|
|
150
|
+
"expo-image": "~1.10.0",
|
|
151
|
+
"react-native-mmkv": "^2.11.0"
|
|
152
|
+
},
|
|
153
|
+
"devDependencies": {
|
|
154
|
+
"@types/react": "~18.2.0",
|
|
155
|
+
"typescript": "^5.3.0"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Quick Reference
|
|
161
|
+
|
|
162
|
+
| Directory | Purpose |
|
|
163
|
+
|-----------|---------|
|
|
164
|
+
| `app/` | File-based routes |
|
|
165
|
+
| `components/ui/` | Reusable UI |
|
|
166
|
+
| `components/features/` | Feature components |
|
|
167
|
+
| `hooks/` | Custom hooks |
|
|
168
|
+
| `services/` | API, auth services |
|
|
169
|
+
| `stores/` | State management |
|
|
170
|
+
| `constants/` | App constants |
|
|
171
|
+
| `types/` | TypeScript types |
|