import React, { forwardRef, useId, useMemo } from 'react'; import classNames from 'classnames'; import { type DateValue } from 'react-aria'; import { Button, DatePicker, type DatePickerProps, FieldError, Group, Provider, Label } from 'react-aria-components'; import { type CalendarDate } from '@internationalized/date'; import { type DateInputValue, type DatePickerBaseProps } from './types'; import { I18nWrapper } from './i18n-wrapper.component'; import { dateToInternationalizedDate, internationalizedDateToDate } from './utils'; import { OpenmrsIntlLocaleContext, useDatepickerContext } from './hooks'; import { DEFAULT_MIN_DATE_FLOOR } from './defaults'; import { CalendarPopover } from './calendar-popover.component'; import { DatePickerInput } from './date-picker-input.component'; import { DatePickerIcon } from './date-picker-icon.component'; import { DateSegment } from './date-segment.component'; import styles from './datepicker.module.scss'; /** * Properties for the OpenmrsDatePicker */ export interface OpenmrsDatePickerProps extends Omit, 'className' | 'onChange' | 'defaultValue' | 'value'>, DatePickerBaseProps { /** The default value (uncontrolled) */ defaultValue?: DateInputValue; /** Handler that is called when the value changes. */ onChange?: (value: Date | null | undefined) => void; /** Handler that is called when the value changes. Note that this provides types from @internationalized/date. */ onChangeRaw?: (value: DateValue | null) => void; /** Specifies the size of the input. Currently supports either `sm`, `md`, or `lg` as an option */ size?: 'sm' | 'md' | 'lg'; /** 'true' to use the short version. */ short?: boolean; /** The value (controlled) */ value?: DateInputValue; } const defaultProps: OpenmrsDatePickerProps = { short: false, size: 'md', }; /** * A date picker component to select a single date. Based on React Aria, but styled to look like Carbon. */ export const OpenmrsDatePicker = /*#__PURE__*/ forwardRef( function OpenmrsDatePicker(props, ref) { const { className, defaultValue: rawDefaultValue, isDisabled, invalid, invalidText, isInvalid: isInvalidRaw, label, labelText, light, maxDate: rawMaxDate, minDate: rawMinDate, onChange: rawOnChange, onChangeRaw, short, size, value: rawValue, ...datePickerProps } = { ...defaultProps, ...props }; 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 && !datePickerProps['aria-label'] && !datePickerProps['aria-labelledby']) { console.warn( 'OpenmrsDatePicker: You must provide either a visible label (labelText/label) or an aria-label for accessibility.', ); } } const defaultValue = useMemo( () => dateToInternationalizedDate(rawDefaultValue, calendar), [rawDefaultValue, calendar], ); const value = useMemo(() => dateToInternationalizedDate(rawValue, calendar, true), [rawValue, calendar]); const maxDate = useMemo(() => dateToInternationalizedDate(rawMaxDate, calendar), [rawMaxDate, calendar]); const minDate = useMemo( () => dateToInternationalizedDate(rawMinDate ?? DEFAULT_MIN_DATE_FLOOR, calendar), [rawMinDate, calendar], ); const isInvalid = useMemo(() => invalid || isInvalidRaw, [invalid, isInvalidRaw]); const onChange = useMemo(() => { if (onChangeRaw && rawOnChange) { console.error( 'An OpenmrsDatePicker component was created with both onChange and onChangeRaw handlers defined. Only onChangeRaw will be used.', ); } return onChangeRaw ?? ((value: DateValue) => rawOnChange?.(internationalizedDateToDate(value))); }, [onChangeRaw, rawOnChange]); return (
{hasVisibleLabel && ( )} {(segment) => { return segment.type !== 'era' ? ( ) : ( ); }} {invalidText}
); }, );