Files
physical-expo/components/simple-calendar.tsx

195 lines
5.1 KiB
TypeScript

import { ThemedText } from "@/components/themed-text";
import { Colors } from "@/constants/theme";
import { useTheme } from "@/context/ThemeContext";
import React, { useMemo, useState } from "react";
import { Modal, Pressable, StyleSheet, View } from "react-native";
interface CalendarModalProps {
visible: boolean;
onClose: () => void;
selectedDate: Date;
onSelectDate: (date: Date) => void;
}
export function CalendarModal({
visible,
onClose,
selectedDate,
onSelectDate,
}: CalendarModalProps) {
const { theme } = useTheme();
const isDark = theme === "dark";
const bg = isDark ? "#1C1C1E" : "#FFFFFF";
const [currentMonth, setCurrentMonth] = useState(new Date(selectedDate));
const daysInMonth = useMemo(() => {
const year = currentMonth.getFullYear();
const month = currentMonth.getMonth();
const date = new Date(year, month, 1);
const days = [];
while (date.getMonth() === month) {
days.push(new Date(date));
date.setDate(date.getDate() + 1);
}
return days;
}, [currentMonth]);
const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// Add empty slots for start of month
const startDay = daysInMonth[0]?.getDay() || 0;
const blanks = Array(startDay).fill(null);
const handleDayPress = (date: Date) => {
onSelectDate(date);
onClose();
};
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={onClose}
>
<Pressable style={styles.overlay} onPress={onClose}>
<View style={[styles.calendarContainer, { backgroundColor: bg }]}>
{/* Header */}
<View style={styles.header}>
<Pressable
onPress={() =>
setCurrentMonth(
new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() - 1,
1
)
)
}
>
<ThemedText style={styles.navText}>{"<"}</ThemedText>
</Pressable>
<ThemedText type="subtitle">
{currentMonth.toLocaleString("default", {
month: "long",
year: "numeric",
})}
</ThemedText>
<Pressable
onPress={() =>
setCurrentMonth(
new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() + 1,
1
)
)
}
>
<ThemedText style={styles.navText}>{">"}</ThemedText>
</Pressable>
</View>
{/* Week Headers */}
<View style={styles.weekRow}>
{weekDays.map((day) => (
<ThemedText key={day} style={styles.weekDayText}>
{day}
</ThemedText>
))}
</View>
{/* Days Grid */}
<View style={styles.daysGrid}>
{blanks.map((_, index) => (
<View key={`blank-${index}`} style={styles.dayCell} />
))}
{daysInMonth.map((date) => {
const isSelected =
date.getDate() === selectedDate.getDate() &&
date.getMonth() === selectedDate.getMonth() &&
date.getFullYear() === selectedDate.getFullYear();
return (
<Pressable
key={date.toISOString()}
style={[
styles.dayCell,
isSelected && {
backgroundColor: Colors[theme].tint,
borderRadius: 20,
},
]}
onPress={() => handleDayPress(date)}
>
<ThemedText
style={[
isSelected && { color: "#fff", fontWeight: "bold" },
]}
>
{date.getDate()}
</ThemedText>
</Pressable>
);
})}
</View>
<Pressable style={styles.closeBtn} onPress={onClose}>
<ThemedText>Close</ThemedText>
</Pressable>
</View>
</Pressable>
</Modal>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "center",
padding: 20,
},
calendarContainer: {
borderRadius: 16,
padding: 20,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
},
navText: {
fontSize: 20,
padding: 10,
},
weekRow: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 10,
},
weekDayText: {
opacity: 0.5,
width: 30,
textAlign: "center",
fontSize: 12,
},
daysGrid: {
flexDirection: "row",
flexWrap: "wrap",
},
dayCell: {
width: "14.28%", // 100% / 7
aspectRatio: 1,
justifyContent: "center",
alignItems: "center",
marginBottom: 5,
},
closeBtn: {
alignItems: "center",
marginTop: 20,
padding: 10,
},
});