Skip to content

Commit

Permalink
fix(date-picker): calendar header controlled state on DatePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
chirokas committed Apr 26, 2024
1 parent dbc8658 commit e7cf05d
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 5 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.
81 changes: 81 additions & 0 deletions packages/components/date-picker/__tests__/date-picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,85 @@ describe("DatePicker", () => {
expect(getTextValue(combobox)).toBe("2/4/2019"); // uncontrolled
});
});

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

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

it("should show the month and year picker (uncontrolled)", async () => {
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");

expect(dialog).toBeVisible();

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

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)", async () => {
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();
});
});
});
30 changes: 25 additions & 5 deletions packages/components/date-picker/src/use-date-picker-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import type {ReactNode} from "react";
import type {ValueBase} from "@react-types/shared";

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

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

Expand Down Expand Up @@ -114,8 +115,6 @@ export type UseDatePickerBaseProps<T extends DateValue> = Props<T> &
export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePickerBaseProps<T>) {
const [props, variantProps] = mapPropsVariants(originalProps, dateInput.variantKeys);

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

const {
as,
ref,
Expand Down Expand Up @@ -144,6 +143,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 ?? false;

Expand Down Expand Up @@ -191,6 +208,7 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
pageBehavior,
isDateUnavailable,
showMonthAndYearPickers,
isHeaderExpanded: isCalendarHeaderExpanded,
onHeaderExpandedChange: setIsCalendarHeaderExpanded,
color:
(originalProps.variant === "bordered" || originalProps.variant === "underlined") &&
Expand All @@ -201,7 +219,7 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
: originalProps.color,
disableAnimation,
},
userCalendarProps,
restUserCalendarProps,
),
};

Expand Down Expand Up @@ -251,7 +269,9 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
};

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

return {
Expand Down

0 comments on commit e7cf05d

Please sign in to comment.