import React, { forwardRef, useId, useMemo } from 'react'; import classNames from 'classnames'; import { Button, DateInput, DateRangePicker, type DateRangePickerProps, FieldError, Label, Provider, type DateRange, Group, } from 'react-aria-components'; import { type CalendarDate } from '@internationalized/date'; import { DateSegment } from './date-segment.component'; import styles from './datepicker.module.scss'; import { OpenmrsIntlLocaleContext, useDatepickerContext } from './hooks'; import { type DateInputValue, type DatePickerBaseProps } from './types'; import { I18nWrapper } from './i18n-wrapper.component'; import { DatePickerIcon } from './date-picker-icon.component'; import { CalendarPopover } from './calendar-popover.component'; import { dateToInternationalizedDate, internationalizedDateToDate } from './utils'; import { DEFAULT_MIN_DATE_FLOOR } from './defaults'; /** Properties for the OpenmrsDateRangePicker */ export interface OpenmrsDateRangePickerProps extends Omit, 'className' | 'onChange' | 'defaultValue' | 'value'>, DatePickerBaseProps { /** The default value (uncontrolled) */ defaultValue?: [DateInputValue, DateInputValue]; /** Handler that is called when the value changes. */ onChange?: (value: [Date | null | undefined, Date | null | undefined]) => void; /** Handler that is called when the value changes. Note that this provides types from @internationalized/date. */ onChangeRaw?: (value: DateRange | null) => void; /** The value (controlled) */ value?: [DateInputValue, DateInputValue]; } /** * A date range picker to enter or select a date and time range. Based on React Aria, but styled to look like Carbon. */ export const OpenmrsDateRangePicker = /*#__PURE__*/ forwardRef( function OpenmrsDateRangePicker( { className, defaultValue: rawDefaultValue, invalid, invalidText, isDisabled, isInvalid: isInvalidRaw, label, labelText, light, maxDate: rawMaxDate, minDate: rawMinDate, onChange, onChangeRaw, value: rawValue, ...dateRangePickerProps }, ref, ) { const { calendar, intlLocale, today_ } = useDatepickerContext(); const id = useId(); const hasVisibleLabel = !!(labelText ?? label); // Warn in development if no accessible label is provided if (process.env.NODE_ENV !== 'production') { if (!hasVisibleLabel && !dateRangePickerProps['aria-label'] && !dateRangePickerProps['aria-labelledby']) { console.warn( 'OpenmrsDateRangePicker: You must provide either a visible label (labelText/label) or an aria-label for accessibility.', ); } } const value = useMemo(() => { if (rawValue) { return { start: dateToInternationalizedDate(rawValue[0], calendar, false)!, end: dateToInternationalizedDate(rawValue[1], calendar, false)!, }; } return undefined; }, [rawValue, calendar]); const defaultValue = useMemo(() => { if (rawDefaultValue) { return { start: dateToInternationalizedDate(rawDefaultValue[0], calendar, false)!, end: dateToInternationalizedDate(rawDefaultValue[1], calendar, false)!, }; } return undefined; }, [rawDefaultValue, calendar]); const minDate = useMemo( () => dateToInternationalizedDate(rawMinDate ?? DEFAULT_MIN_DATE_FLOOR, calendar, true), [rawMinDate, calendar], ); const maxDate = useMemo(() => dateToInternationalizedDate(rawMaxDate, calendar, true), [rawMaxDate, calendar]); const isInvalid = useMemo(() => invalid || isInvalidRaw, [invalid, isInvalidRaw]); const innerOnChange = useMemo(() => { if (onChangeRaw && onChange) { console.error( 'An OpenmrsDateRangePicker component was created with both onChange and onChangeRaw handlers defined. Only onChangeRaw will be used.', ); } return ( onChangeRaw ?? ((range: DateRange) => onChange?.([internationalizedDateToDate(range.start), internationalizedDateToDate(range.end)])) ); }, [onChangeRaw, onChange]); return (
{hasVisibleLabel && ( )}
{(segment) => }
{(segment) => }
{invalidText}
); }, );