Skip to content

Commit

Permalink
🐛(react) fix DatePicker dropdowns
Browse files Browse the repository at this point in the history
When having a start date, using the year or month dropdown was
causing the calendar to abruptly close.

Fixes #244
  • Loading branch information
NathanVss committed Feb 13, 2024
1 parent b0eff28 commit ebfccc1
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/angry-penguins-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@openfun/cunningham-react": patch
---

fix DatePicker dropdowns closing
46 changes: 43 additions & 3 deletions packages/react/src/components/Forms/DatePicker/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { forwardRef, Ref, useMemo, useRef } from "react";
import React, { forwardRef, Ref, useMemo, useRef, useState } from "react";
import {
CalendarDate,
createCalendar,
Expand Down Expand Up @@ -114,6 +114,7 @@ const CalendarAux = forwardRef(
const monthItemsFormatter = useTimeZoneFormatter({ month: "long" });
const selectedMonthItemFormatter = useTimeZoneFormatter({ month: "short" });
const yearItemsFormatter = useTimeZoneFormatter({ year: "numeric" });
const [showGrid, setShowGrid] = useState(true);

const monthItems: Array<Option> = useMemo(() => {
// Note that in some calendar systems, such as the Hebrew, the number of months may differ between years.
Expand Down Expand Up @@ -167,7 +168,30 @@ const CalendarAux = forwardRef(
const updatedFocusedDate = state.focusedDate.set({
[key]: e?.selectedItem?.value,
});
state.setFocusedDate(updatedFocusedDate);

/**
* We need to hide the grid before updated the focused date because if the mouse hovers a cell it will
* automatically internally call the focusCell method which sets the focused date to the hovered cell date.
*
* (Current year = 2024) The steps are:
* 1 - Select year 2050 in the dropdown.
* 2 - Hide the dropdown
* 3 - state.setFocusedDate(2050)
* 3 - Mouse hovers a cell in the grid before the state takes into account the new focused date ( 2050 ).
* 4 - focusCell is called with the current year (2024) overriding the previous call with year=2050
*
* The resulting bug will be the year 2024 being selected in the grid instead of 2050.
*
* So instead why first hide the grid, wait for the state to be updated, set the focused date to 2050, and
* then show the grid again. This way we will prevent the mouse from hovering a cell before the state is updated.
*/
setShowGrid(false);
setTimeout(() => {
state.setFocusedDate(updatedFocusedDate);
setTimeout(() => {
setShowGrid(true);
});
});
},
});
};
Expand Down Expand Up @@ -214,6 +238,22 @@ const CalendarAux = forwardRef(
<div
ref={ref}
{...calendarProps}
// We need to remove the id from the calendar when the dropdowns are open to avoid having the following bug:
// 1 - Open the calendar
// 2 - Select a start date
// 3 - Click on the dropdown to select another year
// 4 - BUG: The calendar closes abruptly.
//
// This way caused by this internal call from Spectrum: https://github.com/adobe/react-spectrum/blob/cdb2fe21213d9e8b03d782d82bda07742ab3afbd/packages/%40react-aria/calendar/src/useRangeCalendar.ts#L59
//
// So instead we decided to remove the id of the calendar when the dropdowns are open and add it back when
// the dropdowns are closed in order to make this condition fail: https://github.com/adobe/react-spectrum/blob/cdb2fe21213d9e8b03d782d82bda07742ab3afbd/packages/%40react-aria/calendar/src/useRangeCalendar.ts#L55
// This way the `body` variable will never be found.
id={
!downshiftMonth.isOpen && !downshiftYear.isOpen
? calendarProps.id
: ""
}
className={classNames("c__calendar__wrapper", {
"c__calendar__wrapper--opened":
!downshiftMonth.isOpen && !downshiftYear.isOpen,
Expand Down Expand Up @@ -308,7 +348,7 @@ const CalendarAux = forwardRef(
/>
</div>
</div>
{!downshiftMonth.isOpen && !downshiftYear.isOpen && (
{!downshiftMonth.isOpen && !downshiftYear.isOpen && showGrid && (
<CalendarGrid state={state} />
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,60 @@ describe("<DateRangePicker/>", () => {
expectDatesToBeEqual(endDate, endInput.textContent);
});

it("picks a date range spanning multiple years using dropdown", async () => {
const user = userEvent.setup();
render(
<CunninghamProvider>
<DateRangePicker
startLabel="Start date"
endLabel="End date"
name="datepicker"
/>
</CunninghamProvider>,
);
const [input] = await screen.findAllByRole("button");
await user.click(input);
expectCalendarToBeOpen();

// Select the start date.
const yearButton = screen.getByRole("combobox", {
name: "Select a year",
});
await user.click(yearButton);
await user.click(screen.getByRole("option", { name: "1910" }));

const monthButton = screen.getByRole("combobox", {
name: "Select a month",
});
await user.click(monthButton);
await user.click(screen.getByRole("option", { name: "January" }));

await user.click(
screen.getByRole("button", {
name: "Saturday, January 1, 1910",
}),
);

// Select the end date.
await user.click(yearButton);
await user.click(screen.getByRole("option", { name: "2040" }));
await user.click(monthButton);
await user.click(screen.getByRole("option", { name: "September" }));
await user.click(
screen.getByRole("button", {
name: "Sunday, September 2, 2040",
}),
);

// Make sure the correct dates are set.
expectCalendarToBeClosed();
const startDate = "Saturday, January 1, 1910";
const endDate = "Sunday, September 2, 2040";
const [startInput, endInput] = await screen.queryAllByRole("presentation");
expectDatesToBeEqual(startDate, startInput.textContent);
expectDatesToBeEqual(endDate, endInput.textContent);
});

it("types a date range", async () => {
const user = userEvent.setup();
render(
Expand Down

0 comments on commit ebfccc1

Please sign in to comment.