@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
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
- import { View, Text } from "react-native";
2
+ import { View } 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 CircularProgressProps {
7
8
  progress: number;
@@ -28,9 +29,9 @@ export default function CircularProgress({ progress, size = 60, strokeWidth = 6
28
29
  }}
29
30
  >
30
31
  <View style={{ transform: [{ rotate: "-45deg" }] }}>
31
- <Text style={{ color: theme.baseContent, fontWeight: "700", fontSize: size * 0.25 }}>
32
- {progress}%
33
- </Text>
32
+ <Txt color={theme.baseContent} weight="bold" style={{ fontSize: size * 0.25 }}>
33
+ {Math.round(progress)}%
34
+ </Txt>
34
35
  </View>
35
36
  </View>
36
37
  );
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect } from "react";
2
- import { View, StyleProp, ViewStyle, Text } from "react-native";
2
+ import { View, StyleProp, ViewStyle } from "react-native";
3
3
  import Animated, {
4
4
  useSharedValue,
5
5
  useAnimatedStyle,
@@ -7,6 +7,7 @@ import Animated, {
7
7
  } from "react-native-reanimated";
8
8
  import { useColorScheme } from "nativewind";
9
9
  import { Colors } from "../../../constants/Colors";
10
+ import { Txt } from "../../common/Typography";
10
11
 
11
12
  export interface ProgressBarProps {
12
13
  progress: number; // 0 to 100
@@ -33,8 +34,8 @@ export default function ProgressBar({ progress, color, height = 8, label, style
33
34
  <View style={[{ width: "100%", marginBottom: label ? 16 : 0 }, style]}>
34
35
  {label && (
35
36
  <View style={{ flexDirection: "row", justifyContent: "space-between", marginBottom: 6 }}>
36
- <Text style={{ color: theme.baseContent, fontSize: 13, fontWeight: "500" }}>{label}</Text>
37
- <Text style={{ color: theme.secondary, fontSize: 13 }}>{Math.round(progress)}%</Text>
37
+ <Txt color={theme.baseContent} variant="sm" weight="semibold">{label}</Txt>
38
+ <Txt color={theme.secondary} variant="sm">{Math.round(progress)}%</Txt>
38
39
  </View>
39
40
  )}
40
41
  <View style={{ width: "100%", height, backgroundColor: theme.base300, borderRadius: height / 2, overflow: "hidden" }}>
@@ -16,6 +16,7 @@ export interface SkeletonProps {
16
16
  borderRadius?: number;
17
17
  style?: StyleProp<ViewStyle>;
18
18
  variant?: "circular" | "rectangular" | "text";
19
+ className?: string;
19
20
  }
20
21
 
21
22
  export default function Skeleton({
@@ -24,6 +25,7 @@ export default function Skeleton({
24
25
  borderRadius,
25
26
  style,
26
27
  variant = "rectangular",
28
+ className=""
27
29
  }: SkeletonProps) {
28
30
  const { colorScheme } = useColorScheme();
29
31
  const theme = Colors[colorScheme ?? "light"];
@@ -76,6 +78,7 @@ export default function Skeleton({
76
78
  animatedStyle,
77
79
  style,
78
80
  ]}
81
+ className={className}
79
82
  />
80
83
  );
81
84
  }
@@ -12,7 +12,7 @@ import { Colors } from "../../../constants/Colors";
12
12
  import { EvilIcons } from "@expo/vector-icons";
13
13
 
14
14
  export interface SpinnerProps {
15
- size?: number;
15
+ size?: number | "xs" | "sm" | "md" | "lg" | "xl";
16
16
  color?: string;
17
17
  style?: StyleProp<ViewStyle>;
18
18
  }
@@ -22,6 +22,14 @@ export default function Spinner({ size = 24, color, style }: SpinnerProps) {
22
22
  const theme = Colors[colorScheme ?? "light"];
23
23
  const rotation = useSharedValue(0);
24
24
 
25
+ const numericSize = typeof size === "number" ? size : {
26
+ xs: 16,
27
+ sm: 20,
28
+ md: 24,
29
+ lg: 32,
30
+ xl: 40,
31
+ }[size] || 24;
32
+
25
33
  useEffect(() => {
26
34
  rotation.value = withRepeat(
27
35
  withTiming(360, { duration: 2000, easing: Easing.linear }),
@@ -35,8 +43,8 @@ export default function Spinner({ size = 24, color, style }: SpinnerProps) {
35
43
  }));
36
44
 
37
45
  return (
38
- <Animated.View style={[animatedStyle, style, { width: size, height: size, alignItems: "center", justifyContent: "center" }]}>
39
- <EvilIcons name="spinner" size={size} color={color || theme.primary} />
46
+ <Animated.View style={[animatedStyle, style, { width: numericSize, height: numericSize, alignItems: "center", justifyContent: "center" }]}>
47
+ <EvilIcons name="spinner" size={numericSize} color={color || theme.primary} />
40
48
  </Animated.View>
41
49
  );
42
50
  }
@@ -1,8 +1,9 @@
1
1
  import React from "react";
2
- import { TouchableOpacity, View, Text } from "react-native";
2
+ import { TouchableOpacity, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
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 CheckboxProps {
8
9
  checked: boolean;
@@ -47,7 +48,7 @@ export default function Checkbox({
47
48
  >
48
49
  {checked && <Ionicons name="checkmark" size={16} color={theme.primaryContent} />}
49
50
  </View>
50
- {label && <Text style={{ color: theme.baseContent, fontSize: 15 }}>{label}</Text>}
51
+ {label && <Txt color={theme.baseContent} variant="base">{label}</Txt>}
51
52
  </TouchableOpacity>
52
53
  );
53
54
  }
@@ -0,0 +1,186 @@
1
+ import React, { useState } from "react";
2
+ import { View, TouchableOpacity, ScrollView } from "react-native";
3
+ import { Txt } from "../../common/Typography";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+ import { useColorScheme } from "nativewind";
6
+ import { Colors } from "../../../constants/Colors";
7
+ import BottomModal from "../BottomModal";
8
+ import Button from "../buttons/Button";
9
+ import dayjs from "dayjs";
10
+
11
+ interface DatePickerProps {
12
+ label: string;
13
+ value: string; // YYYY-MM-DD
14
+ onChange: (value: string) => void;
15
+ placeholder?: string;
16
+ minimumDate?: string; // YYYY-MM-DD
17
+ }
18
+
19
+ export default function DatePicker({
20
+ label,
21
+ value,
22
+ onChange,
23
+ placeholder = "Select Date",
24
+ minimumDate,
25
+ }: DatePickerProps) {
26
+ const { colorScheme } = useColorScheme();
27
+ const colors = Colors[colorScheme ?? "light"];
28
+ const [isOpen, setIsOpen] = useState(false);
29
+
30
+ // Parse initial selected date or default to today
31
+ const selectedDate = value ? dayjs(value) : dayjs();
32
+ const [currentMonth, setCurrentMonth] = useState(selectedDate);
33
+
34
+ // Month navigation
35
+ const handlePrevMonth = () => {
36
+ setCurrentMonth((prev) => prev.subtract(1, "month"));
37
+ };
38
+
39
+ const handleNextMonth = () => {
40
+ setCurrentMonth((prev) => prev.add(1, "month"));
41
+ };
42
+
43
+ // Generate days in current month grid
44
+ const daysInMonth = currentMonth.daysInMonth();
45
+ const startDayOfWeek = currentMonth.startOf("month").day(); // 0 (Sun) - 6 (Sat)
46
+
47
+ const daysArray: { dayNum: number; dateString: string; isCurrentMonth: boolean }[] = [];
48
+
49
+ // Padding days from previous month
50
+ const prevMonth = currentMonth.subtract(1, "month");
51
+ const prevMonthDays = prevMonth.daysInMonth();
52
+ for (let i = startDayOfWeek - 1; i >= 0; i--) {
53
+ const dayNum = prevMonthDays - i;
54
+ daysArray.push({
55
+ dayNum,
56
+ dateString: prevMonth.date(dayNum).format("YYYY-MM-DD"),
57
+ isCurrentMonth: false,
58
+ });
59
+ }
60
+
61
+ // Days of current month
62
+ for (let i = 1; i <= daysInMonth; i++) {
63
+ daysArray.push({
64
+ dayNum: i,
65
+ dateString: currentMonth.date(i).format("YYYY-MM-DD"),
66
+ isCurrentMonth: true,
67
+ });
68
+ }
69
+
70
+ // Padding days for next month to complete the row
71
+ const remainingDays = 42 - daysArray.length; // 6 rows * 7 days = 42 total slots
72
+ const nextMonth = currentMonth.add(1, "month");
73
+ for (let i = 1; i <= remainingDays; i++) {
74
+ daysArray.push({
75
+ dayNum: i,
76
+ dateString: nextMonth.date(i).format("YYYY-MM-DD"),
77
+ isCurrentMonth: false,
78
+ });
79
+ }
80
+
81
+ const handleDateSelect = (dateStr: string) => {
82
+ if (minimumDate && dayjs(dateStr).isBefore(dayjs(minimumDate), "day")) {
83
+ return; // Date is disabled
84
+ }
85
+ onChange(dateStr);
86
+ setIsOpen(false);
87
+ };
88
+
89
+ const formattedDisplay = value ? dayjs(value).format("MMM DD, YYYY") : "";
90
+
91
+ return (
92
+ <View className="flex-1">
93
+ <Txt className="text-xs text-base-content/60 uppercase mb-2 font-medium">
94
+ {label}
95
+ </Txt>
96
+ <TouchableOpacity
97
+ onPress={() => setIsOpen(true)}
98
+ activeOpacity={0.8}
99
+ className="bg-base-100 rounded-2xl px-4 py-3 border border-base-300 flex-row justify-between items-center h-12"
100
+ >
101
+ <Txt className={formattedDisplay ? "text-base-content font-medium" : "text-base-content/40 font-medium"}>
102
+ {formattedDisplay || placeholder}
103
+ </Txt>
104
+ <Ionicons name="calendar-outline" size={18} color={colors.primary} />
105
+ </TouchableOpacity>
106
+
107
+ <BottomModal isOpen={isOpen} onClose={() => setIsOpen(false)} heightPercent={0.65}>
108
+ <View className="p-4 flex-1">
109
+ <Txt variant="lg" className="font-bold text-center mb-4">
110
+ {label}
111
+ </Txt>
112
+
113
+ {/* Month & Year Navigation Header */}
114
+ <View className="flex-row justify-between items-center px-2 mb-4">
115
+ <TouchableOpacity onPress={handlePrevMonth} className="p-2 bg-base-200 rounded-full">
116
+ <Ionicons name="chevron-back" size={20} color={colors.primary} />
117
+ </TouchableOpacity>
118
+ <Txt className="font-bold text-base-content text-base">
119
+ {currentMonth.format("MMMM YYYY")}
120
+ </Txt>
121
+ <TouchableOpacity onPress={handleNextMonth} className="p-2 bg-base-200 rounded-full">
122
+ <Ionicons name="chevron-forward" size={20} color={colors.primary} />
123
+ </TouchableOpacity>
124
+ </View>
125
+
126
+ {/* Day Names Header */}
127
+ <View className="flex-row justify-between mb-2">
128
+ {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (
129
+ <View key={day} className="flex-1 items-center">
130
+ <Txt variant="xs" className="text-base-content/40 font-bold">
131
+ {day}
132
+ </Txt>
133
+ </View>
134
+ ))}
135
+ </View>
136
+
137
+ {/* Days Grid */}
138
+ <ScrollView showsVerticalScrollIndicator={false}>
139
+ <View className="flex-row flex-wrap">
140
+ {daysArray.map((item, idx) => {
141
+ const isSelected = value === item.dateString;
142
+ const isToday = dayjs().format("YYYY-MM-DD") === item.dateString;
143
+ const isDisabled = !!(minimumDate && dayjs(item.dateString).isBefore(dayjs(minimumDate), "day"));
144
+
145
+ return (
146
+ <TouchableOpacity
147
+ key={idx}
148
+ onPress={() => handleDateSelect(item.dateString)}
149
+ disabled={isDisabled}
150
+ style={{ width: "14.28%", aspectRatio: 1 }}
151
+ className={`items-center justify-center rounded-2xl my-1 relative ${
152
+ isSelected
153
+ ? "bg-primary"
154
+ : isToday
155
+ ? "bg-primary/10 border border-primary/30"
156
+ : ""
157
+ } ${isDisabled ? "opacity-25" : ""}`}
158
+ >
159
+ <Txt
160
+ className={`font-semibold ${
161
+ isSelected
162
+ ? "text-white"
163
+ : item.isCurrentMonth
164
+ ? "text-base-content"
165
+ : "text-base-content/30"
166
+ }`}
167
+ >
168
+ {item.dayNum}
169
+ </Txt>
170
+ {isToday && !isSelected && (
171
+ <View className="absolute bottom-1 w-1 h-1 bg-primary rounded-full" />
172
+ )}
173
+ </TouchableOpacity>
174
+ );
175
+ })}
176
+ </View>
177
+ </ScrollView>
178
+
179
+ <View className="pt-4 border-t border-base-200">
180
+ <Button label="Close" variant="outline" onPress={() => setIsOpen(false)} className="w-full" />
181
+ </View>
182
+ </View>
183
+ </BottomModal>
184
+ </View>
185
+ );
186
+ }
@@ -0,0 +1,129 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import * as ImagePicker from "expo-image-picker";
4
+ import { Txt } from "@/components/common/Typography";
5
+ import BottomModal from "@/components/ui/BottomModal";
6
+ import Button from "@/components/ui/buttons/Button";
7
+
8
+ export type ImagePickerSource = "camera" | "gallery" | "both";
9
+
10
+ export interface ImagePickerResult {
11
+ uri: string;
12
+ name: string;
13
+ type: string;
14
+ }
15
+
16
+ export interface ImagePickerModalProps {
17
+ isOpen: boolean;
18
+ onClose: () => void;
19
+ onImageSelected?: (image: ImagePickerResult) => void;
20
+ onImagesSelected?: (images: ImagePickerResult[]) => void;
21
+ source?: ImagePickerSource;
22
+ title?: string;
23
+ subtitle?: string;
24
+ allowsEditing?: boolean;
25
+ allowsMultipleSelection?: boolean;
26
+ }
27
+
28
+ export default function ImagePickerModal({
29
+ isOpen,
30
+ onClose,
31
+ onImageSelected,
32
+ onImagesSelected,
33
+ source = "both",
34
+ title = "Select Image Source",
35
+ subtitle = "Choose where to pick your image from",
36
+ allowsEditing = false,
37
+ allowsMultipleSelection = false,
38
+ }: ImagePickerModalProps) {
39
+
40
+ const launchPicker = async (type: "gallery" | "camera") => {
41
+ onClose();
42
+
43
+ // Delay picker execution to allow the modal dismiss animation to finish smoothly
44
+ setTimeout(async () => {
45
+ try {
46
+ let result: ImagePicker.ImagePickerResult;
47
+
48
+ if (type === "gallery") {
49
+ const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
50
+ if (!perm.granted) return;
51
+
52
+ result = await ImagePicker.launchImageLibraryAsync({
53
+ mediaTypes: ["images"],
54
+ quality: 0.8,
55
+ allowsEditing: !allowsMultipleSelection && allowsEditing, // multi-select cannot have editing on some OS
56
+ allowsMultipleSelection,
57
+ aspect: allowsEditing ? [16, 9] : undefined, // Assuming a common aspect ratio if cropped
58
+ });
59
+ } else {
60
+ const perm = await ImagePicker.requestCameraPermissionsAsync();
61
+ if (!perm.granted) return;
62
+
63
+ result = await ImagePicker.launchCameraAsync({
64
+ mediaTypes: ["images"],
65
+ quality: 0.8,
66
+ allowsEditing,
67
+ aspect: allowsEditing ? [16, 9] : undefined,
68
+ });
69
+ }
70
+
71
+ if (result && !result.canceled && result.assets && result.assets.length > 0) {
72
+ const mappedAssets = result.assets.map(asset => {
73
+ const filename = asset.uri.split("/").pop() || "image.jpg";
74
+ const match = /\.(\w+)$/.exec(filename);
75
+ const mimeType = match ? `image/${match[1]}` : "image/jpeg";
76
+ return {
77
+ uri: asset.uri,
78
+ name: filename,
79
+ type: mimeType,
80
+ };
81
+ });
82
+
83
+ if (allowsMultipleSelection && onImagesSelected) {
84
+ onImagesSelected(mappedAssets);
85
+ } else if (onImageSelected && mappedAssets.length > 0) {
86
+ onImageSelected(mappedAssets[0]);
87
+ }
88
+ }
89
+ } catch (err) {
90
+ console.error("ImagePicker error:", err);
91
+ }
92
+ }, 350);
93
+ };
94
+
95
+ return (
96
+ <BottomModal isOpen={isOpen} onClose={onClose}>
97
+ <View className="p-6">
98
+ <Txt variant="lg" className="font-bold mb-1 text-center">
99
+ {title}
100
+ </Txt>
101
+ {subtitle ? (
102
+ <Txt className="text-base-content/60 text-center mb-6 text-sm">
103
+ {subtitle}
104
+ </Txt>
105
+ ) : null}
106
+
107
+ <View className="gap-3">
108
+ {(source === "both" || source === "gallery") && (
109
+ <Button
110
+ label="Choose from Gallery"
111
+ leftIcon="images-outline"
112
+ variant="primary"
113
+ onPress={() => launchPicker("gallery")}
114
+ />
115
+ )}
116
+
117
+ {(source === "both" || source === "camera") && (
118
+ <Button
119
+ label="Take Photo with Camera"
120
+ leftIcon="camera-outline"
121
+ variant={source === "camera" ? "primary" : "outline"}
122
+ onPress={() => launchPicker("camera")}
123
+ />
124
+ )}
125
+ </View>
126
+ </View>
127
+ </BottomModal>
128
+ );
129
+ }
@@ -1,8 +1,14 @@
1
1
  import React, { useState } from "react";
2
- import { View, TextInput, TextInputProps, TouchableOpacity, Text } from "react-native";
2
+ import {
3
+ View,
4
+ TextInput,
5
+ TextInputProps,
6
+ TouchableOpacity,
7
+ } from "react-native";
3
8
  import { useColorScheme } from "nativewind";
4
9
  import { Colors } from "../../../constants/Colors";
5
10
  import { Ionicons } from "@expo/vector-icons";
11
+ import { Txt } from "../../common/Typography";
6
12
 
7
13
  export interface InputProps extends TextInputProps {
8
14
  label?: string;
@@ -11,7 +17,10 @@ export interface InputProps extends TextInputProps {
11
17
  rightIcon?: keyof typeof Ionicons.glyphMap;
12
18
  onRightIconPress?: () => void;
13
19
  helperText?: string;
14
- containerStyle?: import("react-native").StyleProp<import("react-native").ViewStyle>;
20
+ containerStyle?: import("react-native").StyleProp<
21
+ import("react-native").ViewStyle
22
+ >;
23
+ labelOnBorder?: boolean;
15
24
  }
16
25
 
17
26
  export default function Input({
@@ -23,6 +32,7 @@ export default function Input({
23
32
  helperText,
24
33
  style,
25
34
  containerStyle,
35
+ labelOnBorder = false,
26
36
  ...props
27
37
  }: InputProps) {
28
38
  const { colorScheme } = useColorScheme();
@@ -32,17 +42,44 @@ export default function Input({
32
42
  const borderColor = error
33
43
  ? theme.error
34
44
  : isFocused
35
- ? theme.primary
36
- : theme.base300;
45
+ ? theme.primary
46
+ : theme.base300;
37
47
 
38
48
  return (
39
- <View className="mb-4">
40
- {label && (
41
- <Text style={{ color: theme.baseContent, marginBottom: 6, fontWeight: "500" }}>
42
- {label}
43
- </Text>
49
+ <View className="mb-4 relative">
50
+ {!labelOnBorder && label && (
51
+ <Txt
52
+ color={theme.baseContent}
53
+ weight="semibold"
54
+ style={{ marginBottom: 6 }}
55
+ >
56
+ {label} {error ? "*" : ""}{" "}
57
+ </Txt>
44
58
  )}
45
-
59
+
60
+ {labelOnBorder && label && (
61
+ <View
62
+ style={{
63
+ position: "absolute",
64
+ top: -6,
65
+ left: 12,
66
+ backgroundColor: theme.base100,
67
+ paddingHorizontal: 6,
68
+ zIndex: 10,
69
+ }}
70
+ >
71
+ <Txt
72
+ color={
73
+ error ? theme.error : isFocused ? theme.primary : theme.secondary
74
+ }
75
+ variant="xs"
76
+ weight="semibold"
77
+ >
78
+ {label} {error ? "*" : ""}
79
+ </Txt>
80
+ </View>
81
+ )}
82
+
46
83
  <View
47
84
  style={[
48
85
  {
@@ -66,7 +103,7 @@ export default function Input({
66
103
  style={{ marginRight: 8 }}
67
104
  />
68
105
  )}
69
-
106
+
70
107
  <TextInput
71
108
  style={[
72
109
  {
@@ -90,7 +127,10 @@ export default function Input({
90
127
  />
91
128
 
92
129
  {rightIcon && (
93
- <TouchableOpacity onPress={onRightIconPress} disabled={!onRightIconPress}>
130
+ <TouchableOpacity
131
+ onPress={onRightIconPress}
132
+ disabled={!onRightIconPress}
133
+ >
94
134
  <Ionicons
95
135
  name={rightIcon}
96
136
  size={20}
@@ -102,7 +142,7 @@ export default function Input({
102
142
  </View>
103
143
 
104
144
  {(error || helperText) && (
105
- <Text
145
+ <Txt
106
146
  style={{
107
147
  color: error ? theme.error : theme.secondary,
108
148
  fontSize: 12,
@@ -111,7 +151,7 @@ export default function Input({
111
151
  }}
112
152
  >
113
153
  {error || helperText}
114
- </Text>
154
+ </Txt>
115
155
  )}
116
156
  </View>
117
157
  );