import React from "react" import moment, { Moment } from "moment" import { DateTime, Namespace, TempusDominus } from "@eonasdan/tempus-dominus" import { applyTempusDominusLocale } from "./DateTimePickerComponent" /** Props for DateRangePickerComponent. */ export interface DateRangePickerComponentProps { /** Current range value. */ value: [Moment | null, Moment | null] | null /** Called when the range changes. */ onChange: (value: [Moment | null, Moment | null]) => void /** True to use datetime values instead of date values. */ datetime?: boolean /** Locale code. */ locale?: string /** Label above the start calendar. Defaults to "Start". */ startLabel?: string /** Label above the end calendar. Defaults to "End". */ endLabel?: string } /** Shared inline date range picker built from two side-by-side single-date Tempus Dominus calendars. */ export default class DateRangePickerComponent extends React.Component { private startControl: TempusDominus | null = null private endControl: TempusDominus | null = null private syncingFromProps = false componentDidUpdate(prevProps: DateRangePickerComponentProps) { if (prevProps.locale !== this.props.locale) { if (this.startControl) applyTempusDominusLocale(this.startControl, this.props.locale) if (this.endControl) applyTempusDominusLocale(this.endControl, this.props.locale) } const prevStart = prevProps.value?.[0] ?? null const prevEnd = prevProps.value?.[1] ?? null const nextStart = this.props.value?.[0] ?? null const nextEnd = this.props.value?.[1] ?? null if (!this.momentValuesEqual(prevStart, nextStart) && this.startControl) { this.syncControlFromProps(this.startControl, nextStart) } if (!this.momentValuesEqual(prevEnd, nextEnd) && this.endControl) { this.syncControlFromProps(this.endControl, nextEnd) } } private momentValuesEqual(left: Moment | null, right: Moment | null) { if (!left && !right) { return true } if (!left || !right) { return false } return left.isSame(right) } private buildControl(container: HTMLDivElement): TempusDominus { return new TempusDominus(container, { useCurrent: false, display: { inline: true, keepOpen: true, buttons: { clear: true, today: true }, components: { calendar: true, date: true, month: true, year: true, decades: true, clock: false, hours: false, minutes: false, seconds: false }, icons: { time: "fas fa-clock", date: "fas fa-calendar", up: "fas fa-arrow-up", down: "fas fa-arrow-down", next: "fas fa-arrow-right", previous: "fas fa-arrow-left", today: "fas fa-calendar-check", clear: "fas fa-trash", close: "fas fa-times" }, theme: "light" }, localization: { hourCycle: "h23" } }) } private startContainerRef = (container: HTMLDivElement | null) => { if (container) { this.startControl = this.buildControl(container) applyTempusDominusLocale(this.startControl, this.props.locale) this.syncControlFromProps(this.startControl, this.props.value?.[0] ?? null) this.startControl.subscribe(Namespace.events.change, this.handleChange) } else if (this.startControl) { this.startControl.dispose() this.startControl = null } } private endContainerRef = (container: HTMLDivElement | null) => { if (container) { this.endControl = this.buildControl(container) applyTempusDominusLocale(this.endControl, this.props.locale) this.syncControlFromProps(this.endControl, this.props.value?.[1] ?? null) this.endControl.subscribe(Namespace.events.change, this.handleChange) } else if (this.endControl) { this.endControl.dispose() this.endControl = null } } private syncControlFromProps(control: TempusDominus, value: Moment | null) { this.syncingFromProps = true // Inline pickers have no input; TempusDominus.clear() assumes one exists. control.dates.clear() if (value) { control.dates.setValue(DateTime.convert(value.toDate()), 0) } this.syncingFromProps = false } private handleChange = () => { if (this.syncingFromProps) { return } const startPicked = this.startControl?.dates.picked[0] ?? null const endPicked = this.endControl?.dates.picked[0] ?? null let start: Moment | null = startPicked ? moment(startPicked) : null let end: Moment | null = endPicked ? moment(endPicked) : null if (start && this.props.datetime) { start = moment(start).startOf("day") } if (end && this.props.datetime) { end = moment(end).endOf("day") } if (start && end && end.isBefore(start)) { [start, end] = [end, start] } this.props.onChange([start, end]) } render() { return (
{this.props.startLabel ?? "Start"}
{this.props.endLabel ?? "End"}
) } }