import React from "react"; import { View, Text, StyleSheet } from "react-native"; import type { CalendarDay as CalendarDayType } from "../types/calendar"; import { CalendarDay } from "./calendar-day"; import { COLORS, type ColorPalette } from "../styles/colors"; const DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; export interface CalendarGridProps { /** Calendar days */ days: CalendarDayType[]; /** Currently selected date */ selectedDate: Date | null; /** Callback when date is selected */ onDateSelect: (date: Date) => void; /** Check if date is today */ isToday: (date: Date) => boolean; /** Check if date is selected */ isDateSelected: (date: Date) => boolean; /** Custom container style */ style?: any; /** Custom day style */ dayStyle?: any; /** Custom day text style */ dayTextStyle?: any; /** Show day labels (Sun, Mon, etc.) */ showDayLabels?: boolean; /** Custom day label style */ /** Custom day label style */ dayLabelStyle?: any; /** Override weekday labels */ dayLabels?: string[]; /** Show/hide event markers inside days */ showEventIndicators?: boolean; /** Optional color overrides */ colors?: Partial; /** Dates with events (timestamps) */ markedDates?: number[]; /** Minimum selectable date (inclusive) */ minDate?: number | string | Date; /** Maximum selectable date (inclusive) */ maxDate?: number | string | Date; /** Exact dates to disable */ disabledDates?: Array; /** Ranges of dates to disable */ disabledDateRanges?: Array<{ start: number | string | Date; end: number | string | Date; }>; /** Weekdays to disable (0 = Sunday..6 = Saturday) */ disabledDaysOfWeek?: number[]; /** Custom predicate — runs last and overrides other checks */ isDateDisabled?: (date: Date) => boolean; /** Override month names (unused by grid but forwarded) */ monthNames?: string[]; } /** * Calendar grid with day labels and day cells * Fully customizable via props */ export function CalendarGrid({ days, onDateSelect, isToday, isDateSelected, style, dayStyle, dayTextStyle, showDayLabels = true, dayLabelStyle, markedDates = [], dayLabels, showEventIndicators = true, colors, minDate, maxDate, disabledDates = [], disabledDateRanges = [], disabledDaysOfWeek = [], isDateDisabled, }: CalendarGridProps) { const hasEvent = (timestamp: number) => { return markedDates.some((date) => { const d1 = new Date(timestamp); const d2 = new Date(date); return ( d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate() ); }); }; const toMidnight = (d: number | string | Date) => { const date = new Date(d); date.setHours(0, 0, 0, 0); return date.getTime(); }; const disabledDatesTs = disabledDates.map(toMidnight); const disabledRangesTs = disabledDateRanges.map((r) => ({ start: toMidnight(r.start), end: toMidnight(r.end), })); const minTs = minDate ? toMidnight(minDate) : undefined; const maxTs = maxDate ? toMidnight(maxDate) : undefined; const isDisabled = (timestamp: number) => { const ts = new Date(timestamp).setHours(0, 0, 0, 0); const date = new Date(ts); if (typeof isDateDisabled === "function") { if (isDateDisabled(date)) return true; } if (typeof minTs === "number" && ts < minTs) return true; if (typeof maxTs === "number" && ts > maxTs) return true; if (disabledDatesTs.includes(ts)) return true; for (const r of disabledRangesTs) { if (ts >= r.start && ts <= r.end) return true; } if (disabledDaysOfWeek.length > 0) { const dow = date.getDay(); if (disabledDaysOfWeek.includes(dow)) return true; } return false; }; const palette = colors ? ({ ...COLORS, ...colors } as ColorPalette) : COLORS; return ( {showDayLabels && ( {(dayLabels ?? DAY_LABELS).map((label, i) => ( {label} ))} )} {days.map((day, index) => ( ))} ); } const styles = StyleSheet.create({ container: { backgroundColor: COLORS.background, }, dayLabels: { flexDirection: "row", paddingHorizontal: 12, paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: COLORS.border, }, dayLabel: { width: "14.28%", textAlign: "center", fontSize: 12, fontWeight: "600", color: COLORS.textSecondary, }, grid: { flexDirection: "row", flexWrap: "wrap", padding: 12, }, });