import { useRef, useState, useEffect } from 'react' import { usePopoverState, Popover } from 'reakit' import { MONTH_NAMES } from '~/utils/date' import Flatpickr from 'react-flatpickr' import { DateTimePickerState, isEmptyValue } from './datePickerUtils' export default function CalendarPopover({ popover, onChange, state, minDate, maxDate, }: { popover: ReturnType onChange: (date: Date) => void state: DateTimePickerState | null minDate?: Date | null maxDate?: Date | null }) { const fp = useRef(null) const containerRef = useRef(null) const value = state?.jsDate // nav state must be tracked independently because programmatic Flatpickr changes don't trigger re-renders. // this state helps sync the custom nav with the picker. const [navState, setNavState] = useState<{ month: number; year: number }>({ month: new Date().getMonth(), year: new Date().getFullYear(), }) // sync navigation as the outer state changes useEffect(() => { if (!state) return let month = new Date().getMonth() let year = new Date().getFullYear() if (state.jsDate) { year = state.jsDate.getFullYear() month = state.jsDate.getMonth() fp.current?.flatpickr.setDate(state.jsDate) } else if ( isEmptyValue(state.year) && isEmptyValue(state.month) && isEmptyValue(state.day) ) { fp.current?.flatpickr.clear() } else { if ( state.year !== null && !isEmptyValue(state.year) && String(state.year).length === 4 ) { year = state.year } if ( state.month !== null && !isEmptyValue(state.month) && state.month <= 12 ) { // `month` from state is 1-based; convert to 0-based month = state.month - 1 } } setNavState({ month, year }) }, [state]) // sync flatpickr as the inner nav state changes useEffect(() => { fp.current?.flatpickr.changeMonth(navState.month, false) fp.current?.flatpickr.changeYear(navState.year) }, [navState]) const onMonthNav = (state: CalendarNavState) => { setNavState({ month: state.month, year: state.year }) } const onPickerChange = (dates: Date[]) => { const date = dates[0] if (!date) return onChange(date) } return (
{fp.current && ( )} { return ( // flatpickr requires an input but we're managing our own text input ) }} />
) } interface CalendarNavState { year: number month: number } function CalendarHeader({ fp, state, onChange, }: { fp: Flatpickr state: { month?: number; year?: number } onChange: (state: CalendarNavState) => void }) { const onNavClick = (type: 'month' | 'year', direction: 'prev' | 'next') => { let m = fp.flatpickr.currentMonth let y = fp.flatpickr.currentYear const change = direction === 'prev' ? -1 : 1 if (type === 'month') { if (m === 0 && direction === 'prev') { m = 11 y = y - 1 } else if (m === 11 && direction === 'next') { m = 0 y = y + 1 } else { m = m + change } } else if (type === 'year') { y = y + change } const { minDate, maxDate } = fp.flatpickr.config if (minDate || maxDate) { const newDateStart = new Date(y, m, 1) const newDateEnd = new Date(y, m + 1, -1) console.log(newDateEnd) if (minDate && newDateEnd.valueOf() < minDate.valueOf()) { y = minDate.getFullYear() m = minDate.getMonth() } else if (maxDate && newDateStart.valueOf() > maxDate.valueOf()) { y = maxDate.getFullYear() m = maxDate.getMonth() } } fp.flatpickr.changeYear(y) fp.flatpickr.changeMonth(m, false) onChange({ year: y, month: m }) } const onReset = () => { const year = new Date().getFullYear() const month = new Date().getMonth() fp.flatpickr.changeYear(year) fp.flatpickr.changeMonth(month, false) onChange({ year, month }) } const now = new Date() return (
} /> } /> } /> } />
) } function CalendarNavButton(props: any) { const onClick = () => { props.onClick(props.type, props.direction) } return (