@imksh/create 1.0.24 → 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.
- package/package.json +1 -1
- package/templates/mern-react-native/client/src/vite-env.d.ts +1 -0
- package/templates/mern-react-native/mobile/babel.config.js +1 -0
- package/templates/mern-react-native/mobile/drizzle.config.ts +8 -0
- package/templates/mern-react-native/mobile/expo-env.d.ts +3 -0
- package/templates/mern-react-native/mobile/package.json +6 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/_layout.tsx +12 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/change-password.tsx +112 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +160 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/mern-react-native/mobile/src/app/{login.tsx → (auth)/login.tsx} +69 -21
- package/templates/mern-react-native/mobile/src/app/(tabs)/index.tsx +18 -17
- package/templates/mern-react-native/mobile/src/app/(tabs)/profile.tsx +23 -60
- package/templates/mern-react-native/mobile/src/app/_layout.tsx +22 -4
- package/templates/mern-react-native/mobile/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/mern-react-native/mobile/src/components/common/LocationLoader.tsx +591 -0
- package/templates/mern-react-native/mobile/src/components/common/ScreenHeader.tsx +41 -17
- package/templates/mern-react-native/mobile/src/components/common/TabHeader.tsx +58 -0
- package/templates/mern-react-native/mobile/src/components/common/Typography.tsx +49 -30
- package/templates/mern-react-native/mobile/src/components/common/Welcome.tsx +2 -2
- package/templates/mern-react-native/mobile/src/components/ui/BottomModal.tsx +31 -20
- package/templates/mern-react-native/mobile/src/components/ui/ClassicTabBar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/CustomTabBar.tsx +108 -71
- package/templates/mern-react-native/mobile/src/components/ui/Sidebar.tsx +81 -56
- package/templates/mern-react-native/mobile/src/components/ui/buttons/Button.tsx +106 -37
- package/templates/mern-react-native/mobile/src/components/ui/buttons/IconButton.tsx +4 -2
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Accordion.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Avatar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Badge.tsx +45 -17
- package/templates/mern-react-native/mobile/src/components/ui/data-display/ImageViewer.tsx +19 -9
- package/templates/mern-react-native/mobile/src/components/ui/feedback/CircularProgress.tsx +5 -4
- package/templates/mern-react-native/mobile/src/components/ui/feedback/ProgressBar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/feedback/Skeleton.tsx +3 -0
- package/templates/mern-react-native/mobile/src/components/ui/feedback/Spinner.tsx +11 -3
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Checkbox.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Input.tsx +54 -14
- package/templates/mern-react-native/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/OTPInput.tsx +32 -9
- package/templates/mern-react-native/mobile/src/components/ui/inputs/PasswordInput.tsx +2 -1
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Radio.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/SearchBar.tsx +41 -4
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Slider.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Switch.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/TextArea.tsx +2 -1
- package/templates/mern-react-native/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/mern-react-native/mobile/src/components/ui/layout/Grid.tsx +52 -10
- package/templates/mern-react-native/mobile/src/components/ui/layout/Section.tsx +7 -8
- package/templates/mern-react-native/mobile/src/components/ui/navigation/Pagination.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/navigation/ProgressStepper.tsx +6 -5
- package/templates/mern-react-native/mobile/src/components/ui/navigation/Tabs.tsx +30 -4
- package/templates/mern-react-native/mobile/src/config/api.ts +12 -1
- package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +72 -0
- package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +37 -7
- package/templates/mern-react-native/mobile/src/db/index.ts +9 -0
- package/templates/mern-react-native/mobile/src/db/schema.ts +14 -0
- package/templates/mern-react-native/mobile/src/hooks/useDebounce.ts +17 -0
- package/templates/mern-react-native/mobile/src/services/socket.service.ts +36 -0
- package/templates/mern-react-native/mobile/src/store/useAuthStore.ts +122 -27
- package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +53 -0
- package/templates/mern-react-native/mobile/src/types/auth.type.ts +14 -0
- package/templates/mern-react-native/mobile/tsconfig.json +1 -0
- package/templates/mern-ts/client/src/vite-env.d.ts +1 -0
- package/templates/react-native/babel.config.js +1 -0
- package/templates/react-native/drizzle.config.ts +8 -0
- package/templates/react-native/expo-env.d.ts +3 -0
- package/templates/react-native/package.json +6 -0
- package/templates/react-native/src/app/(auth)/_layout.tsx +12 -0
- package/templates/react-native/src/app/(auth)/change-password.tsx +112 -0
- package/templates/react-native/src/app/(auth)/edit-profile.tsx +160 -0
- package/templates/react-native/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/react-native/src/app/{login.tsx → (auth)/login.tsx} +69 -21
- package/templates/react-native/src/app/(tabs)/index.tsx +18 -17
- package/templates/react-native/src/app/(tabs)/profile.tsx +23 -60
- package/templates/react-native/src/app/_layout.tsx +22 -4
- package/templates/react-native/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/react-native/src/components/common/LocationLoader.tsx +591 -0
- package/templates/react-native/src/components/common/ScreenHeader.tsx +41 -17
- package/templates/react-native/src/components/common/TabHeader.tsx +58 -0
- package/templates/react-native/src/components/common/Typography.tsx +49 -30
- package/templates/react-native/src/components/common/Welcome.tsx +2 -2
- package/templates/react-native/src/components/ui/BottomModal.tsx +31 -20
- package/templates/react-native/src/components/ui/ClassicTabBar.tsx +4 -3
- package/templates/react-native/src/components/ui/CustomTabBar.tsx +108 -71
- package/templates/react-native/src/components/ui/Sidebar.tsx +81 -56
- package/templates/react-native/src/components/ui/buttons/Button.tsx +106 -37
- package/templates/react-native/src/components/ui/buttons/IconButton.tsx +4 -2
- package/templates/react-native/src/components/ui/data-display/Accordion.tsx +3 -2
- package/templates/react-native/src/components/ui/data-display/Avatar.tsx +4 -3
- package/templates/react-native/src/components/ui/data-display/Badge.tsx +45 -17
- package/templates/react-native/src/components/ui/data-display/ImageViewer.tsx +19 -9
- package/templates/react-native/src/components/ui/feedback/CircularProgress.tsx +5 -4
- package/templates/react-native/src/components/ui/feedback/ProgressBar.tsx +4 -3
- package/templates/react-native/src/components/ui/feedback/Skeleton.tsx +3 -0
- package/templates/react-native/src/components/ui/feedback/Spinner.tsx +11 -3
- package/templates/react-native/src/components/ui/inputs/Checkbox.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/react-native/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/react-native/src/components/ui/inputs/Input.tsx +54 -14
- package/templates/react-native/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/react-native/src/components/ui/inputs/OTPInput.tsx +32 -9
- package/templates/react-native/src/components/ui/inputs/PasswordInput.tsx +2 -1
- package/templates/react-native/src/components/ui/inputs/Radio.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/SearchBar.tsx +41 -4
- package/templates/react-native/src/components/ui/inputs/Slider.tsx +4 -3
- package/templates/react-native/src/components/ui/inputs/Switch.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/TextArea.tsx +2 -1
- package/templates/react-native/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/react-native/src/components/ui/layout/Grid.tsx +52 -10
- package/templates/react-native/src/components/ui/layout/Section.tsx +7 -8
- package/templates/react-native/src/components/ui/navigation/Pagination.tsx +4 -3
- package/templates/react-native/src/components/ui/navigation/ProgressStepper.tsx +6 -5
- package/templates/react-native/src/components/ui/navigation/Tabs.tsx +30 -4
- package/templates/react-native/src/config/api.ts +12 -1
- package/templates/react-native/src/config/sidebar.config.ts +72 -0
- package/templates/react-native/src/config/tabNavigation.ts +37 -7
- package/templates/react-native/src/db/index.ts +9 -0
- package/templates/react-native/src/db/schema.ts +14 -0
- package/templates/react-native/src/hooks/useDebounce.ts +17 -0
- package/templates/react-native/src/services/socket.service.ts +36 -0
- package/templates/react-native/src/store/useAuthStore.ts +122 -27
- package/templates/react-native/src/store/useNotificationStore.ts +53 -0
- package/templates/react-native/src/types/auth.type.ts +14 -0
- package/templates/react-native/tsconfig.json +1 -0
- package/templates/react-ts/src/vite-env.d.ts +1 -0
- package/templates/mern-react-native/mobile/.env.example +0 -2
- package/templates/mern-react-native/mobile/.vscode/extensions.json +0 -1
- package/templates/mern-react-native/mobile/.vscode/settings.json +0 -7
- package/templates/mern-react-native/mobile/README.md +0 -159
- package/templates/mern-react-native/mobile/docs/README.md +0 -17
- package/templates/mern-react-native/mobile/docs/components/buttons.md +0 -40
- package/templates/mern-react-native/mobile/docs/components/data-display.md +0 -44
- package/templates/mern-react-native/mobile/docs/components/feedback.md +0 -36
- package/templates/mern-react-native/mobile/docs/components/gestures.md +0 -60
- package/templates/mern-react-native/mobile/docs/components/inputs.md +0 -41
- package/templates/mern-react-native/mobile/docs/components/layout.md +0 -34
- package/templates/mern-react-native/mobile/docs/components/navigation.md +0 -31
- package/templates/mern-react-native/mobile/docs/features.md +0 -85
- package/templates/mern-react-native/mobile/docs/getting-started.md +0 -47
- package/templates/mern-react-native/mobile/src/components/common/HomeHeader.tsx +0 -43
- package/templates/mern-react-native/mobile/src/components/settings/SettingToggle.tsx +0 -26
- package/templates/mern-react-native/mobile/src/components/settings/SettingsOptions.tsx +0 -47
- package/templates/react-native/.env.example +0 -2
- package/templates/react-native/.vscode/extensions.json +0 -1
- package/templates/react-native/.vscode/settings.json +0 -7
- package/templates/react-native/README.md +0 -159
- package/templates/react-native/docs/README.md +0 -17
- package/templates/react-native/docs/components/buttons.md +0 -40
- package/templates/react-native/docs/components/data-display.md +0 -44
- package/templates/react-native/docs/components/feedback.md +0 -36
- package/templates/react-native/docs/components/gestures.md +0 -60
- package/templates/react-native/docs/components/inputs.md +0 -41
- package/templates/react-native/docs/components/layout.md +0 -34
- package/templates/react-native/docs/components/navigation.md +0 -31
- package/templates/react-native/docs/features.md +0 -85
- package/templates/react-native/docs/getting-started.md +0 -47
- package/templates/react-native/src/components/common/HomeHeader.tsx +0 -43
- package/templates/react-native/src/components/settings/SettingToggle.tsx +0 -26
- package/templates/react-native/src/components/settings/SettingsOptions.tsx +0 -47
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { View
|
|
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
|
-
<
|
|
32
|
-
{progress}%
|
|
33
|
-
</
|
|
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
|
|
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
|
-
<
|
|
37
|
-
<
|
|
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:
|
|
39
|
-
<EvilIcons name="spinner" size={
|
|
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
|
|
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 && <
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
36
|
-
|
|
45
|
+
? theme.primary
|
|
46
|
+
: theme.base300;
|
|
37
47
|
|
|
38
48
|
return (
|
|
39
|
-
<View className="mb-4">
|
|
40
|
-
{label && (
|
|
41
|
-
<
|
|
42
|
-
{
|
|
43
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
154
|
+
</Txt>
|
|
115
155
|
)}
|
|
116
156
|
</View>
|
|
117
157
|
);
|