@imksh/create 1.0.23 → 1.0.25

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 (163) hide show
  1. package/package.json +1 -1
  2. package/src/utils/copyTemplate.js +6 -0
  3. package/templates/mern-react-native/client/src/vite-env.d.ts +1 -0
  4. package/templates/mern-react-native/mobile/babel.config.js +1 -0
  5. package/templates/mern-react-native/mobile/drizzle.config.ts +8 -0
  6. package/templates/mern-react-native/mobile/expo-env.d.ts +3 -0
  7. package/templates/mern-react-native/mobile/package.json +6 -0
  8. package/templates/mern-react-native/mobile/src/app/(auth)/_layout.tsx +12 -0
  9. package/templates/mern-react-native/mobile/src/app/(auth)/change-password.tsx +112 -0
  10. package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  11. package/templates/mern-react-native/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  12. package/templates/mern-react-native/mobile/src/app/{login.tsx → (auth)/login.tsx} +69 -21
  13. package/templates/mern-react-native/mobile/src/app/(tabs)/index.tsx +18 -17
  14. package/templates/mern-react-native/mobile/src/app/(tabs)/profile.tsx +23 -60
  15. package/templates/mern-react-native/mobile/src/app/_layout.tsx +22 -4
  16. package/templates/mern-react-native/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  17. package/templates/mern-react-native/mobile/src/components/common/LocationLoader.tsx +591 -0
  18. package/templates/mern-react-native/mobile/src/components/common/ScreenHeader.tsx +41 -17
  19. package/templates/mern-react-native/mobile/src/components/common/TabHeader.tsx +58 -0
  20. package/templates/mern-react-native/mobile/src/components/common/Typography.tsx +49 -30
  21. package/templates/mern-react-native/mobile/src/components/common/Welcome.tsx +2 -2
  22. package/templates/mern-react-native/mobile/src/components/ui/BottomModal.tsx +31 -20
  23. package/templates/mern-react-native/mobile/src/components/ui/ClassicTabBar.tsx +4 -3
  24. package/templates/mern-react-native/mobile/src/components/ui/CustomTabBar.tsx +108 -71
  25. package/templates/mern-react-native/mobile/src/components/ui/Sidebar.tsx +81 -56
  26. package/templates/mern-react-native/mobile/src/components/ui/buttons/Button.tsx +106 -37
  27. package/templates/mern-react-native/mobile/src/components/ui/buttons/IconButton.tsx +4 -2
  28. package/templates/mern-react-native/mobile/src/components/ui/data-display/Accordion.tsx +3 -2
  29. package/templates/mern-react-native/mobile/src/components/ui/data-display/Avatar.tsx +4 -3
  30. package/templates/mern-react-native/mobile/src/components/ui/data-display/Badge.tsx +45 -17
  31. package/templates/mern-react-native/mobile/src/components/ui/data-display/ImageViewer.tsx +19 -9
  32. package/templates/mern-react-native/mobile/src/components/ui/feedback/CircularProgress.tsx +5 -4
  33. package/templates/mern-react-native/mobile/src/components/ui/feedback/ProgressBar.tsx +4 -3
  34. package/templates/mern-react-native/mobile/src/components/ui/feedback/Skeleton.tsx +3 -0
  35. package/templates/mern-react-native/mobile/src/components/ui/feedback/Spinner.tsx +11 -3
  36. package/templates/mern-react-native/mobile/src/components/ui/inputs/Checkbox.tsx +3 -2
  37. package/templates/mern-react-native/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  38. package/templates/mern-react-native/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  39. package/templates/mern-react-native/mobile/src/components/ui/inputs/Input.tsx +54 -14
  40. package/templates/mern-react-native/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  41. package/templates/mern-react-native/mobile/src/components/ui/inputs/OTPInput.tsx +32 -9
  42. package/templates/mern-react-native/mobile/src/components/ui/inputs/PasswordInput.tsx +2 -1
  43. package/templates/mern-react-native/mobile/src/components/ui/inputs/Radio.tsx +3 -2
  44. package/templates/mern-react-native/mobile/src/components/ui/inputs/SearchBar.tsx +41 -4
  45. package/templates/mern-react-native/mobile/src/components/ui/inputs/Slider.tsx +4 -3
  46. package/templates/mern-react-native/mobile/src/components/ui/inputs/Switch.tsx +3 -2
  47. package/templates/mern-react-native/mobile/src/components/ui/inputs/TextArea.tsx +2 -1
  48. package/templates/mern-react-native/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  49. package/templates/mern-react-native/mobile/src/components/ui/layout/Grid.tsx +52 -10
  50. package/templates/mern-react-native/mobile/src/components/ui/layout/Section.tsx +7 -8
  51. package/templates/mern-react-native/mobile/src/components/ui/navigation/Pagination.tsx +4 -3
  52. package/templates/mern-react-native/mobile/src/components/ui/navigation/ProgressStepper.tsx +6 -5
  53. package/templates/mern-react-native/mobile/src/components/ui/navigation/Tabs.tsx +30 -4
  54. package/templates/mern-react-native/mobile/src/config/api.ts +12 -1
  55. package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +72 -0
  56. package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +37 -7
  57. package/templates/mern-react-native/mobile/src/db/index.ts +9 -0
  58. package/templates/mern-react-native/mobile/src/db/schema.ts +14 -0
  59. package/templates/mern-react-native/mobile/src/hooks/useDebounce.ts +17 -0
  60. package/templates/mern-react-native/mobile/src/services/socket.service.ts +36 -0
  61. package/templates/mern-react-native/mobile/src/store/useAuthStore.ts +122 -27
  62. package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +53 -0
  63. package/templates/mern-react-native/mobile/src/types/auth.type.ts +14 -0
  64. package/templates/mern-react-native/mobile/tsconfig.json +1 -0
  65. package/templates/mern-ts/client/src/vite-env.d.ts +1 -0
  66. package/templates/react-native/babel.config.js +1 -0
  67. package/templates/react-native/drizzle.config.ts +8 -0
  68. package/templates/react-native/expo-env.d.ts +3 -0
  69. package/templates/react-native/package.json +6 -0
  70. package/templates/react-native/src/app/(auth)/_layout.tsx +12 -0
  71. package/templates/react-native/src/app/(auth)/change-password.tsx +112 -0
  72. package/templates/react-native/src/app/(auth)/edit-profile.tsx +160 -0
  73. package/templates/react-native/src/app/(auth)/forgot-password.tsx +191 -0
  74. package/templates/react-native/src/app/{login.tsx → (auth)/login.tsx} +69 -21
  75. package/templates/react-native/src/app/(tabs)/index.tsx +18 -17
  76. package/templates/react-native/src/app/(tabs)/profile.tsx +23 -60
  77. package/templates/react-native/src/app/_layout.tsx +22 -4
  78. package/templates/react-native/src/components/common/AnimatedHeader.tsx +328 -0
  79. package/templates/react-native/src/components/common/LocationLoader.tsx +591 -0
  80. package/templates/react-native/src/components/common/ScreenHeader.tsx +41 -17
  81. package/templates/react-native/src/components/common/TabHeader.tsx +58 -0
  82. package/templates/react-native/src/components/common/Typography.tsx +49 -30
  83. package/templates/react-native/src/components/common/Welcome.tsx +2 -2
  84. package/templates/react-native/src/components/ui/BottomModal.tsx +31 -20
  85. package/templates/react-native/src/components/ui/ClassicTabBar.tsx +4 -3
  86. package/templates/react-native/src/components/ui/CustomTabBar.tsx +108 -71
  87. package/templates/react-native/src/components/ui/Sidebar.tsx +81 -56
  88. package/templates/react-native/src/components/ui/buttons/Button.tsx +106 -37
  89. package/templates/react-native/src/components/ui/buttons/IconButton.tsx +4 -2
  90. package/templates/react-native/src/components/ui/data-display/Accordion.tsx +3 -2
  91. package/templates/react-native/src/components/ui/data-display/Avatar.tsx +4 -3
  92. package/templates/react-native/src/components/ui/data-display/Badge.tsx +45 -17
  93. package/templates/react-native/src/components/ui/data-display/ImageViewer.tsx +19 -9
  94. package/templates/react-native/src/components/ui/feedback/CircularProgress.tsx +5 -4
  95. package/templates/react-native/src/components/ui/feedback/ProgressBar.tsx +4 -3
  96. package/templates/react-native/src/components/ui/feedback/Skeleton.tsx +3 -0
  97. package/templates/react-native/src/components/ui/feedback/Spinner.tsx +11 -3
  98. package/templates/react-native/src/components/ui/inputs/Checkbox.tsx +3 -2
  99. package/templates/react-native/src/components/ui/inputs/DatePicker.tsx +186 -0
  100. package/templates/react-native/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  101. package/templates/react-native/src/components/ui/inputs/Input.tsx +54 -14
  102. package/templates/react-native/src/components/ui/inputs/LocationPicker.tsx +211 -0
  103. package/templates/react-native/src/components/ui/inputs/OTPInput.tsx +32 -9
  104. package/templates/react-native/src/components/ui/inputs/PasswordInput.tsx +2 -1
  105. package/templates/react-native/src/components/ui/inputs/Radio.tsx +3 -2
  106. package/templates/react-native/src/components/ui/inputs/SearchBar.tsx +41 -4
  107. package/templates/react-native/src/components/ui/inputs/Slider.tsx +4 -3
  108. package/templates/react-native/src/components/ui/inputs/Switch.tsx +3 -2
  109. package/templates/react-native/src/components/ui/inputs/TextArea.tsx +2 -1
  110. package/templates/react-native/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  111. package/templates/react-native/src/components/ui/layout/Grid.tsx +52 -10
  112. package/templates/react-native/src/components/ui/layout/Section.tsx +7 -8
  113. package/templates/react-native/src/components/ui/navigation/Pagination.tsx +4 -3
  114. package/templates/react-native/src/components/ui/navigation/ProgressStepper.tsx +6 -5
  115. package/templates/react-native/src/components/ui/navigation/Tabs.tsx +30 -4
  116. package/templates/react-native/src/config/api.ts +12 -1
  117. package/templates/react-native/src/config/sidebar.config.ts +72 -0
  118. package/templates/react-native/src/config/tabNavigation.ts +37 -7
  119. package/templates/react-native/src/db/index.ts +9 -0
  120. package/templates/react-native/src/db/schema.ts +14 -0
  121. package/templates/react-native/src/hooks/useDebounce.ts +17 -0
  122. package/templates/react-native/src/services/socket.service.ts +36 -0
  123. package/templates/react-native/src/store/useAuthStore.ts +122 -27
  124. package/templates/react-native/src/store/useNotificationStore.ts +53 -0
  125. package/templates/react-native/src/types/auth.type.ts +14 -0
  126. package/templates/react-native/tsconfig.json +1 -0
  127. package/templates/react-ts/src/vite-env.d.ts +1 -0
  128. package/templates/mern-react-native/mobile/.env +0 -2
  129. package/templates/mern-react-native/mobile/.env.example +0 -2
  130. package/templates/mern-react-native/mobile/.vscode/extensions.json +0 -1
  131. package/templates/mern-react-native/mobile/.vscode/settings.json +0 -7
  132. package/templates/mern-react-native/mobile/README.md +0 -159
  133. package/templates/mern-react-native/mobile/docs/README.md +0 -17
  134. package/templates/mern-react-native/mobile/docs/components/buttons.md +0 -40
  135. package/templates/mern-react-native/mobile/docs/components/data-display.md +0 -44
  136. package/templates/mern-react-native/mobile/docs/components/feedback.md +0 -36
  137. package/templates/mern-react-native/mobile/docs/components/gestures.md +0 -60
  138. package/templates/mern-react-native/mobile/docs/components/inputs.md +0 -41
  139. package/templates/mern-react-native/mobile/docs/components/layout.md +0 -34
  140. package/templates/mern-react-native/mobile/docs/components/navigation.md +0 -31
  141. package/templates/mern-react-native/mobile/docs/features.md +0 -85
  142. package/templates/mern-react-native/mobile/docs/getting-started.md +0 -47
  143. package/templates/mern-react-native/mobile/src/components/common/HomeHeader.tsx +0 -43
  144. package/templates/mern-react-native/mobile/src/components/settings/SettingToggle.tsx +0 -26
  145. package/templates/mern-react-native/mobile/src/components/settings/SettingsOptions.tsx +0 -47
  146. package/templates/react-native/.env +0 -2
  147. package/templates/react-native/.env.example +0 -2
  148. package/templates/react-native/.vscode/extensions.json +0 -1
  149. package/templates/react-native/.vscode/settings.json +0 -7
  150. package/templates/react-native/README.md +0 -159
  151. package/templates/react-native/docs/README.md +0 -17
  152. package/templates/react-native/docs/components/buttons.md +0 -40
  153. package/templates/react-native/docs/components/data-display.md +0 -44
  154. package/templates/react-native/docs/components/feedback.md +0 -36
  155. package/templates/react-native/docs/components/gestures.md +0 -60
  156. package/templates/react-native/docs/components/inputs.md +0 -41
  157. package/templates/react-native/docs/components/layout.md +0 -34
  158. package/templates/react-native/docs/components/navigation.md +0 -31
  159. package/templates/react-native/docs/features.md +0 -85
  160. package/templates/react-native/docs/getting-started.md +0 -47
  161. package/templates/react-native/src/components/common/HomeHeader.tsx +0 -43
  162. package/templates/react-native/src/components/settings/SettingToggle.tsx +0 -26
  163. package/templates/react-native/src/components/settings/SettingsOptions.tsx +0 -47
