import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { DAYS_OF_WEEK } from './constants'; import { eqDate, formatString, formatTitleString, hasVisibleDate, initArray } from './utils'; import { useTheme } from '../ThemeContext'; import Icon from '../Icon'; import noop from '../../helpers/noop'; import React, { ReactElement } from 'react'; type Color = string; type ParsedMarkedDates = { [key: string]: Color[]; }; export type Props = { /** * Testing id of component */ readonly testID?: string; /** * selected date value */ readonly value?: Date; /** * Handler to be called when selected date value is changed. */ readonly onChange?: (d: Date) => void; /** * current month to display on calendar */ readonly currentView?: Date; /** * Handler to be called when the user taps the button Previous. */ readonly onPressPrev?: () => void; /** * Handler to be called when the user taps the button Next. */ readonly onPressNext?: () => void; /** * Handler to be called when the user taps the title. */ readonly onPressTitle?: () => void; /** * marked dates to display on calendar */ readonly markedDates?: { date: Date; colors: string[]; }[]; /** * minimum date. Restrict the range of possible date/time values */ readonly minDate?: Date; /** * maximum date. Restrict the range of possible date/time values */ readonly maxDate?: Date; }; const Calendar = ({ testID = '', value = new Date(), onChange = noop, currentView = new Date(), onPressPrev = noop, onPressNext = noop, onPressTitle = noop, markedDates = [], minDate, maxDate, }: Props): ReactElement => { const theme = useTheme(); const currentYear = currentView.getFullYear(); const currentMonth = currentView.getMonth(); const now = new Date(); const firstDateOfMonth = new Date(currentYear, currentMonth, 1); const lastDateOfMonth = new Date(currentYear, currentMonth + 1, 0); const lastDateOfPreviousMonth = new Date(currentYear, currentMonth, 0.0); const firstDayOfMonth = firstDateOfMonth.getDay(); const lastDayOfMonth = lastDateOfMonth.getDay(); const lastDateNumberOfMonth = lastDateOfMonth.getDate(); const lastDateNumberOfPreviousMonth = lastDateOfPreviousMonth.getDate(); const getValidDay = (date: Date): Date | undefined => { if (minDate === undefined && maxDate === undefined) { return date; } if (minDate !== undefined && maxDate === undefined) { return date >= minDate ? date : undefined; } if (minDate === undefined && maxDate !== undefined) { return date <= maxDate ? date : undefined; } if (minDate !== undefined && maxDate !== undefined) { return date >= minDate && date <= maxDate ? date : undefined; } return undefined; }; const daysOfPreviousMonth = initArray(firstDayOfMonth, index => { const reversedIndex = firstDayOfMonth - index - 1; const count = lastDateNumberOfPreviousMonth - reversedIndex; return getValidDay(new Date(currentYear, currentMonth - 1, count)); }); const daysOfCurrentMonth = initArray(lastDateNumberOfMonth, index => getValidDay(new Date(currentYear, currentMonth, index + 1)), ); const daysOfNextMonth = initArray(6 - lastDayOfMonth, index => getValidDay(new Date(currentYear, currentMonth + 1, index + 1)), ); const parsedMarkedDates: ParsedMarkedDates = markedDates.reduce((current, mark) => { const markedDateString = mark.date.toDateString(); const colors = current[markedDateString]; if (colors !== undefined) { current[markedDateString] = colors.concat(mark.colors); } else { current[markedDateString] = mark.colors; } return current; }, {}); const renderMarks = date => { const isSelected = eqDate(value, date); const colors = parsedMarkedDates[date.toDateString()]; if (colors === undefined) return null; return ( {colors.map(color => ( ))} ); }; const showPrevButton = minDate === undefined ? true : hasVisibleDate(daysOfPreviousMonth) || minDate < firstDateOfMonth; const showNextButton = maxDate === undefined ? true : hasVisibleDate(daysOfNextMonth) || maxDate > lastDateOfMonth; return ( <> {showPrevButton ? ( ) : ( )} {formatTitleString(currentView)} {showNextButton ? ( ) : ( )} {DAYS_OF_WEEK.map(dayName => ( {dayName} ))} {daysOfPreviousMonth.map((date, index) => date ? ( onChange(date)} style={theme.calendar.day} > {renderMarks(date)} {formatString(date.getDate())} ) : ( ), )} {daysOfCurrentMonth.map((date, index) => date ? ( onChange(date)} style={theme.calendar.day} > {renderMarks(date)} {formatString(date.getDate())} ) : ( ), )} {daysOfNextMonth.map((date, index) => date ? ( onChange(date)} style={theme.calendar.day} > {renderMarks(date)} {formatString(date.getDate())} ) : ( ), )} ); }; export default Calendar;