@modhamanish/rn-mm-template 1.0.2 → 1.0.4

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.
Files changed (32) hide show
  1. package/MMTemplate/App.tsx +15 -2
  2. package/MMTemplate/README.md +53 -0
  3. package/MMTemplate/package.json +5 -2
  4. package/MMTemplate/src/components/AppText.tsx +125 -0
  5. package/MMTemplate/src/components/CustomToast.tsx +8 -6
  6. package/MMTemplate/src/components/FeatureItem.tsx +11 -8
  7. package/MMTemplate/src/components/InfoCard.tsx +10 -6
  8. package/MMTemplate/src/components/LanguageSwitcher.tsx +8 -7
  9. package/MMTemplate/src/components/TextInput.tsx +214 -23
  10. package/MMTemplate/src/components/ThemeSwitcher.tsx +12 -7
  11. package/MMTemplate/src/locales/en.json +19 -2
  12. package/MMTemplate/src/locales/hi.json +19 -2
  13. package/MMTemplate/src/navigation/AppStack.tsx +2 -0
  14. package/MMTemplate/src/navigation/MainTab.tsx +15 -4
  15. package/MMTemplate/src/navigation/routes.ts +2 -0
  16. package/MMTemplate/src/screens/AddNoteScreen.tsx +155 -0
  17. package/MMTemplate/src/screens/HomeScreen.tsx +60 -33
  18. package/MMTemplate/src/screens/LoginScreen.tsx +67 -25
  19. package/MMTemplate/src/screens/NoteScreen.tsx +241 -0
  20. package/MMTemplate/src/screens/ProfileScreen.tsx +26 -25
  21. package/MMTemplate/src/screens/SettingsScreen.tsx +17 -16
  22. package/MMTemplate/src/screens/WelcomeScreen.tsx +23 -23
  23. package/MMTemplate/src/services/axiosInstance.ts +41 -0
  24. package/MMTemplate/src/services/note.query.ts +40 -0
  25. package/MMTemplate/src/services/queryKeys.ts +4 -0
  26. package/MMTemplate/src/types/components.types.ts +47 -1
  27. package/MMTemplate/src/types/navigation.types.ts +2 -0
  28. package/MMTemplate/src/types/services.types.ts +6 -0
  29. package/MMTemplate/src/utils/storageHelper.ts +1 -0
  30. package/MMTemplate/src/utils/validationSchemas.ts +6 -0
  31. package/README.md +34 -0
  32. package/package.json +1 -1
@@ -1,11 +1,21 @@
1
- import React from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import {
3
3
  TextInput as RNTextInput,
4
4
  TextInputProps,
5
5
  View,
6
- Text,
7
6
  StyleSheet,
7
+ StyleProp,
8
+ TextStyle,
9
+ ViewStyle,
8
10
  } from 'react-native';
11
+ import Animated, {
12
+ useSharedValue,
13
+ useAnimatedStyle,
14
+ withTiming,
15
+ interpolate,
16
+ Extrapolate,
17
+ } from 'react-native-reanimated';
18
+ import AppText from './AppText';
9
19
  import { useTheme } from '../context/ThemeContext';
10
20
  import { ThemeType } from '../theme/Colors';
11
21
 
@@ -13,6 +23,17 @@ interface CustomTextInputProps extends TextInputProps {
13
23
  label?: string;
14
24
  error?: string;
15
25
  touched?: boolean;
26
+ renderLeft?: () => React.ReactNode;
27
+ renderRight?: () => React.ReactNode;
28
+ leftIcon?: React.ReactNode;
29
+ rightIcon?: React.ReactNode;
30
+ prefix?: string;
31
+ suffix?: string;
32
+ prefixStyle?: StyleProp<TextStyle>;
33
+ suffixStyle?: StyleProp<TextStyle>;
34
+ containerStyle?: StyleProp<ViewStyle>;
35
+ inputContainerStyle?: StyleProp<ViewStyle>;
36
+ labelBackgroundColor?: string;
16
37
  }
17
38
 