@@ -20,6 +20,9 @@ import { Txt } from "../common/Typography";
20
20
  import { Ionicons } from "@expo/vector-icons";
21
21
  import { useSafeAreaInsets } from "react-native-safe-area-context";
22
22
  import { useAuthStore } from "../../store/useAuthStore";
23
+ import { sidebarConfig } from "../../config/sidebar.config";
24
+ import { router } from "expo-router";
25
+ import Image from "./data-display/Image";
23
26
 
24
27
  const { width } = Dimensions.get("window");
25
28
  const SIDEBAR_WIDTH = width * 0.75;
@@ -27,6 +30,7 @@ const SIDEBAR_WIDTH = width * 0.75;
27
30
  interface SidebarProps {
28
31
  isOpen: boolean;
29
32
  onClose: () => void;
33
+ side?: "left" | "right";
30
34
  children?: React.ReactNode;
31
35
  }
32
36
 
@@ -44,24 +48,30 @@ const MenuItem = ({
44
48
  <TouchableOpacity
45
49
  activeOpacity={0.7}
46
50
  onPress={onPress}
47
- className="flex-row items-center py-4 px-4 rounded-2xl"
51
+ className="flex-row items-center py-4 px-6 rounded-2xl mx-2"
48
52
  >
49
53
  <View className="w-10 h-10 rounded-full items-center justify-center bg-primary/10 mr-4">
50
54
  <Ionicons name={icon} size={20} color={colors.primary} />
51
55
  </View>
52
- <Txt variant="h3" className="text-base-content font-medium">
56
+ <Txt variant="md" className="text-base-content font-semibold">
53
57
  {label}
54
58
  </Txt>
55
59
  </TouchableOpacity>
56
60
  );
57
61
 
58
- export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
62
+ export default function Sidebar({
63
+ isOpen,
64
+ onClose,
65
+ side = "left",
66
+ children,
67
+ }: SidebarProps) {
59
68
  const { colorScheme } = useColorScheme();
60
69
  const colors = Colors[colorScheme ?? "light"];
61
70
  const insets = useSafeAreaInsets();
62
71
  const { user, logout } = useAuthStore();
63
72
 
64
- const translateX = useSharedValue(-SIDEBAR_WIDTH);
73
+ const startValue = side === "left" ? -SIDEBAR_WIDTH : SIDEBAR_WIDTH;
74
+ const translateX = useSharedValue(isOpen ? 0 : startValue);
65
75
  const backdropOpacity = useSharedValue(0);
66
76
  const [isRendered, setIsRendered] = useState(false);
67
77
 
@@ -70,24 +80,26 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
70
80
  }
71
81
 
72
82
  useEffect(() => {
83
+ const targetValue = isOpen
84
+ ? 0
85
+ : side === "left"
86
+ ? -SIDEBAR_WIDTH
87
+ : SIDEBAR_WIDTH;
88
+ translateX.value = withTiming(targetValue, {
89
+ duration: 300,
90
+ easing: Easing.out(Easing.ease),
91
+ });
92
+
73
93
  if (isOpen) {
74
- translateX.value = withTiming(0, {
75
- duration: 300,
76
- easing: Easing.out(Easing.ease),
77
- });
78
94
  backdropOpacity.value = withTiming(0.4, { duration: 300 });
79
95
  } else {
80
- translateX.value = withTiming(-SIDEBAR_WIDTH, {
81
- duration: 300,
82
- easing: Easing.in(Easing.ease),
83
- });
84
96
  backdropOpacity.value = withTiming(0, { duration: 300 }, (finished) => {
85
97
  if (finished) {
86
98
  runOnJS(setIsRendered)(false);
87
99
  }
88
100
  });
89
101
  }
90
- }, [isOpen, backdropOpacity, translateX]);
102
+ }, [isOpen, side, backdropOpacity, translateX, startValue]);
91
103
 
