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;