Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: verify date input only on blur on calendar input, close calenda… #1574

Merged
merged 1 commit into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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(
<CalendarInput calendar="gregory" onDateSelect={onDateSelectMock} />
)

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(
<CalendarInput calendar="gregory" onDateSelect={onDateSelectMock} />
)

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,
})
)
})
})
28 changes: 24 additions & 4 deletions components/calendar/src/calendar-input/calendar-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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(
() => ({
Expand All @@ -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 = () => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -147,7 +163,11 @@ export const CalendarInput = ({
modifiers={[offsetModifier]}
>
<Card>
<CalendarContainer {...calendarProps} />
<CalendarContainer
{...calendarProps}
excludedRef={excludeRef}
unfocusable
/>
</Card>
</Popper>
</Layer>
Expand Down
26 changes: 18 additions & 8 deletions components/calendar/src/calendar/calendar-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const CalendarContainer = ({
prevMonth,
prevYear,
languageDirection,
excludedRef,
unfocusable,
}) => {
const navigationProps = useMemo(() => {
return {
Expand Down Expand Up @@ -50,14 +52,20 @@ export const CalendarContainer = ({
dir={languageDirection}
data-test="calendar"
>
<NavigationContainer {...navigationProps} />
<CalendarTable
selectedDate={date}
calendarWeekDays={calendarWeekDays}
weekDayLabels={weekDayLabels}
cellSize={cellSize}
width={width}
/>
<div ref={excludedRef}>
<NavigationContainer
{...navigationProps}
unfocusable={unfocusable}
/>
<CalendarTable
selectedDate={date}
calendarWeekDays={calendarWeekDays}
weekDayLabels={weekDayLabels}
cellSize={cellSize}
width={width}
unfocusable={unfocusable}
/>
</div>
</div>
<style jsx>{`
.calendar-wrapper {
Expand All @@ -82,11 +90,13 @@ export const CalendarContainer = ({
CalendarContainer.defaultProps = {
cellSize: '32px',
width: '240px',
unfocusable: false,
}

CalendarContainer.propTypes = {
/** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */
date: PropTypes.string,
unfocusable: PropTypes.bool,
...CalendarTableProps,
...NavigationContainerProps,
}
9 changes: 8 additions & 1 deletion components/calendar/src/calendar/calendar-table-cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import cx from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'

export const CalendarTableCell = ({ day, cellSize, selectedDate }) => {
export const CalendarTableCell = ({
day,
cellSize,
selectedDate,
unfocusable,
}) => {
const dayHoverBackgroundColor = colors.grey200
const selectedDayBackgroundColor = colors.teal700

Expand All @@ -16,6 +21,7 @@ export const CalendarTableCell = ({ day, cellSize, selectedDate }) => {
isToday: day.isToday,
otherMonth: !day.isInCurrentMonth,
})}
tabIndex={unfocusable ? -1 : 0}
>
{day.label}
</button>
Expand Down Expand Up @@ -94,4 +100,5 @@ CalendarTableCell.propTypes = {
onClick: PropTypes.func,
}),
selectedDate: PropTypes.string,
unfocusable: PropTypes.bool,
}
3 changes: 3 additions & 0 deletions components/calendar/src/calendar/calendar-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const CalendarTable = ({
width,
cellSize,
selectedDate,
unfocusable,
}) => (
<div className="calendar-table-wrapper">
<table className="calendar-table">
Expand All @@ -24,6 +25,7 @@ export const CalendarTable = ({
key={day?.calendarDate}
cellSize={cellSize}
width={width}
unfocusable={unfocusable}
/>
))}
</tr>
Expand Down Expand Up @@ -67,6 +69,7 @@ export const CalendarTableProps = {
).isRequired,
cellSize: PropTypes.string,
selectedDate: PropTypes.string,
unfocusable: PropTypes.bool,
weekDayLabels: PropTypes.arrayOf(PropTypes.string),
width: PropTypes.string,
}
Expand Down
6 changes: 6 additions & 0 deletions components/calendar/src/calendar/navigation-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const NavigationContainer = ({
nextYear,
prevMonth,
prevYear,
unfocusable,
}) => {
const PreviousIcon =
languageDirection === 'ltr' ? IconChevronLeft16 : IconChevronRight16
Expand All @@ -36,6 +37,7 @@ export const NavigationContainer = ({
data-test="calendar-previous-month"
aria-label={`${i18n.t(`Go to ${prevMonth.label}`)}`}
type="button"
tabIndex={unfocusable ? -1 : 0}
>
<PreviousIcon />
</button>
Expand All @@ -50,6 +52,7 @@ export const NavigationContainer = ({
name="next-month"
aria-label={`${i18n.t(`Go to ${nextMonth.label}`)}`}
type="button"
tabIndex={unfocusable ? -1 : 0}
>
<NextIcon />
</button>
Expand All @@ -62,6 +65,7 @@ export const NavigationContainer = ({
name="previous-year"
aria-label={`${i18n.t('Go to previous year')}`}
type="button"
tabIndex={unfocusable ? -1 : 0}
>
<PreviousIcon />
</button>
Expand All @@ -80,6 +84,7 @@ export const NavigationContainer = ({
name="next-year"
aria-label={`${i18n.t('Go to next year')}`}
type="button"
tabIndex={unfocusable ? -1 : 0}
>
<NextIcon />
</button>
Expand Down Expand Up @@ -184,6 +189,7 @@ export const NavigationContainerProps = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
navigateTo: PropTypes.func,
}),
unfocusable: PropTypes.bool,
}

NavigationContainer.propTypes = NavigationContainerProps
Loading