92
104
  const sidebarAnimatedStyle = useAnimatedStyle(() => ({
93
105
  transform: [{ translateX: translateX.value }],
@@ -99,6 +111,9 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
99
111
 
100
112
  if (!isRendered && !isOpen) return null;
101
113
 
114
+ const role = user?.role?.toLowerCase() || "default";
115
+ const menuItems = sidebarConfig[role] || sidebarConfig.default;
116
+
102
117
  return (
103
118
  <Portal>
104
119
  <View style={[StyleSheet.absoluteFill, { zIndex: 999 }]}>
@@ -120,12 +135,13 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
120
135
  position: "absolute",
121
136
  top: 0,
122
137
  bottom: 0,
123
- left: 0,
138
+ left: side === "left" ? 0 : undefined,
139
+ right: side === "right" ? 0 : undefined,
124
140
  width: SIDEBAR_WIDTH,
125
141
  backgroundColor: colors.base100,
126
142
  elevation: 24,
127
143
  shadowColor: "#000",
128
- shadowOffset: { width: 5, height: 0 },
144
+ shadowOffset: { width: side === "left" ? 5 : -5, height: 0 },
129
145
  shadowOpacity: 0.2,
130
146
  shadowRadius: 15,
131
147
  overflow: "hidden",
@@ -137,13 +153,17 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
137
153
  <View className="flex-1">
138
154
  {/* Sidebar Header */}
139
155
  <View
140
- className="px-6 bg-base-200 border-b border-base-300 items-center flex flex-row gap-4"
156
+ className="px-6 bg-base-200 border-b border-base-300 items-center flex flex-row gap-4"
141
157
  style={{
142
158
  paddingTop: insets.top > 0 ? insets.top + 12 : 30,
143
- paddingBottom: 8,
159
+ paddingBottom: 16,
144
160
  }}
145
161
  >
146
- <View
162
+ <TouchableOpacity
163
+ onPress={() => {
164
+ onClose();
165
+ router.push("/(tenant)/profile");
166
+ }}
147
167
  className="w-12 h-12 rounded-full bg-primary items-center justify-center"
148
168
  style={{
149
169
  shadowColor: colors.primary,
@@ -153,15 +173,36 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
153
173
  elevation: 8,
154
174
  }}
155
175
  >
156
- <Txt variant="h3">
157
- {user?.name?.charAt(0).toUpperCase() || "U"}
158
- </Txt>
159
- </View>
160
- <View>
161
- <Txt variant="h2" className="text-xl mb-1">
176
+ {user?.avatar?.url ? (
177
+ <View className="w-11 h-11 rounded-full border border-base-300 overflow-hidden">
178
+ <Image
179
+ src={user.avatar.url}
180
+ style={{ width: 44, height: 44 }}
181
+ />
182
+ </View>
183
+ ) : (
184
+ <View className="w-11 h-11 rounded-full bg-primary/15 items-center justify-center border border-primary/10">
185
+ <Ionicons
186
+ name="person"
187
+ size={22}
188
+ color={colors.primary}
189
+ />
190
+ </View>
191
+ )}
192
+ </TouchableOpacity>
193
+ <View className="flex-1">
194
+ <Txt
195
+ variant="md"
196
+ className="font-bold text-base-content"
197
+ numberOfLines={1}
198
+ >
162
199
  {user?.name || "User Name"}
163
200
  </Txt>
164
- <Txt variant="caption" className="text-base-content/60">
201
+ <Txt
202
+ variant="xs"
203
+ className="text-base-content/50"
204
+ numberOfLines={1}
205
+ >
165
206
  {user?.email || "user@example.com"}
166
207
  </Txt>
167
208
  </View>
@@ -169,42 +210,30 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
169
210
 
170
211
  {/* Menu Items */}
171
212
  <View className="flex-1 pt-4">
172
- <MenuItem
173
- icon="home-outline"
174
- label="Dashboard"
175
- onPress={onClose}
176
- colors={colors}
177
- />
178
- <MenuItem
179
- icon="person-outline"
180
- label="Profile"
181
- onPress={onClose}
182
- colors={colors}
183
- />
184
- <MenuItem
185
- icon="settings-outline"
186
- label="Settings"
187
- onPress={onClose}
188
- colors={colors}
189
- />
190
- <MenuItem
191
- icon="help-buoy-outline"
192
- label="Help & Support"
193
- onPress={onClose}
194
- colors={colors}
195
- />
213
+ {menuItems.map((item, index) => (
214
+ <MenuItem
215
+ key={index}
216
+ icon={item.icon}
217
+ label={item.label}
218
+ onPress={() => {
219
+ onClose();
220
+ router.push(item.route as any);
221
+ }}
222
+ colors={colors}
223
+ />
224
+ ))}
196
225
  </View>
197
226
 
198
227
  {/* Footer */}
199
228
  <View
200
- className="p-6"
229
+ className="p-6 border-t border-base-300"
201
230
  style={{
202
231
  paddingBottom: insets.bottom > 0 ? insets.bottom + 10 : 20,
203
232
  }}
204
233
  >
205
234
  <TouchableOpacity
206
235
  activeOpacity={0.8}
207
- className="flex-row items-center justify-center p-3 rounded-xl bg-error/10"
236
+ className="flex-row items-center justify-center p-3 rounded-xl bg-error/10 border border-error/20"
208
237
  onPress={() => {
209
238
  onClose();
210
239
  logout();
@@ -215,11 +244,7 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
215
244
  size={20}
216
245
  color={colors.error}
217
246
  />
218
- <Txt
219
- variant="mid"
220
- className="ml-2 font-bold"
221
- style={{ color: colors.error }}
222
- >
247
+ <Txt variant="md" className="ml-2 font-bold text-error">
223
248
  Log Out
224
249
  </Txt>
225
250
  </TouchableOpacity>
@@ -1,12 +1,26 @@
1
1
  import React from "react";
2
- import { TouchableOpacity, ActivityIndicator, TouchableOpacityProps } from "react-native";
2
+ import {
3
+ TouchableOpacity,
4
+ ActivityIndicator,
5
+ TouchableOpacityProps,
6
+ View,
7
+ } from "react-native";
3
8
  import { useColorScheme } from "nativewind";
4
9
  import { Colors } from "../../../constants/Colors";
5
10
  import { Txt } from "../../common/Typography";
6
11
  import { Ionicons } from "@expo/vector-icons";
7
12
 
8
- export type ButtonVariant = "primary" | "secondary" | "accent" | "info" | "success" | "warning" | "error" | "ghost" | "outline";
9
- export type ButtonSize = "sm" | "md" | "lg";
13
+ export type ButtonVariant =
14
+ | "primary"
15
+ | "secondary"
16
+ | "accent"
17
+ | "info"
18
+ | "success"
19
+ | "warning"
20
+ | "error"
21
+ | "ghost"
22
+ | "outline";
23
+ export type ButtonSize = "xs" | "sm" | "base" | "md" | "lg";
10
24
 
11
25
  export interface ButtonProps extends TouchableOpacityProps {
12
26
  label: string;
@@ -17,17 +31,19 @@ export interface ButtonProps extends TouchableOpacityProps {
17
31
  rightIcon?: keyof typeof Ionicons.glyphMap;
18
32
  isFullWidth?: boolean;
19
33
  soft?: boolean;
34
+ outline?: boolean;
20
35
  }
21
36
 
22
37
  export default function Button({
23
38
  label,
24
39
  variant = "primary",
25
- size = "md",
40
+ size = "base",
26
41
  isLoading = false,
27
42
  leftIcon,
28
43
  rightIcon,
29
44
  isFullWidth = false,
30
45
  soft = false,
46
+ outline = false,
31
47
  style,
32
48
  disabled,
33
49
  ...props
@@ -35,38 +51,73 @@ export default function Button({
35
51
  const { colorScheme } = useColorScheme();
36
52
  const theme = Colors[colorScheme ?? "light"];
37
53
 
38
- const getVariantStyles = (): { bg: string; text: string; border?: string } => {
54
+ const getVariantStyles = (): {
55
+ bg: string;
56
+ text: string;
57
+ border?: string;
58
+ } => {
39
59
  switch (variant) {
40
- case "primary": return { bg: theme.primary, text: theme.primaryContent };
41
- case "secondary": return { bg: theme.secondary, text: theme.secondaryContent };
42
- case "accent": return { bg: theme.accent, text: theme.accentContent };
43
- case "info": return { bg: theme.info, text: "#fff" };
44
- case "success": return { bg: theme.success, text: "#fff" };
45
- case "warning": return { bg: theme.warning, text: "#fff" };
46
- case "error": return { bg: theme.error, text: "#fff" };
47
- case "ghost": return { bg: "transparent", text: theme.baseContent };
48
- case "outline": return { bg: "transparent", text: theme.baseContent, border: theme.base300 };
49
- default: return { bg: theme.primary, text: theme.primaryContent };
60
+ case "primary":
61
+ return { bg: theme.primary, text: theme.primaryContent };
62
+ case "secondary":
63
+ return { bg: theme.secondary, text: theme.secondaryContent };
64
+ case "accent":
65
+ return { bg: theme.accent, text: theme.accentContent };
66
+ case "info":
67
+ return { bg: theme.info, text: "#fff" };
68
+ case "success":
69
+ return { bg: theme.success, text: "#fff" };
70
+ case "warning":
71
+ return { bg: theme.warning, text: "#fff" };
72
+ case "error":
73
+ return { bg: theme.error, text: "#fff" };
74
+ case "ghost":
75
+ return { bg: "transparent", text: theme.baseContent };
76
+ case "outline":
77
+ return {
78
+ bg: "transparent",
79
+ text: theme.baseContent,
80
+ border: theme.base300,
81
+ };
82
+ default:
83
+ return { bg: theme.primary, text: theme.primaryContent };
50
84
  }
51
85
  };
52
86
 
53
- const getSizeStyles = (): { height: number; px: number; fontSize: number; iconSize: number } => {
87
+ const getSizeStyles = (): {
88
+ height: number;
89
+ px: number;
90
+ fontSize: number;
91
+ iconSize: number;
92
+ } => {
54
93
  switch (size) {
55
- case "sm": return { height: 36, px: 12, fontSize: 13, iconSize: 16 };
56
- case "md": return { height: 48, px: 16, fontSize: 15, iconSize: 20 };
57
- case "lg": return { height: 56, px: 24, fontSize: 17, iconSize: 24 };
58
- default: return { height: 48, px: 16, fontSize: 15, iconSize: 20 };
94
+ case "xs":
95
+ return { height: 30, px: 10, fontSize: 12, iconSize: 14 };
96
+ case "sm":
97
+ return { height: 36, px: 12, fontSize: 13, iconSize: 16 };
98
+ case "base":
99
+ return { height: 40, px: 12, fontSize: 14, iconSize: 16 };
100
+ case "md":
101
+ return { height: 48, px: 16, fontSize: 15, iconSize: 20 };
102
+ case "lg":
103
+ return { height: 56, px: 24, fontSize: 17, iconSize: 24 };
104
+ default:
105
+ return { height: 40, px: 12, fontSize: 14, iconSize: 16 };
59
106
  }
60
107
  };
61
108
 
62
109
  const vStyles = getVariantStyles();
63
- if (soft && variant !== "ghost" && variant !== "outline") {
110
+ if (outline) {
111
+ vStyles.border = vStyles.bg === "transparent" ? theme.base300 : vStyles.bg;
112
+ vStyles.text = vStyles.bg === "transparent" ? theme.baseContent : vStyles.bg;
113
+ vStyles.bg = "transparent";
114
+ } else if (soft && variant !== "ghost" && variant !== "outline") {
64
115
  vStyles.text = vStyles.bg;
65
116
  vStyles.bg = vStyles.bg + "26"; // ~15% opacity
66
117
  }
67
118
 
68
119
  const sStyles = getSizeStyles();
69
-
120
+
70
121
  const isDisabled = disabled || isLoading;
71
122
 
72
123
  return (
@@ -83,7 +134,7 @@ export default function Button({
83
134
  alignItems: "center",
84
135
  justifyContent: "center",
85
136
  opacity: isDisabled ? 0.6 : 1,
86
- borderWidth: variant === "outline" ? 1 : 0,
137
+ borderWidth: (variant === "outline" || outline) ? 1 : 0,
87
138
  borderColor: vStyles.border,
88
139
  width: isFullWidth ? "100%" : undefined,
89
140
  },
@@ -91,21 +142,39 @@ export default function Button({
91
142
  ]}
92
143
  {...props}
93
144
  >
94
- {isLoading ? (
95
- <ActivityIndicator color={vStyles.text} size="small" />
96
- ) : (
97
- <>
98
- {leftIcon && (
99
- <Ionicons name={leftIcon} size={sStyles.iconSize} color={vStyles.text} style={{ marginRight: 8 }} />
100
- )}
101
- <Txt style={{ color: vStyles.text, fontSize: sStyles.fontSize, fontWeight: "600" }}>
102
- {label}
103
- </Txt>
104
- {rightIcon && (
105
- <Ionicons name={rightIcon} size={sStyles.iconSize} color={vStyles.text} style={{ marginLeft: 8 }} />
106
- )}
107
- </>
145
+ {/* Loading overlay */}
146
+ {isLoading && (
147
+ <View style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center" }}>
148
+ <ActivityIndicator color={vStyles.text} size="small" />
149
+ </View>
108
150
  )}
151
+
152
+ {/* Button content (hidden while loading to maintain dimensions) */}
153
+ <View style={{ flexDirection: "row", alignItems: "center", opacity: isLoading ? 0 : 1 }}>
154
+ {leftIcon && (
155
+ <Ionicons
156
+ name={leftIcon}
157
+ size={sStyles.iconSize}
158
+ color={vStyles.text}
159
+ style={{ marginRight: 8 }}
160
+ />
161
+ )}
162
+ <Txt
163
+ color={vStyles.text}
164
+ weight="semibold"
165
+ style={{ fontSize: sStyles.fontSize }}
166
+ >
167
+ {label}
168
+ </Txt>
169
+ {rightIcon && (
170
+ <Ionicons
171
+ name={rightIcon}
172
+ size={sStyles.iconSize}
173
+ color={vStyles.text}
174
+ style={{ marginLeft: 8 }}
175
+ />
176
+ )}
177
+ </View>
109
178
  </TouchableOpacity>
110
179
  );
111
180
  }
@@ -10,6 +10,7 @@ export interface IconButtonProps extends TouchableOpacityProps {
10
10
  variant?: ButtonVariant;
11
11
  size?: ButtonSize;
12
12
  isLoading?: boolean;
13
+ color?: string;
13
14
  }
14
15
 
15
16
  export default function IconButton({
@@ -17,6 +18,7 @@ export default function IconButton({
17
18
  variant = "ghost",
18
19
  size = "md",
19
20
  isLoading = false,
21
+ color,
20
22
  style,
21
23
  disabled,
22
24
  ...props
@@ -73,9 +75,9 @@ export default function IconButton({
73
75
  {...props}
74
76
  >
75
77
  {isLoading ? (
76
- <ActivityIndicator color={vStyles.iconColor} size="small" />
78
+ <ActivityIndicator color={color || vStyles.iconColor} size="small" />
77
79
  ) : (
78
- <Ionicons name={icon} size={sStyles.iconSize} color={vStyles.iconColor} />
80
+ <Ionicons name={icon} size={sStyles.iconSize} color={color || vStyles.iconColor} />
79
81
  )}
80
82
  </TouchableOpacity>
81
83
  );
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { View, TouchableOpacity, Text } from "react-native";
2
+ import { View, TouchableOpacity } from "react-native";
3
3
  import Animated, {
4
4
  useAnimatedStyle,
5
5
  useSharedValue,
@@ -11,6 +11,7 @@ import Animated, {
11
11
  import { Ionicons } from "@expo/vector-icons";
12
12
  import { useColorScheme } from "nativewind";
13
13
  import { Colors } from "../../../constants/Colors";
14
+ import { Txt } from "../../common/Typography";
14
15
 
15
16
  export interface AccordionProps {
16
17
  title: string;
@@ -60,7 +61,7 @@ export default function Accordion({ title, children }: AccordionProps) {
60
61
  onPress={toggleAccordion}
61
62
  style={{ padding: 16, flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}
62
63
  >
63
- <Text style={{ color: theme.baseContent, fontWeight: "600", fontSize: 16 }}>{title}</Text>
64
+ <Txt color={theme.baseContent} weight="semibold" variant="md">{title}</Txt>
64
65
  <Animated.View style={iconStyle}>
65
66
  <Ionicons name="chevron-down" size={24} color={theme.baseContent} />
66
67
  </Animated.View>
@@ -1,8 +1,9 @@
1
1
  import React from "react";
2
- import { View, Text, ViewProps } from "react-native";
2
+ import { View, ViewProps } from "react-native";
3
3
  import { Image } from "expo-image";
4
4
  import { useColorScheme } from "nativewind";
5
5
  import { Colors } from "../../../constants/Colors";
6
+ import { Txt } from "../../common/Typography";
6
7
 
7
8
  export interface AvatarProps extends ViewProps {
8
9
  url?: string;
@@ -37,9 +38,9 @@ export default function Avatar({ url, initials, size = 48, style, ...props }: Av
37
38
  contentFit="cover"
38
39
  />
39
40
  ) : (
40
- <Text style={{ color: theme.baseContent, fontSize: size * 0.4, fontWeight: "600" }}>
41
+ <Txt color={theme.baseContent} weight="semibold" style={{ fontSize: size * 0.4 }}>
41
42
  {initials?.substring(0, 2).toUpperCase()}
42
- </Text>
43
+ </Txt>
43
44
  )}
44
45
  </View>
45
46
  );
@@ -1,19 +1,28 @@
1
1
  import React from "react";
2
- import { View, Text, ViewProps } from "react-native";
2
+ import { View, ViewProps } from "react-native";
3
3
  import { useColorScheme } from "nativewind";
4
4
  import { Colors } from "../../../constants/Colors";
5
+ import { Txt } from "../../common/Typography";
5
6
 
6
7
  export interface BadgeProps extends ViewProps {
7
8
  label: string;
8
- variant?: "primary" | "secondary" | "accent" | "info" | "success" | "warning" | "error" | "neutral";
9
- size?: "sm" | "md" | "lg";
9
+ variant?:
10
+ | "primary"
11
+ | "secondary"
12
+ | "accent"
13
+ | "info"
14
+ | "success"
15
+ | "warning"
16
+ | "error"
17
+ | "neutral";
18
+ size?: "xs" | "sm" | "base" | "md" | "lg";
10
19
  soft?: boolean;
11
20
  }
12
21
 
13
22
  export default function Badge({
14
23
  label,
15
24
  variant = "primary",
16
- size = "md",
25
+ size = "base",
17
26
  soft = false,
18
27
  style,
19
28
  ...props
@@ -23,24 +32,39 @@ export default function Badge({
23
32
 
24
33
  const getVariantStyles = (): { bg: string; text: string } => {
25
34
  switch (variant) {
26
- case "secondary": return { bg: theme.secondary, text: theme.secondaryContent };
27
- case "accent": return { bg: theme.accent, text: theme.accentContent };
28
- case "info": return { bg: theme.info, text: "#fff" };
29
- case "success": return { bg: theme.success, text: "#fff" };
30
- case "warning": return { bg: theme.warning, text: "#fff" };
31
- case "error": return { bg: theme.error, text: "#fff" };
32
- case "neutral": return { bg: theme.neutral, text: theme.neutralContent };
35
+ case "secondary":
36
+ return { bg: theme.secondary, text: theme.secondaryContent };
37
+ case "accent":
38
+ return { bg: theme.accent, text: theme.accentContent };
39
+ case "info":
40
+ return { bg: theme.info, text: "#fff" };
41
+ case "success":
42
+ return { bg: theme.success, text: "#fff" };
43
+ case "warning":
44
+ return { bg: theme.warning, text: "#fff" };
45
+ case "error":
46
+ return { bg: theme.error, text: "#fff" };
47
+ case "neutral":
48
+ return { bg: theme.neutral, text: theme.neutralContent };
33
49
  case "primary":
34
- default: return { bg: theme.primary, text: theme.primaryContent };
50
+ default:
51
+ return { bg: theme.primary, text: theme.primaryContent };
35
52
  }
36
53
  };
37
54
 
38
55
  const getSizeStyles = () => {
39
56
  switch (size) {
40
- case "sm": return { height: 20, px: 6, fontSize: 10 };
41
- case "lg": return { height: 28, px: 12, fontSize: 14 };
57
+ case "xs":
58
+ return { height: 16, px: 4, fontSize: 6 };
59
+ case "sm":
60
+ return { height: 18, px: 6, fontSize: 8 };
61
+ case "base":
62
+ return { height: 22, px: 8, fontSize: 10 };
63
+ case "lg":
64
+ return { height: 28, px: 12, fontSize: 14 };
42
65
  case "md":
43
- default: return { height: 24, px: 8, fontSize: 12 };
66
+ default:
67
+ return { height: 24, px: 8, fontSize: 12 };
44
68
  }
45
69
  };
46
70
 
@@ -68,9 +92,13 @@ export default function Badge({
68
92
  ]}
69
93
  {...props}
70
94
  >
71
- <Text style={{ color: vStyles.text, fontSize: sStyles.fontSize, fontWeight: "600" }}>
95
+ <Txt
96
+ color={vStyles.text}
97
+ weight="semibold"
98
+ style={{ fontSize: sStyles.fontSize }}
99
+ >
72
100
  {label}
73
- </Text>
101
+ </Txt>
74
102
  </View>
75
103
  );
76
104
  }
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { StatusBar } from "react-native";
2
3
  import ImageViewing from "react-native-image-viewing";
3
4
  import { ImageSource } from "react-native-image-viewing/dist/@types";
4
5
 
@@ -9,15 +10,24 @@ export interface ImageViewerProps {
9
10
  onRequestClose: () => void;
10
11
  }
11
12
 
12
- export default function ImageViewer({ images, imageIndex, visible, onRequestClose }: ImageViewerProps) {
13
+ export default function ImageViewer({
14
+ images,
15
+ imageIndex,
16
+ visible,
17
+ onRequestClose,
18
+ }: ImageViewerProps) {
13
19
  return (
14
- <ImageViewing
15
- images={images}
16
- imageIndex={imageIndex}
17
- visible={visible}
18
- onRequestClose={onRequestClose}
19
- swipeToCloseEnabled
20
- doubleTapToZoomEnabled
21
- />
20
+ <>
21
+ <StatusBar hidden={visible} />
22
+ <ImageViewing
23
+ images={images}
24
+ imageIndex={imageIndex}
25
+ visible={visible}
26
+ onRequestClose={onRequestClose}
27
+ swipeToCloseEnabled
28
+ doubleTapToZoomEnabled
29
+ presentationStyle="overFullScreen"
30
+ />
31
+ </>
22
32
  );
23
33
  }