18
39
  const TextInput: React.FC<CustomTextInputProps> = ({
@@ -20,22 +41,161 @@ const TextInput: React.FC<CustomTextInputProps> = ({
20
41
  error,
21
42
  touched,
22
43
  style,
44
+ renderLeft,
45
+ renderRight,
46
+ leftIcon,
47
+ rightIcon,
48
+ prefix,
49
+ suffix,
50
+ prefixStyle,
51
+ suffixStyle,
52
+ containerStyle,
53
+ inputContainerStyle,
54
+ labelBackgroundColor,
55
+ value,
56
+ placeholder,
57
+ onFocus,
58
+ onBlur,
23
59
  ...props
24
60
  }) => {
25
61
  const theme = useTheme();
26
62
  const styles = getStyles(theme);
63
+ const [isFocused, setIsFocused] = useState(false);
64
+ const [leftWidth, setLeftWidth] = useState(0);
27
65
 
66
+ const hasValue = value !== undefined && value !== '';
28
67
  const hasError = touched && error;
68
+ const labelBg = labelBackgroundColor || theme.colors.backgroundColor;
69
+
70
+ const labelAnimation = useSharedValue(hasValue ? 1 : 0);
71
+
72
+ useEffect(() => {
73
+ labelAnimation.value = withTiming(isFocused || hasValue ? 1 : 0, {
74
+ duration: 200,
75
+ });
76
+ }, [isFocused, hasValue, labelAnimation]);
77
+
78
+ const animatedLabelStyle = useAnimatedStyle(() => {
79
+ return {
80
+ transform: [
81
+ {
82
+ translateY: interpolate(
83
+ labelAnimation.value,
84
+ [0, 1],
85
+ [0, -28],
86
+ Extrapolate.CLAMP,
87
+ ),
88
+ },
89
+ {
90
+ translateX: interpolate(
91
+ labelAnimation.value,
92
+ [0, 1],
93
+ [leftWidth, -4],
94
+ Extrapolate.CLAMP,
95
+ ),
96
+ },
97
+ {
98
+ scale: interpolate(
99
+ labelAnimation.value,
100
+ [0, 1],
101
+ [1, 0.85],
102
+ Extrapolate.CLAMP,
103
+ ),
104
+ },
105
+ ],
106
+ backgroundColor: labelAnimation.value === 1 ? labelBg : 'transparent',
107
+ paddingHorizontal: interpolate(
108
+ labelAnimation.value,
109
+ [0, 1],
110
+ [0, 4],
111
+ Extrapolate.CLAMP,
112
+ ),
113
+ color:
114
+ isFocused || hasValue
115
+ ? hasError
116
+ ? theme.colors.primary
117
+ : theme.colors.primary
118
+ : theme.colors.textColor + '80',
119
+ };
120
+ });
121
+
122
+ const handleFocus = (e: any) => {
123
+ setIsFocused(true);
124
+ onFocus?.(e);
125
+ };
126
+
127
+ const handleBlur = (e: any) => {
128
+ setIsFocused(false);
129
+ onBlur?.(e);
130
+ };
131
+
132
+ const onLeftLayout = (event: any) => {
133
+ setLeftWidth(event.nativeEvent.layout.width);
134
+ };
135
+
136
+ const showPlaceholder = label ? isFocused : true;
29
137
 
30
138
  return (
31
- <View style={styles.container}>
32
- {label && <Text style={styles.label}>{label}</Text>}
33
- <RNTextInput
34
- style={[styles.input, hasError && styles.inputError, style]}
35
- placeholderTextColor={theme.colors.textColor + '60'}
36
- {...props}
37
- />
38
- {hasError && <Text style={styles.errorText}>{error}</Text>}
139
+ <View style={[styles.container, containerStyle]}>
140
+ <View
141
+ style={[
142
+ styles.inputWrapper,
143
+ isFocused && styles.inputWrapperFocused,
144
+ hasError && styles.inputWrapperError,
145
+ inputContainerStyle,
146
+ ]}
147
+ >
148
+ {label && (
149
+ <Animated.View
150
+ pointerEvents="none"
151
+ style={[styles.labelContainer, animatedLabelStyle]}
152
+ >
153
+ <AppText variant="medium">{label}</AppText>
154
+ </Animated.View>
155
+ )}
156
+
157
+ <View style={styles.leftElements} onLayout={onLeftLayout}>
158
+ {renderLeft ? (
159
+ renderLeft()
160
+ ) : typeof leftIcon === 'string' ? (
161
+ <AppText style={styles.iconLeft}>{leftIcon}</AppText>
162
+ ) : (
163
+ leftIcon
164
+ )}
165
+
166
+ {prefix && (
167
+ <AppText style={[styles.prefix, prefixStyle]}>{prefix}</AppText>
168
+ )}
169
+ </View>
170
+
171
+ <RNTextInput
172
+ style={[styles.input, style]}
173
+ placeholder={showPlaceholder ? placeholder : ''}
174
+ placeholderTextColor={theme.colors.textColor + '60'}
175
+ onFocus={handleFocus}
176
+ onBlur={handleBlur}
177
+ value={value}
178
+ {...props}
179
+ />
180
+
181
+ {suffix && (
182
+ <AppText style={[styles.suffix, suffixStyle]}>{suffix}</AppText>
183
+ )}
184
+
185
+ {renderRight ? (
186
+ renderRight()
187
+ ) : typeof rightIcon === 'string' ? (
188
+ <AppText style={styles.iconRight}>{rightIcon}</AppText>
189
+ ) : (
190
+ rightIcon
191
+ )}
192
+ </View>
193
+
194
+ {hasError && (
195
+ <AppText size="small" style={styles.errorText}>
196
+ {error}
197
+ </AppText>
198
+ )}
39
199
  </View>
40
200
  );
41
201
  };
@@ -45,30 +205,61 @@ export default TextInput;
45
205
  const getStyles = ({ colors }: ThemeType) =>
46
206
  StyleSheet.create({
47
207
  container: {
48
- marginBottom: 20,
49
- },
50
- label: {
51
- fontSize: 14,
52
- fontWeight: '600',
53
- color: colors.textColor,
54
- marginBottom: 8,
208
+ marginBottom: 24,
209
+ marginTop: 12,
55
210
  },
56
- input: {
211
+ inputWrapper: {
212
+ flexDirection: 'row',
213
+ alignItems: 'center',
57
214
  backgroundColor: colors.backgroundColor,
58
215
  borderWidth: 1,
59
216
  borderColor: colors.textColor + '30',
60
217
  borderRadius: 12,
61
- padding: 16,
218
+ paddingHorizontal: 12,
219
+ minHeight: 56,
220
+ },
221
+ inputWrapperFocused: {
222
+ borderColor: colors.primary,
223
+ },
224
+ inputWrapperError: {
225
+ borderColor: colors.primary,
226
+ },
227
+ labelContainer: {
228
+ position: 'absolute',
229
+ left: 12,
230
+ top: 16,
231
+ zIndex: 1,
232
+ },
233
+ input: {
234
+ flex: 1,
62
235
  fontSize: 16,
63
236
  color: colors.textColor,
237
+ paddingVertical: 12,
238
+ paddingHorizontal: 4,
64
239
  },
65
- inputError: {
66
- borderColor: colors.primary,
240
+ leftElements: {
241
+ flexDirection: 'row',
242
+ alignItems: 'center',
243
+ },
244
+ iconLeft: {
245
+ marginRight: 8,
246
+ fontSize: 20,
247
+ },
248
+ iconRight: {
249
+ marginLeft: 8,
250
+ fontSize: 20,
251
+ },
252
+ prefix: {
253
+ marginRight: 4,
254
+ color: colors.textColor + '80',
255
+ },
256
+ suffix: {
257
+ marginLeft: 4,
258
+ color: colors.textColor + '80',
67
259
  },
68
260
  errorText: {
69
261
  color: colors.primary,
70
- fontSize: 12,
71
- marginTop: 4,
262
+ marginTop: 6,
72
263
  marginLeft: 4,
73
264
  },
74
265
  });
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
2
+ import { View, TouchableOpacity, StyleSheet } from 'react-native';
3
+ import AppText from './AppText';
3
4
  import { useTheme } from '../context/ThemeContext';
4
5
  import { ThemeType } from '../theme/Colors';
5
6
 
@@ -15,17 +16,23 @@ const ThemeSwitcher = () => {
15
16
  style={[styles.button, !isDark && styles.activeButton]}
16
17
  onPress={() => !isDark || toggleTheme()}
17
18
  >
18
- <Text style={[styles.buttonText, !isDark && styles.activeButtonText]}>
19
+ <AppText
20
+ variant="semiBold"
21
+ style={[styles.buttonText, !isDark && styles.activeButtonText]}
22
+ >
19
23
  ☀️ Light
20
- </Text>
24
+ </AppText>
21
25
  </TouchableOpacity>
22
26
  <TouchableOpacity
23
27
  style={[styles.button, isDark && styles.activeButton]}
24
28
  onPress={() => isDark || toggleTheme()}
25
29
  >
26
- <Text style={[styles.buttonText, isDark && styles.activeButtonText]}>
30
+ <AppText
31
+ variant="semiBold"
32
+ style={[styles.buttonText, isDark && styles.activeButtonText]}
33
+ >
27
34
  🌙 Dark
28
- </Text>
35
+ </AppText>
29
36
  </TouchableOpacity>
30
37
  </View>
31
38
  );
@@ -53,8 +60,6 @@ const getStyles = ({ colors }: ThemeType) =>
53
60
  backgroundColor: colors.primary,
54
61
  },
55
62
  buttonText: {
56
- fontSize: 14,
57
- fontWeight: '600',
58
63
  color: colors.primary,
59
64
  },
60
65
  activeButtonText: {
@@ -15,11 +15,25 @@
15
15
  "error": "Error",
16
16
  "success": "Success",
17
17
  "home": "Home",
18
- "profile": "Profile"
18
+ "profile": "Profile",
19
+ "note": "Note",
20
+ "notes": "Notes",
21
+ "noNotesFound": "No notes found",
22
+ "addNote": "Add Note",
23
+ "searchNotes": "Search notes...",
24
+ "title": "Title",
25
+ "description": "Description",
26
+ "enterTitle": "Enter title",
27
+ "enterDescription": "Enter description",
28
+ "creating": "Creating...",
29
+ "noteCreated": "Note created successfully",
30
+ "titleRequired": "Title is required",
31
+ "descriptionRequired": "Description is required"
19
32
  },
20
33
  "auth": {
21
34
  "welcomeBack": "Welcome Back!",
22
35
  "signInToContinue": "Sign in to continue",
36
+ "mockCredentialsHint": "Use the following credentials to login:",
23
37
  "email": "Email",
24
38
  "password": "Password",
25
39
  "enterEmail": "Enter your email",
@@ -62,7 +76,10 @@
62
76
  "storageSupport": "Fast Storage",
63
77
  "storageDescription": "High-performance persistence with react-native-mmkv",
64
78
  "localesDescription": "Translation files for multi-language support.",
65
- "mockDescription": "Mock data for development and testing."
79
+ "mockDescription": "Mock data for development and testing.",
80
+ "assetsDescription": "Global assets like images, icons, and fonts.",
81
+ "servicesDescription": "API calls and external service integrations.",
82
+ "typesDescription": "TypeScript interfaces and type definitions."
66
83
  },
67
84
  "settings": {
68
85
  "settings": "Settings",
@@ -15,11 +15,25 @@
15
15
  "error": "त्रुटि",
16
16
  "success": "सफलता",
17
17
  "home": "होम",
18
- "profile": "प्रोफ़ाइल"
18
+ "profile": "प्रोफ़ाइल",
19
+ "note": "नोट",
20
+ "notes": "नोट्स",
21
+ "noNotesFound": "कोई नोट्स नहीं मिले",
22
+ "addNote": "नोट जोड़ें",
23
+ "searchNotes": "नोट्स खोजें...",
24
+ "title": "शीर्षक",
25
+ "description": "विवरण",
26
+ "enterTitle": "शीर्षक दर्ज करें",
27
+ "enterDescription": "विवरण दर्ज करें",
28
+ "creating": "बनाया जा रहा है...",
29
+ "noteCreated": "नोट सफलतापूर्वक बनाया गया",
30
+ "titleRequired": "शीर्षक आवश्यक है",
31
+ "descriptionRequired": "विवरण आवश्यक है"
19
32
  },
20
33
  "auth": {
21
34
  "welcomeBack": "वापसी पर स्वागत है!",
22
35
  "signInToContinue": "जारी रखने के लिए साइन इन करें",
36
+ "mockCredentialsHint": "लॉगिन करने के लिए निम्न क्रेडेंशियल का उपयोग करें:",
23
37
  "email": "ईमेल",
24
38
  "password": "पासवर्ड",
25
39
  "enterEmail": "अपना ईमेल दर्ज करें",
@@ -62,7 +76,10 @@
62
76
  "storageSupport": "तेज़ स्टोरेज",
63
77
  "storageDescription": "react-native-mmkv के साथ उच्च प्रदर्शन दृढ़ता",
64
78
  "localesDescription": "बहु-भाषा समर्थन के लिए अनुवाद फ़ाइलें।",
65
- "mockDescription": "विकास और परीक्षण के लिए मॉक डेटा।"
79
+ "mockDescription": "विकास और परीक्षण के लिए मॉक डेटा।",
80
+ "assetsDescription": "छवियों, आइकन और फोंट जैसे वैश्विक एसेट्स।",
81
+ "servicesDescription": "API कॉल और बाहरी सेवा एकीकरण।",
82
+ "typesDescription": "TypeScript इंटरफेस और टाइप परिभाषाएं।"
66
83
  },
67
84
  "settings": {
68
85
  "settings": "सेटिंग्स",
@@ -7,6 +7,7 @@ import { AppStackParamList } from '../types/navigation.types';
7
7
  // Screens
8
8
  import MainTab from './MainTab';
9
9
  import SettingsScreen from '../screens/SettingsScreen';
10
+ import AddNoteScreen from '../screens/AddNoteScreen';
10
11
 
11
12
  const Stack = createNativeStackNavigator<AppStackParamList>();
12
13
 
@@ -20,6 +21,7 @@ const AppStack: FC = () => {
20
21
  >
21
22
  <Stack.Screen name={Routes.MainTab} component={MainTab} />
22
23
  <Stack.Screen name={Routes.SettingsScreen} component={SettingsScreen} />
24
+ <Stack.Screen name={Routes.AddNoteScreen} component={AddNoteScreen} />
23
25
  </Stack.Navigator>
24
26
  );
25
27
  };
@@ -1,12 +1,15 @@
1
1
  import React, { FC } from 'react';
2
- import { Text } from 'react-native';
2
+ import AppText from '../components/AppText';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5
5
  import Routes from './routes';
6
6
  import { MainTabParamList } from '../types/navigation.types';
7
+ import { useTheme } from '../context/ThemeContext';
8
+
9
+ // Screens
7
10
  import HomeScreen from '../screens/HomeScreen';
8
11
  import ProfileScreen from '../screens/ProfileScreen';
9
- import { useTheme } from '../context/ThemeContext';
12
+ import NoteScreen from '../screens/NoteScreen';
10
13
 
11
14
  const BottomTab = createBottomTabNavigator<MainTabParamList>();
12
15
 
@@ -32,7 +35,15 @@ const MainTab: FC = () => {
32
35
  component={HomeScreen}
33
36
  options={{
34
37
  tabBarLabel: t('common.home'),
35
- tabBarIcon: () => <Text>🏠</Text>,
38
+ tabBarIcon: () => <AppText>🏠</AppText>,
39
+ }}
40
+ />
41
+ <BottomTab.Screen
42
+ name={Routes.NoteScreen}
43
+ component={NoteScreen}
44
+ options={{
45
+ tabBarLabel: t('common.note'),
46
+ tabBarIcon: () => <AppText>📝</AppText>,
36
47
  }}
37
48
  />
38
49
  <BottomTab.Screen
@@ -40,7 +51,7 @@ const MainTab: FC = () => {
40
51
  component={ProfileScreen}
41
52
  options={{
42
53
  tabBarLabel: t('common.profile'),
43
- tabBarIcon: () => <Text>👤</Text>,
54
+ tabBarIcon: () => <AppText>👤</AppText>,
44
55
  }}
45
56
  />
46
57
  </BottomTab.Navigator>
@@ -10,8 +10,10 @@ enum Routes {
10
10
 
11
11
  // App Stack Screens
12
12
  HomeScreen = 'HomeScreen',
13
+ NoteScreen = 'NoteScreen',
13
14
  ProfileScreen = 'ProfileScreen',
14
15
  SettingsScreen = 'SettingsScreen',
16
+ AddNoteScreen = 'AddNoteScreen',
15
17
  }
16
18
 
17
19
  export default Routes;
@@ -0,0 +1,155 @@
1
+ import React, { FC } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ TouchableOpacity,
6
+ ActivityIndicator,
7
+ ScrollView,
8
+ } from 'react-native';
9
+ import AppText from '../components/AppText';
10
+ import { useTranslation } from 'react-i18next';
11
+ import { useTheme } from '../context/ThemeContext';
12
+ import { ThemeType } from '../theme/Colors';
13
+ import FullScreenContainer from '../components/FullScreenContainer';
14
+ import TextInput from '../components/TextInput';
15
+ import AnimationView from '../components/AnimationView';
16
+ import { useAddNoteMutation } from '../services/note.query';
17
+ import { goBack } from '../utils/navigationUtils';
18
+ import { useFormik } from 'formik';
19
+ import { NoteSchema } from '../utils/validationSchemas';
20
+
21
+ const AddNoteScreen: FC = () => {
22
+ const { t } = useTranslation();
23
+ const theme = useTheme();
24
+ const styles = getStyles(theme);
25
+
26
+ const { mutate: addNote, isPending } = useAddNoteMutation();
27
+
28
+ const formik = useFormik({
29
+ initialValues: {
30
+ title: '',
31
+ description: '',
32
+ },
33
+ validationSchema: NoteSchema,
34
+ onSubmit: values => {
35
+ addNote(values);
36
+ },
37
+ });
38
+
39
+ const { values, errors, touched, handleChange, handleBlur, handleSubmit } =
40
+ formik;
41
+
42
+ return (
43
+ <FullScreenContainer style={styles.container}>
44
+ <View style={styles.header}>
45
+ <TouchableOpacity style={styles.backButton} onPress={() => goBack()}>
46
+ <AppText size="xlarge" style={styles.backIcon}>
47
+
48
+ </AppText>
49
+ </TouchableOpacity>
50
+ <AppText variant="h3" style={styles.headerTitle}>
51
+ {t('common.addNote')}
52
+ </AppText>
53
+ <View style={{ width: 40 }} />
54
+ </View>
55
+
56
+ <ScrollView contentContainerStyle={styles.content}>
57
+ <AnimationView animType="FadeIn" duration={500}>
58
+ <TextInput
59
+ label={t('common.title')}
60
+ placeholder={t('common.enterTitle')}
61
+ value={values.title}
62
+ onChangeText={handleChange('title')}
63
+ onBlur={handleBlur('title')}
64
+ error={errors.title}
65
+ touched={touched.title}
66
+ />
67
+
68
+ <TextInput
69
+ label={t('common.description')}
70
+ placeholder={t('common.enterDescription')}
71
+ value={values.description}
72
+ onChangeText={handleChange('description')}
73
+ onBlur={handleBlur('description')}
74
+ error={errors.description}
75
+ touched={touched.description}
76
+ multiline
77
+ numberOfLines={10}
78
+ style={styles.textArea}
79
+ />
80
+
81
+ <TouchableOpacity
82
+ style={[styles.saveButton, isPending && styles.saveButtonDisabled]}
83
+ onPress={() => handleSubmit()}
84
+ disabled={isPending}
85
+ activeOpacity={0.8}
86
+ >
87
+ {isPending ? (
88
+ <ActivityIndicator color={theme.colors.white} />
89
+ ) : (
90
+ <AppText variant="bold" size={18} style={styles.saveButtonText}>
91
+ {t('common.save')}
92
+ </AppText>
93
+ )}
94
+ </TouchableOpacity>
95
+ </AnimationView>
96
+ </ScrollView>
97
+ </FullScreenContainer>
98
+ );
99
+ };
100
+
101
+ export default AddNoteScreen;
102
+
103
+ const getStyles = ({ colors }: ThemeType) =>
104
+ StyleSheet.create({
105
+ container: {
106
+ flex: 1,
107
+ backgroundColor: colors.backgroundColor,
108
+ },
109
+ header: {
110
+ flexDirection: 'row',
111
+ alignItems: 'center',
112
+ justifyContent: 'space-between',
113
+ paddingHorizontal: 16,
114
+ paddingVertical: 12,
115
+ borderBottomWidth: 1,
116
+ borderBottomColor: colors.textColor + '10',
117
+ },
118
+ backButton: {
119
+ width: 40,
120
+ height: 40,
121
+ justifyContent: 'center',
122
+ alignItems: 'center',
123
+ },
124
+ backIcon: {
125
+ color: colors.textColor,
126
+ },
127
+ headerTitle: {
128
+ color: colors.textColor,
129
+ },
130
+ content: {
131
+ padding: 20,
132
+ },
133
+ textArea: {
134
+ height: 150,
135
+ textAlignVertical: 'top',
136
+ },
137
+ saveButton: {
138
+ backgroundColor: colors.primary,
139
+ borderRadius: 12,
140
+ padding: 16,
141
+ alignItems: 'center',
142
+ marginTop: 24,
143
+ shadowColor: colors.primary,
144
+ shadowOffset: { width: 0, height: 4 },
145
+ shadowOpacity: 0.3,
146
+ shadowRadius: 8,
147
+ elevation: 5,
148
+ },
149
+ saveButtonDisabled: {
150
+ opacity: 0.6,
151
+ },
152
+ saveButtonText: {
153
+ color: colors.white,
154
+ },
155
+ });