UNPKG

@spaced-out/ui-design-system

Version:
215 lines (192 loc) 5.88 kB
// @flow strict import * as React from 'react'; import {isEmpty} from 'lodash'; // $FlowFixMe[untyped-import] import moment from 'moment-timezone'; import type { DateRange, DateRangePickerError, DateRangeWithTimezone, } from '../../types'; import { getAddedDate, getMonthEndDate, getSubtractedDate, getValidDates, MARKERS, NAVIGATION_ACTION, } from '../../utils'; import classify from '../../utils/classify'; import { getTodayInTimezone, isAfter, isBefore, isBetween, isSame, isSameOrAfter, isSameOrBefore, } from '../../utils/date-range-picker'; import {DateRangeWrapper} from './DateRangeWrapper'; import css from './DateRangePicker.module.css'; type ClassNames = $ReadOnly<{wrapper?: string}>; export type DateRangePickerProps = { classNames?: ClassNames, selectedDateRange?: DateRangeWithTimezone, onApply: (datePickerSelectedRange: DateRangeWithTimezone) => void, onError?: (DateRangePickerError) => void, onCancel: () => void, hideTimezone?: boolean, minDate?: string, maxDate?: string, startDateLabel?: string, endDateLabel?: string, t?: ?(key: string, fallback: string) => string, locale?: string, }; export const DateRangePicker: React$AbstractComponent< DateRangePickerProps, HTMLDivElement, > = React.forwardRef<DateRangePickerProps, HTMLDivElement>( ( { minDate, maxDate, onApply, onError, onCancel, classNames, hideTimezone = false, selectedDateRange = {}, startDateLabel, endDateLabel, t, locale, }: DateRangePickerProps, ref, ): React.Node => { const localTimezone = moment.tz.guess(); const {timezone: inputTimezone = ''} = selectedDateRange; const validTimezone = isEmpty(inputTimezone) ? localTimezone : inputTimezone; const [today, setToday] = React.useState(() => getTodayInTimezone(validTimezone), ); const {validMinDate, validMaxDate, validDateRange} = React.useMemo( () => getValidDates({ selectedDateRange, minDate, maxDate, today, onError, t, }), [today], ); const {startDate, endDate} = validDateRange; const validRangeEndMonth = endDate ? getMonthEndDate(endDate) : getMonthEndDate(today); const validRangeStartMonth = startDate && endDate && !isSame(startDate, endDate, 'month') ? getMonthEndDate(startDate) : getMonthEndDate(getSubtractedDate(validRangeEndMonth, 1, 'M')); const [dateRange, setDateRange] = React.useState<DateRange>(validDateRange); const [timezone, setTimezone] = React.useState<string>(validTimezone); const [hoverDay, setHoverDay] = React.useState<string>(''); const [rangeStartMonth, setRangeStartMonth] = React.useState<string>(validRangeStartMonth); const [rangeEndMonth, setRangeEndMonth] = React.useState<string>(validRangeEndMonth); const setRangeStartMonthValidated = (date: string) => { if (isSameOrAfter(date, validMinDate) && isBefore(date, rangeEndMonth)) { setRangeStartMonth(date); } else { setRangeStartMonth(getMonthEndDate(validMinDate)); } }; const setRangeEndMonthValidated = (date: string) => { if ( isSameOrBefore(date, validMaxDate) && isAfter(date, rangeStartMonth) ) { setRangeEndMonth(date); } else { setRangeEndMonth(getMonthEndDate(validMaxDate)); } }; const onMonthNavigate = ( marker: $Values<typeof MARKERS>, action: $Values<typeof NAVIGATION_ACTION>, ) => { if (marker === MARKERS.DATE_RANGE_START) { const newMonth = action === NAVIGATION_ACTION.NEXT ? getMonthEndDate(getAddedDate(rangeStartMonth, 1, 'M')) : getMonthEndDate(getSubtractedDate(rangeStartMonth, 1, 'M')); setRangeStartMonthValidated(newMonth); } else { const newMonth = action === NAVIGATION_ACTION.NEXT ? getMonthEndDate(getAddedDate(rangeEndMonth, 1, 'M')) : getMonthEndDate(getSubtractedDate(rangeEndMonth, 1, 'M')); setRangeEndMonthValidated(newMonth); } }; const onDayClick = (day: string) => { const {startDate, endDate} = dateRange; if (startDate && !endDate && isSameOrAfter(day, startDate)) { setDateRange({startDate, endDate: day}); } else { setDateRange({startDate: day}); } setHoverDay(day); }; const inHoverRange = (day: string): boolean => { const {startDate, endDate} = dateRange; return Boolean( startDate && !endDate && hoverDay && isAfter(hoverDay, startDate) && isBetween(day, startDate, hoverDay), ); }; const handlers = { onDayClick, onDayHover: setHoverDay, onMonthNavigate, }; const handleTimeZone = (newTimezone) => { setToday(getTodayInTimezone(newTimezone)); setTimezone(newTimezone); }; return ( <DateRangeWrapper ref={ref} minDate={validMinDate} maxDate={validMaxDate} onApply={onApply} handlers={handlers} hoverDay={hoverDay} onCancel={onCancel} timezone={timezone} dateRange={dateRange} setTimezone={handleTimeZone} rangeStartMonth={rangeStartMonth} rangeEndMonth={rangeEndMonth} inHoverRange={inHoverRange} setRangeStartMonth={setRangeStartMonthValidated} setRangeEndMonth={setRangeEndMonthValidated} hideTimezone={hideTimezone} cardWrapperClass={classify(css.container, classNames?.wrapper)} today={today} startDateLabel={startDateLabel} endDateLabel={endDateLabel} t={t} locale={locale} /> ); }, );