mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
Fix DatePicker Time Input (#2845)
* 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>
This commit is contained in:
parent
20ba81948d
commit
6bbd234aa2
5
.changeset/pretty-crews-build.md
Normal file
5
.changeset/pretty-crews-build.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@nextui-org/date-picker": patch
|
||||
---
|
||||
|
||||
Fix calendar header controlled state on DatePicker.
|
||||
@ -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(
|
||||
<>
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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,
|
||||
@ -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;
|
||||
@ -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,
|
||||
),
|
||||
};
|
||||
|
||||
@ -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,
|
||||
@ -272,6 +296,7 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
|
||||
userTimeInputProps,
|
||||
selectorButtonProps,
|
||||
selectorIconProps,
|
||||
onClose,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user