UNPKG

react-native-calendar-picker

Version:

Calendar Picker Component for React Native

278 lines (255 loc) 8.6 kB
import React, { Component } from 'react'; import { View } from 'react-native'; import PropTypes from 'prop-types'; import { stylePropType } from './localPropTypes'; import Day from './Day'; import EmptyDay from './EmptyDay'; import { Utils } from './Utils'; import { getISODay } from 'date-fns/getISODay'; export default class DaysGridView extends Component { constructor(props) { super(props); this.initMonthSettings = props => { const { month, year, showDayStragglers, firstDay = 0, } = props; // Retrieve total days in this month & year, accounting for leap years. const numDaysInMonth = Utils.getDaysInMonth(month, year); // Calculate days in prev month for day stragglers. let prevMonth, prevMonthYear; let numDaysInPrevMonth; if (showDayStragglers) { prevMonth = month - 1; prevMonthYear = year; if (prevMonth < 0) { prevMonth = 11; prevMonthYear--; } numDaysInPrevMonth = Utils.getDaysInMonth(prevMonth, prevMonthYear); } // Create a date for day one of the current given month and year const firstDayOfMonth = new Date(year, month, 1); // Determine which day of the week day 1 falls on. // See https://github.com/stephy/CalendarPicker/issues/49 // isoWeekday() gets the ISO day of the week with 1=Monday and 7=Sunday. const firstWeekDay = getISODay(firstDayOfMonth); // Determine starting index based on first day of week prop. const startIndex = ((firstDay > 0) ? (firstWeekDay + Utils.FIRST_DAY_OFFSETS[firstDay]) : firstWeekDay) % 7; return { maxWeekRows: 6, numDaysInWeek: 7, numDaysInMonth, numDaysInPrevMonth, firstDayOfMonth, firstWeekDay, startIndex, }; }; const monthSettings = this.initMonthSettings(props); this.state = { monthSettings, daysGrid: this.generateDaysGrid(monthSettings), }; } componentDidUpdate(prevProps) { // Optimize re-renders by checking props, with special handling for selected dates. // Shallow compare prop changes, excluding selected dates. let excludedProps = [] // Avoid unecessary re-render when selecting date range if (this.props.selectedEndDate) { excludedProps = ['selectedStartDate', 'selectedEndDate'] } const propDiffs = Utils.shallowDiff(this.props, prevProps, excludedProps); if (propDiffs.length) { // Recreate days const monthSettings = this.initMonthSettings(this.props); this.setState({ monthSettings, daysGrid: this.generateDaysGrid(monthSettings), }); } else { // Update daysGrid entries when selected date(s) affect this month. const { selectedStartDate, selectedEndDate } = this.props; const { selectedStartDate: prevSelStart, selectedEndDate: prevSelEnd } = prevProps; const { firstDayOfMonth } = this.state.monthSettings; const isSelectedDiff = !Utils.compareDates(selectedStartDate, prevSelStart, 'day') || !Utils.compareDates(selectedEndDate, prevSelEnd, 'day'); // Check that selected date(s) match this month. if (isSelectedDiff && ( Utils.compareDates(selectedStartDate, firstDayOfMonth, 'month') || Utils.compareDates(selectedEndDate, firstDayOfMonth, 'month') || Utils.compareDates(prevSelStart, firstDayOfMonth, 'month') || Utils.compareDates(prevSelEnd, firstDayOfMonth, 'month'))) { // Range selection potentially affects all dates in the month. Recreate. if (this.props.allowRangeSelection) { this.setState({ daysGrid: this.generateDaysGrid(this.state.monthSettings), }); } else { // Search for affected dates and modify those only const daysGrid = [...this.state.daysGrid]; const { year } = this.props; for (let i = 0; i < daysGrid.length; i++) { for (let j = 0; j < daysGrid[i].length; j++) { const { month, day } = daysGrid[i][j]; // Empty days and stragglers can't be selected. if (month === undefined) { continue; } // Check single date const thisDay = { year, month, day }; const isSelected = Utils.compareDates(selectedStartDate, thisDay, 'day'); const isPrevSelected = Utils.compareDates(prevSelStart, thisDay, 'day'); if (isSelected || isPrevSelected) { daysGrid[i][j] = this.renderDayInCurrentMonth(day); } } } this.setState({ daysGrid }); } } } } renderDayInCurrentMonth(day) { return ({ day, month: this.props.month, component: ( <Day key={day} day={day} {...this.props} /> ), }); } renderEmptyDay(key) { return ({ component: ( <EmptyDay key={'empty' + key} styles={this.props.styles} /> ), }); } renderDayStraggler({ key, day }) { return ({ day, // month doesn't matter for stragglers as long as isn't set to current month component: ( <Day key={key} day={day} styles={this.props.styles} disabledDates={() => true} disabledDatesTextStyle={this.props.disabledDatesTextStyle} textStyle={this.props.textStyle} /> ) }); } // Create grid of days. generateDaysGrid = params => { const { numDaysInWeek, maxWeekRows, startIndex, numDaysInMonth, numDaysInPrevMonth } = params; let daysGrid = [[]]; let dayOfMonth = 1; let dayNextMonth = 1; let lastFilledRow = 0; // Week rows for (let i = 0; i < maxWeekRows; i++) { daysGrid[i] = []; // Days in week for (let j = 0; j < numDaysInWeek; j++) { if (i === 0) { // first row: start current month's day on the correct weekday if (j >= startIndex) { if (dayOfMonth <= numDaysInMonth) { daysGrid[i].push(this.renderDayInCurrentMonth(dayOfMonth++)); } } else { const key = '' + i + j; daysGrid[i].push(this.props.showDayStragglers ? // Show previous month's days this.renderDayStraggler({ key, day: numDaysInPrevMonth - startIndex + j + 1, }) : //... otherwise blank this.renderEmptyDay(key) ); } } else { if (dayOfMonth <= numDaysInMonth) { lastFilledRow = i; daysGrid[i].push(this.renderDayInCurrentMonth(dayOfMonth++)); } else { if (this.props.showDayStragglers && i <= lastFilledRow) { // Show next month's days daysGrid[i].push(this.renderDayStraggler({ key: '' + i + j, day: dayNextMonth++, })); } } } } } return daysGrid; } render() { const { styles } = this.props; const { daysGrid } = this.state; const renderedDaysGrid = daysGrid.map((weekRow, i) => ( <View key={i} style={styles.weekRow}> {weekRow.map(day => day.component)} </View> )); return ( <View style={styles.daysWrapper}> {renderedDaysGrid} </View> ); } } DaysGridView.propTypes = { styles: stylePropType, month: PropTypes.number.isRequired, year: PropTypes.number.isRequired, onPressDay: PropTypes.func, firstDay: PropTypes.number, selectedDayStyle: stylePropType, selectedRangeStartStyle: stylePropType, selectedRangeStyle: stylePropType, selectedRangeEndStyle: stylePropType, todayTextStyle: stylePropType, selectedDayTextStyle: stylePropType, customDatesStyles: PropTypes.oneOfType([ PropTypes.func, PropTypes.arrayOf(PropTypes.shape({ date: PropTypes.oneOfType([ PropTypes.string, PropTypes.instanceOf(Date), ]), containerStyle: stylePropType, style: stylePropType, textStyle: stylePropType, })), ]), disabledDates: PropTypes.oneOfType([PropTypes.array, PropTypes.func]), disabledDatesTextStyle: stylePropType, minRangeDuration: PropTypes.oneOfType([PropTypes.array, PropTypes.number]), maxRangeDuration: PropTypes.oneOfType([PropTypes.array, PropTypes.number]), };