import { View } from 'react-native' import Swiper from './Swiper' import Month from './Month' import { areDatesOnSameDay, dateToUnix, DisableWeekDaysType, getEndOfDay, getInitialIndex, } from './dateUtils' import CalendarHeader from './CalendarHeader' import { memo, useCallback, useState } from 'react' import YearPicker from './YearPicker' import { useTheme } from 'react-native-paper' import { useLatest } from '../shared/utils' import { sharedStyles } from '../shared/styles' import { defaultStartYear, defaultEndYear } from './dateUtils' export type ModeType = 'single' | 'range' | 'multiple' export type ScrollModeType = 'horizontal' | 'vertical' export type ValidRangeType = { startDate?: Date endDate?: Date disabledDates?: Date[] } export type BaseCalendarProps = { locale: string disableWeekDays?: DisableWeekDaysType validRange?: ValidRangeType startYear?: number endYear?: number startWeekOnMonday?: boolean // here they are optional but in final implementation they are required date?: CalendarDate dates?: CalendarDates startDate?: CalendarDate endDate?: CalendarDate dateMode?: 'start' | 'end' } export type CalendarDate = Date | undefined export type CalendarDates = Date[] | undefined | null export type RangeChange = (params: { startDate: CalendarDate endDate: CalendarDate }) => any export type SingleChange = (params: { date: CalendarDate }) => void export type MultiChange = (params: { dates: CalendarDates datePressed: Date change: 'added' | 'removed' }) => any export type MultiConfirm = (params: { dates: Date[] }) => void export interface CalendarSingleProps extends BaseCalendarProps { mode: 'single' date: CalendarDate onChange: SingleChange } export interface CalendarRangeProps extends BaseCalendarProps { mode: 'range' startDate: CalendarDate endDate: CalendarDate onChange: RangeChange } export interface CalendarMultiProps extends BaseCalendarProps { mode: 'multiple' dates: CalendarDates onChange: MultiChange } function Calendar( props: CalendarSingleProps | CalendarRangeProps | CalendarMultiProps ) { const { locale, mode, onChange, startDate, endDate, date, disableWeekDays, startYear, endYear, dates, validRange, dateMode, startWeekOnMonday, } = props const scrollMode = mode === 'range' || mode === 'multiple' ? 'vertical' : 'horizontal' const firstDate = startDate || date || dates?.[0] const theme = useTheme() const [selectedYear, setSelectedYear] = useState( undefined ) const [selectingYear, setSelectingYear] = useState(false) // prevent re-rendering all months when something changed we only need the // latest version of the props and we don't want the useCallback to change const startDateRef = useLatest(startDate) const endDateRef = useLatest(endDate) const onChangeRef = useLatest( onChange ) const datesRef = useLatest(dates) const onPressYear = useCallback( (year: number) => { setSelectedYear(year) setSelectingYear((prev) => !prev) }, [setSelectingYear] ) const onPressDate = useCallback( (d: Date) => { if (mode === 'single') { ;(onChangeRef.current as SingleChange)({ date: dateMode === 'start' ? d : getEndOfDay(d), }) } else if (mode === 'range') { const sd = startDateRef.current const ed = endDateRef.current let isStart: boolean = true if (sd && !ed && dateToUnix(d) >= dateToUnix(sd!)) { isStart = false } ;(onChangeRef.current as RangeChange)({ startDate: isStart ? d : sd, endDate: !isStart ? getEndOfDay(d) : undefined, }) } else if (mode === 'multiple') { datesRef.current = datesRef.current || [] const exists = datesRef.current.some((ed) => areDatesOnSameDay(ed, d)) const newDates = exists ? datesRef.current.filter((ed) => !areDatesOnSameDay(ed, d)) : [...datesRef.current, d] newDates.sort((a, b) => a.getTime() - b.getTime()) ;(onChangeRef.current as MultiChange)({ dates: newDates, datePressed: d, change: exists ? 'removed' : 'added', }) } }, [mode, dateMode, onChangeRef, startDateRef, endDateRef, datesRef] ) const selectColor = theme.colors.primaryContainer return ( ( )} renderHeader={({ onPrev, onNext }) => ( )} /> {scrollMode === 'horizontal' ? ( ) : null} ) } export default memo(Calendar)