Skip to content

Commit

Permalink
Fix DatePicker Time Input (#2845)
Browse files Browse the repository at this point in the history
* fix(date-picker): set `isCalendarHeaderExpanded` to `false` when DatePicker is closed

* fix(date-picker): calendar header controlled state on DatePicker

* chore(date-picker): update test

* chore(date-picker): remove unnecessary `async` in test

* Update packages/components/date-picker/__tests__/date-picker.test.tsx

---------

Co-authored-by: WK Wong <wingkwong.code@gmail.com>
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
  • Loading branch information
3 people committed May 24, 2024
1 parent 20ba819 commit 6bbd234
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-crews-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/date-picker": patch
---

Fix calendar header controlled state on DatePicker.
118 changes: 118 additions & 0 deletions packages/components/date-picker/__tests__/date-picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,124 @@ describe("DatePicker", () => {
});
});


describe("Month and Year Picker", () => {
const onHeaderExpandedChangeSpy = jest.fn();

afterEach(() => {
onHeaderExpandedChangeSpy.mockClear();
});

it("should show the month and year picker (uncontrolled)", () => {
const {getByRole} = render(
<DatePicker
showMonthAndYearPickers
calendarProps={{
onHeaderExpandedChange: onHeaderExpandedChangeSpy,
}}
defaultValue={new CalendarDate(2024, 4, 26)}
label="Date"
/>,
);

const button = getByRole("button");

triggerPress(button);

const dialog = getByRole("dialog");
const header = document.querySelector<HTMLButtonElement>(`button[data-slot="header"]`)!;

expect(dialog).toBeVisible();
expect(onHeaderExpandedChangeSpy).not.toHaveBeenCalled();

triggerPress(header);

const month = getByRole("button", {name: "April"});
const year = getByRole("button", {name: "2024"});

expect(month).toHaveAttribute("data-value", "4");
expect(year).toHaveAttribute("data-value", "2024");
expect(onHeaderExpandedChangeSpy).toHaveBeenCalledTimes(1);
expect(onHeaderExpandedChangeSpy).toHaveBeenCalledWith(true);

triggerPress(button);

expect(dialog).not.toBeInTheDocument();
expect(onHeaderExpandedChangeSpy).toHaveBeenCalledTimes(2);
expect(onHeaderExpandedChangeSpy).toHaveBeenCalledWith(false);
});

it("should show the month and year picker (controlled)", () => {
const {getByRole} = render(
<DatePicker
showMonthAndYearPickers
calendarProps={{
isHeaderExpanded: true,
onHeaderExpandedChange: onHeaderExpandedChangeSpy,
}}
defaultValue={new CalendarDate(2024, 4, 26)}
label="Date"
/>,
);

const button = getByRole("button");

triggerPress(button);

const dialog = getByRole("dialog");
const month = getByRole("button", {name: "April"});
const year = getByRole("button", {name: "2024"});

expect(dialog).toBeVisible();
expect(month).toHaveAttribute("data-value", "4");
expect(year).toHaveAttribute("data-value", "2024");
expect(onHeaderExpandedChangeSpy).not.toHaveBeenCalled();

triggerPress(button);

expect(dialog).not.toBeInTheDocument();
expect(onHeaderExpandedChangeSpy).not.toHaveBeenCalled();
});

it("CalendarBottomContent should render correctly", () => {
const {getByRole, getByTestId} = render(
<DatePicker
showMonthAndYearPickers
CalendarBottomContent={<div data-testid="calendar-bottom-content" />}
label="Date"
/>,
);

const button = getByRole("button");

triggerPress(button);

let dialog = getByRole("dialog");
let calendarBottomContent = getByTestId("calendar-bottom-content");
const header = document.querySelector<HTMLButtonElement>(`button[data-slot="header"]`)!;

expect(dialog).toBeVisible();
expect(calendarBottomContent).toBeVisible();

triggerPress(header);

expect(dialog).toBeVisible();
expect(calendarBottomContent).not.toBeInTheDocument();

triggerPress(button); // close date picker

expect(dialog).not.toBeInTheDocument();
expect(calendarBottomContent).not.toBeInTheDocument();

triggerPress(button);

dialog = getByRole("dialog");
calendarBottomContent = getByTestId("calendar-bottom-content");

expect(dialog).toBeVisible();
expect(calendarBottomContent).toBeVisible();
});
});
it("should close listbox by clicking another datepicker", async () => {
const {getByRole, getAllByRole} = render(
<>
Expand Down
33 changes: 29 additions & 4 deletions packages/components/date-picker/src/use-date-picker-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import type {ValueBase} from "@react-types/shared";

import {dataAttr} from "@nextui-org/shared-utils";
import {dateInput, DatePickerVariantProps} from "@nextui-org/theme";
import {useState} from "react";
import {useCallback} from "react";
import {HTMLNextUIProps, mapPropsVariants, useProviderContext} from "@nextui-org/system";
import {mergeProps} from "@react-aria/utils";
import {useDOMRef} from "@nextui-org/react-utils";
import {useLocalizedStringFormatter} from "@react-aria/i18n";
import {useControlledState} from "@react-stately/utils";

import intlMessages from "../intl/messages";

Expand Down Expand Up @@ -116,8 +117,6 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic

const [props, variantProps] = mapPropsVariants(originalProps, dateInput.variantKeys);

const [isCalendarHeaderExpanded, setIsCalendarHeaderExpanded] = useState(false);

const {
as,
ref,
Expand Down Expand Up @@ -146,6 +145,24 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
createCalendar,
} = props;

const {
isHeaderExpanded,
isHeaderDefaultExpanded,
onHeaderExpandedChange,
...restUserCalendarProps
} = userCalendarProps;

const handleHeaderExpandedChange = useCallback(
(isExpanded: boolean | undefined) => {
onHeaderExpandedChange?.(isExpanded || false);
},
[onHeaderExpandedChange],
);

const [isCalendarHeaderExpanded, setIsCalendarHeaderExpanded] = useControlledState<
boolean | undefined
>(isHeaderExpanded, isHeaderDefaultExpanded ?? false, handleHeaderExpandedChange);

const domRef = useDOMRef(ref);
const disableAnimation =
originalProps.disableAnimation ?? globalContext?.disableAnimation ?? false;
Expand Down Expand Up @@ -194,11 +211,12 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
pageBehavior,
isDateUnavailable,
showMonthAndYearPickers,
isHeaderExpanded: isCalendarHeaderExpanded,
onHeaderExpandedChange: setIsCalendarHeaderExpanded,
color: isDefaultColor ? "primary" : originalProps.color,
disableAnimation,
},
userCalendarProps,
restUserCalendarProps,
),
};

Expand Down Expand Up @@ -249,6 +267,12 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
"data-slot": "selector-icon",
};

const onClose = () => {
if (isHeaderExpanded === undefined) {
setIsCalendarHeaderExpanded(false);
}
};

return {
domRef,
endContent,
Expand All @@ -272,6 +296,7 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
userTimeInputProps,
selectorButtonProps,
selectorIconProps,
onClose,
};
}

Expand Down
6 changes: 6 additions & 0 deletions packages/components/date-picker/src/use-date-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,18 @@ export function useDatePicker<T extends DateValue>({
userTimeInputProps,
selectorButtonProps,
selectorIconProps,
onClose,
} = useDatePickerBase({...originalProps, validationBehavior});

let state: DatePickerState = useDatePickerState({
...originalProps,
validationBehavior,
shouldCloseOnSelect: () => !state.hasTime,
onOpenChange: (isOpen) => {
if (!isOpen) {
onClose();
}
},
});

const baseStyles = clsx(classNames?.base, className);
Expand Down

0 comments on commit 6bbd234

Please sign in to comment.