diff --git a/components/calendar/src/calendar-input/__tests__/calendar-input.test.js b/components/calendar/src/calendar-input/__tests__/calendar-input.test.js new file mode 100644 index 0000000000..75be8ec2fe --- /dev/null +++ b/components/calendar/src/calendar-input/__tests__/calendar-input.test.js @@ -0,0 +1,54 @@ +import { fireEvent, render, waitFor, within } from '@testing-library/react' +import React from 'react' +import { CalendarInput } from '../calendar-input.js' + +describe('Calendar Input', () => { + it('allow selection of a date through the calendar widget', async () => { + const onDateSelectMock = jest.fn() + const screen = render( + + ) + + const dateInput = within( + screen.getByTestId('dhis2-uicore-input') + ).getByRole('textbox') + fireEvent.focus(dateInput) + const calendar = await screen.findByTestId('calendar') + expect(calendar).toBeInTheDocument() + + const todayString = new Date().toISOString().slice(0, -14) + const today = within(calendar).getByTestId(todayString) + + fireEvent.click(today) + + await waitFor(() => { + expect(calendar).not.toBeInTheDocument() + }) + expect(onDateSelectMock).toHaveBeenCalledWith( + expect.objectContaining({ + calendarDateString: todayString, + }) + ) + }) + + it('allow selection of a date through the input', async () => { + const onDateSelectMock = jest.fn() + const screen = render( + + ) + + const dateInputString = '2024/10/12' + const dateInput = within( + screen.getByTestId('dhis2-uicore-input') + ).getByRole('textbox') + + fireEvent.change(dateInput, { target: { value: dateInputString } }) + fireEvent.blur(dateInput) + + expect(onDateSelectMock).toHaveBeenCalledWith( + expect.objectContaining({ + calendarDateString: dateInputString, + }) + ) + }) +}) diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js index c670653b9c..b308e85234 100644 --- a/components/calendar/src/calendar-input/calendar-input.js +++ b/components/calendar/src/calendar-input/calendar-input.js @@ -8,7 +8,7 @@ import { useResolvedDirection, } from '@dhis2/multi-calendar-dates' import cx from 'classnames' -import React, { useRef, useState, useMemo } from 'react' +import React, { useRef, useState, useMemo, useEffect } from 'react' import { CalendarContainer } from '../calendar/calendar-container.js' import { CalendarProps } from '../calendar/calendar.js' import i18n from '../locales/index.js' @@ -40,6 +40,11 @@ export const CalendarInput = ({ } = {}) => { const ref = useRef() const [open, setOpen] = useState(false) + const [partialDate, setPartialDate] = useState(date) + + const excludeRef = useRef(null) + + useEffect(() => setPartialDate(date), [date]) const useDatePickerOptions = useMemo( () => ({ @@ -66,7 +71,17 @@ export const CalendarInput = ({ }) const handleChange = (e) => { - parentOnDateSelect?.({ calendarDateString: e.value }) + setPartialDate(e.value) + } + + const handleBlur = (_, e) => { + parentOnDateSelect?.({ calendarDateString: partialDate }) + if ( + excludeRef.current && + !excludeRef.current.contains(e.relatedTarget) + ) { + setOpen(false) + } } const onFocus = () => { @@ -101,8 +116,9 @@ export const CalendarInput = ({ {...rest} type="text" onFocus={onFocus} - value={date} + value={partialDate} onChange={handleChange} + onBlur={handleBlur} validationText={ pickerResults.errorMessage || pickerResults.warningMessage @@ -147,7 +163,11 @@ export const CalendarInput = ({ modifiers={[offsetModifier]} > - + diff --git a/components/calendar/src/calendar/calendar-container.js b/components/calendar/src/calendar/calendar-container.js index 4d8fb004b2..5bea2e0e93 100644 --- a/components/calendar/src/calendar/calendar-container.js +++ b/components/calendar/src/calendar/calendar-container.js @@ -23,6 +23,8 @@ export const CalendarContainer = ({ prevMonth, prevYear, languageDirection, + excludedRef, + unfocusable, }) => { const navigationProps = useMemo(() => { return { @@ -50,14 +52,20 @@ export const CalendarContainer = ({ dir={languageDirection} data-test="calendar" > - - +
+ + +