From 945b69cbd857d7f07c9d41a2bd5d4fa85d547622 Mon Sep 17 00:00:00 2001 From: Giampaolo Bellavite Date: Sat, 8 Jun 2024 06:52:21 -0500 Subject: [PATCH 1/6] wip --- src/contexts/calendar.tsx | 14 ++--- src/contexts/props.tsx | 46 ++++++++++---- src/helpers/getDropdownMonths.test.ts | 40 ++++++------ src/helpers/getDropdownMonths.ts | 18 +++--- src/helpers/getFirstLastMonths.ts | 16 ++--- src/helpers/getNextFocus.test.tsx | 22 +++---- src/helpers/getNextFocus.tsx | 11 +++- src/helpers/getPossibleFocusDate.test.ts | 14 ++--- src/helpers/getPossibleFocusDate.ts | 12 ++-- src/helpers/getStartEndMonths.test.ts | 78 ++++++++++++------------ src/helpers/getStartEndMonths.ts | 36 +++++------ src/helpers/getStartMonth.test.ts | 20 +++--- src/helpers/getStartMonth.ts | 22 ++++--- src/types.ts | 55 ++++++++++++++--- website/docs/upgrading.mdx | 18 +++--- 15 files changed, 244 insertions(+), 178 deletions(-) diff --git a/src/contexts/calendar.tsx b/src/contexts/calendar.tsx index 8d94f452cb..e668a0a8ac 100644 --- a/src/contexts/calendar.tsx +++ b/src/contexts/calendar.tsx @@ -112,7 +112,7 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) { const lastMonth = displayMonths[displayMonths.length - 1]; /** An array of the dates displayed in the calendar. */ - const dates = getDates(displayMonths, props.toMonth, props); + const dates = getDates(displayMonths, props.endMonth, props); /** An array of the Months displayed in the calendar. */ const months = getMonths(displayMonths, dates, props); @@ -139,13 +139,13 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) { return; } let newMonth = startOfMonth(date); - // if month is before fromMonth, use the first month instead - if (props.fromMonth && newMonth < startOfMonth(props.fromMonth)) { - newMonth = startOfMonth(props.fromMonth); + // if month is before startMonth, use the first month instead + if (props.startMonth && newMonth < startOfMonth(props.startMonth)) { + newMonth = startOfMonth(props.startMonth); } - // if month is after toMonth, use the last month instead - if (props.toMonth && newMonth > startOfMonth(props.toMonth)) { - newMonth = startOfMonth(props.toMonth); + // if month is after endMonth, use the last month instead + if (props.endMonth && newMonth > startOfMonth(props.endMonth)) { + newMonth = startOfMonth(props.endMonth); } setFirstMonth(newMonth); props.onMonthChange?.(newMonth); diff --git a/src/contexts/props.tsx b/src/contexts/props.tsx index 6c5d453206..7e5d8afdef 100644 --- a/src/contexts/props.tsx +++ b/src/contexts/props.tsx @@ -5,6 +5,8 @@ import React, { useId } from "react"; +import { startOfMonth } from "date-fns"; + import { getDataAttributes } from "../helpers/getDataAttributes"; import { getDefaultClassNames } from "../helpers/getDefaultClassNames"; import { getFormatters } from "../helpers/getFormatters"; @@ -22,6 +24,15 @@ import type { SelectHandler } from "../types"; +/** @private */ +export type DeprecatedProps = + | "fromDate" + | "toDate" + | "fromMonth" + | "toMonth" + | "fromYear" + | "toYear"; + /** * Holds the props passed to the DayPicker component, with some optional props * set to meaningful defaults. @@ -35,12 +46,12 @@ import type { export interface PropsContext< T extends Mode = "default", R extends boolean = false -> extends PropsBase { +> extends Omit { classNames: ClassNames; /** The `data-*` attributes passed to ``. */ dataAttributes: DataAttributes; + endMonth: Date | undefined; formatters: Formatters; - fromMonth: Date | undefined; id: string; labels: Labels; max: number | undefined; @@ -48,7 +59,7 @@ export interface PropsContext< mode: T; numberOfMonths: number; required: boolean; - toMonth: Date | undefined; + startMonth: Date | undefined; today: Date; /** The currently selected value. */ selected: Selected | undefined; @@ -66,29 +77,38 @@ const propsContext = createContext | null>(null); * * @private */ -export const PropsProvider = ( +export function PropsProvider( props: PropsWithChildren> -) => { +) { const reactId = useId(); - const { children, ...restProps } = props; - - const { fromMonth, toMonth } = getStartEndMonths(props); + const { startMonth, endMonth } = getStartEndMonths(props); + const { + children, + // Remove deprecated props + fromDate, + fromMonth, + fromYear, + toDate, + toMonth, + toYear, + ...restProps + } = props; const context = { ...restProps, classNames: { ...getDefaultClassNames(), ...restProps.classNames }, dataAttributes: getDataAttributes(props), + endMonth, formatters: getFormatters(props.formatters), - fromMonth, id: props.id ?? reactId, labels: { ...defaultLabels, ...restProps.labels }, - required: "required" in props ? props.required ?? false : false, - min: "min" in props ? props.min ?? undefined : undefined, max: "max" in props ? props.max ?? undefined : undefined, + min: "min" in props ? props.min ?? undefined : undefined, mode: props.mode ?? ("default" as Mode), numberOfMonths: props.numberOfMonths ?? 1, + required: "required" in props ? props.required ?? false : false, + startMonth, today: props.today ?? new Date(), - toMonth, selected: "selected" in props ? props.selected : undefined, defaultSelected: "defaultSelected" in props ? props.defaultSelected : undefined, @@ -98,7 +118,7 @@ export const PropsProvider = ( return ( {children} ); -}; +} /** * Access to the props passed to DayPicker. diff --git a/src/helpers/getDropdownMonths.test.ts b/src/helpers/getDropdownMonths.test.ts index 43ff50f4b9..aa0a1a1f1e 100644 --- a/src/helpers/getDropdownMonths.test.ts +++ b/src/helpers/getDropdownMonths.test.ts @@ -2,31 +2,31 @@ import * as formatters from "../formatters"; import { getDropdownMonths } from "./getDropdownMonths"; -test("returns undefined if `fromMonth` is not defined", () => { +test("returns undefined if `startMonth` is not defined", () => { const result = getDropdownMonths({ - fromMonth: undefined, - toMonth: new Date(), + startMonth: undefined, + endMonth: new Date(), formatters }); expect(result).toBeUndefined(); }); -test("returns undefined if `toMonth` is not defined", () => { +test("returns undefined if `endMonth` is not defined", () => { const result = getDropdownMonths({ - fromMonth: new Date(), - toMonth: undefined, + startMonth: new Date(), + endMonth: undefined, formatters }); expect(result).toBeUndefined(); }); -test("returns sorted months between `fromMonth` and `toMonth`", () => { - const fromMonth = new Date(2023, 0, 1); - const toMonth = new Date(2023, 11, 31); +test("returns sorted months between `startMonth` and `endMonth`", () => { + const startMonth = new Date(2023, 0, 1); + const endMonth = new Date(2023, 11, 31); const result = getDropdownMonths( - { fromMonth, toMonth, formatters }, - fromMonth.getFullYear() + { startMonth, endMonth, formatters }, + startMonth.getFullYear() ); expect(result).toBeDefined(); @@ -38,11 +38,11 @@ test("returns sorted months between `fromMonth` and `toMonth`", () => { }); test("formats month labels correctly", () => { - const fromMonth = new Date(2023, 3, 1); - const toMonth = new Date(2023, 11, 31); + const startMonth = new Date(2023, 3, 1); + const endMonth = new Date(2023, 11, 31); const result = getDropdownMonths( - { fromMonth, toMonth, formatters }, - fromMonth.getFullYear() + { startMonth, endMonth, formatters }, + startMonth.getFullYear() ); if (!result) throw new Error("Unexpected undefined result"); expect(result[0]).toEqual({ disabled: false, label: "April", value: 3 }); @@ -51,17 +51,17 @@ test("formats month labels correctly", () => { describe("when using a custom formatter", () => { test("formats month labels correctly", () => { - const fromMonth = new Date(2023, 0, 1); - const toMonth = new Date(2023, 11, 31); + const startMonth = new Date(2023, 0, 1); + const endMonth = new Date(2023, 11, 31); const result = getDropdownMonths( { - fromMonth, - toMonth, + startMonth, + endMonth, formatters: { formatMonthDropdown: (month) => `Month ${month.toString()}` } }, - fromMonth.getFullYear() + startMonth.getFullYear() ); if (!result) throw new Error("Unexpected undefined result"); expect(result[0]).toEqual({ disabled: false, label: "Month 0", value: 0 }); diff --git a/src/helpers/getDropdownMonths.ts b/src/helpers/getDropdownMonths.ts index 2e5d072918..e2bb1e0ca5 100644 --- a/src/helpers/getDropdownMonths.ts +++ b/src/helpers/getDropdownMonths.ts @@ -11,16 +11,16 @@ import { Formatters, Mode } from "../types"; export function getDropdownMonths( props: Pick< PropsContext, - "fromMonth" | "toMonth" | "locale" + "startMonth" | "endMonth" | "locale" > & { formatters: Pick; }, year?: number | undefined ): DropdownOption[] | undefined { - if (!props.fromMonth) return undefined; - if (!props.toMonth) return undefined; - const navStartMonth = startOfMonth(props.fromMonth); - const navEndMonth = startOfMonth(props.toMonth); + if (!props.startMonth) return undefined; + if (!props.endMonth) return undefined; + const navStartMonth = startOfMonth(props.startMonth); + const navEndMonth = startOfMonth(props.endMonth); const months: number[] = []; let month = navStartMonth; @@ -38,11 +38,11 @@ export function getDropdownMonths( ); const disabled = (year && - props.fromMonth && - new Date(year, value) < startOfMonth(props.fromMonth)) || + props.startMonth && + new Date(year, value) < startOfMonth(props.startMonth)) || (year && - props.toMonth && - new Date(year, value) > startOfMonth(props.toMonth)) || + props.endMonth && + new Date(year, value) > startOfMonth(props.endMonth)) || false; return { value, label, disabled }; }); diff --git a/src/helpers/getFirstLastMonths.ts b/src/helpers/getFirstLastMonths.ts index 869567641a..c40163867e 100644 --- a/src/helpers/getFirstLastMonths.ts +++ b/src/helpers/getFirstLastMonths.ts @@ -17,8 +17,8 @@ export function getFirstLastMonths( PropsBase, | "fromYear" | "toYear" - | "fromMonth" - | "toMonth" + | "startMonth" + | "endMonth" | "month" | "defaultMonth" | "today" @@ -28,16 +28,16 @@ export function getFirstLastMonths( ): [firstMonth: Date, lastMonth?: Date] { const { month, defaultMonth, today, numberOfMonths = 1 } = props; let initialMonth = month || defaultMonth || today || new Date(); - const { fromMonth, toMonth } = getStartEndMonths(props); + const { startMonth, endMonth } = getStartEndMonths(props); // Fix the initialMonth if is after the to-date - if (toMonth && differenceInCalendarMonths(toMonth, initialMonth) < 0) { + if (endMonth && differenceInCalendarMonths(endMonth, initialMonth) < 0) { const offset = -1 * (numberOfMonths - 1); - initialMonth = addMonths(toMonth, offset); + initialMonth = addMonths(endMonth, offset); } // Fix the initialMonth if is before the from-date - if (fromMonth && differenceInCalendarMonths(initialMonth, fromMonth) < 0) { - initialMonth = fromMonth; + if (startMonth && differenceInCalendarMonths(initialMonth, startMonth) < 0) { + initialMonth = startMonth; } - return [startOfMonth(initialMonth), toMonth]; + return [startOfMonth(initialMonth), endMonth]; } diff --git a/src/helpers/getNextFocus.test.tsx b/src/helpers/getNextFocus.test.tsx index 594364be65..4207d8d226 100644 --- a/src/helpers/getNextFocus.test.tsx +++ b/src/helpers/getNextFocus.test.tsx @@ -7,14 +7,14 @@ import type { Mode } from "../types"; import { getNextFocus } from "./getNextFocus"; -const defaultDayPicker: Pick< +const props: Pick< PropsContext, - "disabled" | "hidden" | "fromMonth" | "toMonth" + "disabled" | "hidden" | "startMonth" | "endMonth" > = { disabled: [], hidden: [], - fromMonth: undefined, - toMonth: undefined + startMonth: undefined, + endMonth: undefined }; it("should return `undefined` if `attempt` exceeds 365", () => { @@ -24,13 +24,7 @@ it("should return `undefined` if `attempt` exceeds 365", () => { ); const moveBy: MoveFocusBy = "day"; const moveDir: MoveFocusDir = "after"; - const result = getNextFocus( - moveBy, - moveDir, - focusedDay, - defaultDayPicker, - 366 - ); + const result = getNextFocus(moveBy, moveDir, focusedDay, props, 366); expect(result).toBeUndefined(); }); @@ -40,7 +34,7 @@ it("should return the focus date if it is not disabled or hidden", () => { new Date(2020, 0, 1) ); const expectedDate = new Date(2020, 0, 2); - const result = getNextFocus("day", "after", focusedDay, defaultDayPicker); + const result = getNextFocus("day", "after", focusedDay, props); expect(result?.date).toEqual(expectedDate); }); @@ -52,7 +46,7 @@ it("should return the next focus date if it is disabled", () => { const disabledDate = new Date(2020, 0, 2); const expectedDate = new Date(2020, 0, 3); const result = getNextFocus("day", "after", focusedDay, { - ...defaultDayPicker, + ...props, disabled: [disabledDate] }); expect(result?.date).toEqual(expectedDate); @@ -66,7 +60,7 @@ it("should return the next focus date if it is hidden", () => { const hiddenDate = new Date(2020, 0, 2); const expectedDate = new Date(2020, 0, 3); const result = getNextFocus("day", "after", focusedDay, { - ...defaultDayPicker, + ...props, hidden: [hiddenDate] }); expect(result?.date).toEqual(expectedDate); diff --git a/src/helpers/getNextFocus.tsx b/src/helpers/getNextFocus.tsx index 12deb89a06..ce98e72f2c 100644 --- a/src/helpers/getNextFocus.tsx +++ b/src/helpers/getNextFocus.tsx @@ -10,7 +10,12 @@ import { getPossibleFocusDate } from "./getPossibleFocusDate"; export type Options = Pick< PropsContext, - "modifiers" | "locale" | "ISOWeek" | "weekStartsOn" | "fromMonth" | "toMonth" + | "modifiers" + | "locale" + | "ISOWeek" + | "weekStartsOn" + | "startMonth" + | "endMonth" >; export function getNextFocus( @@ -26,8 +31,8 @@ export function getNextFocus( | "locale" | "ISOWeek" | "weekStartsOn" - | "fromMonth" - | "toMonth" + | "startMonth" + | "endMonth" >, attempt: number = 0 ): CalendarDay | undefined { diff --git a/src/helpers/getPossibleFocusDate.test.ts b/src/helpers/getPossibleFocusDate.test.ts index 666c98be71..7e03f20607 100644 --- a/src/helpers/getPossibleFocusDate.test.ts +++ b/src/helpers/getPossibleFocusDate.test.ts @@ -20,13 +20,13 @@ import { getPossibleFocusDate } from "./getPossibleFocusDate"; const baseDate = new Date(2023, 0, 1); // Jan 1, 2023 const options: Pick< PropsContext, - "locale" | "ISOWeek" | "weekStartsOn" | "fromMonth" | "toMonth" + "locale" | "ISOWeek" | "weekStartsOn" | "startMonth" | "endMonth" > = { locale: undefined, ISOWeek: false, weekStartsOn: 0, // Sunday - fromMonth: new Date(2022, 0, 1), // Jan 1, 2022 - toMonth: new Date(2024, 0, 1) // Jan 1, 2024 + startMonth: new Date(2022, 0, 1), // Jan 1, 2022 + endMonth: new Date(2024, 0, 1) // Jan 1, 2024 }; const testCases: { @@ -98,22 +98,22 @@ ISOWeekTestCases.forEach(({ moveBy, moveDir, expectedFn }) => { }); }); -test("should not move before fromMonth", () => { +test("should not move before startMonth", () => { const result = getPossibleFocusDate( "day", "before", new Date(2022, 0, 2), options ); - expect(result).toEqual(options.fromMonth); + expect(result).toEqual(options.startMonth); }); -test("should not move after toMonth", () => { +test("should not move after endMonth", () => { const result = getPossibleFocusDate( "day", "after", new Date(2023, 11, 31), options ); - expect(result).toEqual(options.toMonth); + expect(result).toEqual(options.endMonth); }); diff --git a/src/helpers/getPossibleFocusDate.ts b/src/helpers/getPossibleFocusDate.ts index d0e687da32..0a6797635f 100644 --- a/src/helpers/getPossibleFocusDate.ts +++ b/src/helpers/getPossibleFocusDate.ts @@ -20,10 +20,10 @@ export function getPossibleFocusDate( focusedDate: Date, options: Pick< PropsContext, - "locale" | "ISOWeek" | "weekStartsOn" | "fromMonth" | "toMonth" + "locale" | "ISOWeek" | "weekStartsOn" | "startMonth" | "endMonth" > ): Date { - const { weekStartsOn, fromMonth, toMonth, locale, ISOWeek } = options; + const { weekStartsOn, startMonth, endMonth, locale, ISOWeek } = options; const moveFns = { day: addDays, @@ -42,10 +42,10 @@ export function getPossibleFocusDate( focusedDate, moveDir === "after" ? 1 : -1 ); - if (moveDir === "before" && fromMonth) { - nextFocusedDate = max([fromMonth, nextFocusedDate]); - } else if (moveDir === "after" && toMonth) { - nextFocusedDate = min([toMonth, nextFocusedDate]); + if (moveDir === "before" && startMonth) { + nextFocusedDate = max([startMonth, nextFocusedDate]); + } else if (moveDir === "after" && endMonth) { + nextFocusedDate = min([endMonth, nextFocusedDate]); } return nextFocusedDate; } diff --git a/src/helpers/getStartEndMonths.test.ts b/src/helpers/getStartEndMonths.test.ts index 3db2e825be..805d734399 100644 --- a/src/helpers/getStartEndMonths.test.ts +++ b/src/helpers/getStartEndMonths.test.ts @@ -1,89 +1,91 @@ import { getStartEndMonths } from "./getStartEndMonths"; -describe('when "fromMonth" is not passed in', () => { - test('"fromMonth" should be undefined', () => { - const { fromMonth } = getStartEndMonths({}); - expect(fromMonth).toBeUndefined(); +describe('when "startMonth" is not passed in', () => { + test('"startMonth" should be undefined', () => { + const { startMonth } = getStartEndMonths({}); + expect(startMonth).toBeUndefined(); }); }); -describe('when "fromMonth" is passed in', () => { - const { fromMonth } = getStartEndMonths({ fromMonth: new Date(2021, 4, 3) }); - test('"fromMonth" should be the start of that month', () => { - expect(fromMonth).toEqual(new Date(2021, 4, 1)); +describe('when "startMonth" is passed in', () => { + const { startMonth } = getStartEndMonths({ + startMonth: new Date(2021, 4, 3) + }); + test('"startMonth" should be the start of that month', () => { + expect(startMonth).toEqual(new Date(2021, 4, 1)); }); describe('when "fromYear" is passed in', () => { - test('"fromMonth" should be the start of that month', () => { - expect(fromMonth).toEqual(new Date(2021, 4, 1)); + test('"startMonth" should be the start of that month', () => { + expect(startMonth).toEqual(new Date(2021, 4, 1)); }); }); }); describe('when "fromYear" is passed in', () => { - const { fromMonth } = getStartEndMonths({ fromYear: 2021 }); - test('"fromMonth" should be the start of that year', () => { - expect(fromMonth).toEqual(new Date(2021, 0, 1)); + const { startMonth } = getStartEndMonths({ fromYear: 2021 }); + test('"startMonth" should be the start of that year', () => { + expect(startMonth).toEqual(new Date(2021, 0, 1)); }); }); -describe('when "toMonth" is passed in', () => { - const { toMonth } = getStartEndMonths({ toMonth: new Date(2021, 4, 3) }); - test('"toMonth" should be the end of that month', () => { - expect(toMonth).toEqual(new Date(2021, 4, 31)); +describe('when "endMonth" is passed in', () => { + const { endMonth } = getStartEndMonths({ endMonth: new Date(2021, 4, 3) }); + test('"endMonth" should be the end of that month', () => { + expect(endMonth).toEqual(new Date(2021, 4, 31)); }); describe('when "fromYear" is passed in', () => { - test('"toMonth" should be the end of that month', () => { - expect(toMonth).toEqual(new Date(2021, 4, 31)); + test('"endMonth" should be the end of that month', () => { + expect(endMonth).toEqual(new Date(2021, 4, 31)); }); }); }); describe('when "toYear" is passed in', () => { const toYear = 2021; - const expectedtoMonth = new Date(2021, 11, 31); - const { toMonth } = getStartEndMonths({ toYear }); - test('"toMonth" should be the end of that year', () => { - expect(toMonth).toEqual(expectedtoMonth); + const expectedendMonth = new Date(2021, 11, 31); + const { endMonth } = getStartEndMonths({ toYear }); + test('"endMonth" should be the end of that year', () => { + expect(endMonth).toEqual(expectedendMonth); }); }); describe('when "captionLayout" is dropdown', () => { const today = new Date(2024, 4, 3); - const { fromMonth, toMonth } = getStartEndMonths({ + const { startMonth, endMonth } = getStartEndMonths({ captionLayout: "dropdown", today }); - test('"fromMonth" should be 100 years ago', () => { - expect(fromMonth).toEqual(new Date(1924, 0, 1)); + test('"startMonth" should be 100 years ago', () => { + expect(startMonth).toEqual(new Date(1924, 0, 1)); }); - test('"toMonth" should be the end of this year', () => { - expect(toMonth).toEqual(new Date(2024, 11, 31)); + test('"endMonth" should be the end of this year', () => { + expect(endMonth).toEqual(new Date(2024, 11, 31)); }); describe('when "fromYear" is set', () => { const today = new Date(2024, 4, 3); const fromYear = 2022; - const { fromMonth, toMonth } = getStartEndMonths({ + const { startMonth, endMonth } = getStartEndMonths({ captionLayout: "dropdown", fromYear, today }); - test('"fromMonth" should be equal to the "fromYear"', () => { - expect(fromMonth).toEqual(new Date(2022, 0, 1)); + test('"startMonth" should be equal to the "fromYear"', () => { + expect(startMonth).toEqual(new Date(2022, 0, 1)); }); - test('"toMonth" should be the end of this year', () => { - expect(toMonth).toEqual(new Date(2024, 11, 31)); + test('"endMonth" should be the end of this year', () => { + expect(endMonth).toEqual(new Date(2024, 11, 31)); }); }); describe('when "toYear" is set', () => { const today = new Date(2021, 4, 3); const toYear = 2022; - const { toMonth, fromMonth } = getStartEndMonths({ + const { endMonth, startMonth } = getStartEndMonths({ captionLayout: "dropdown", toYear, today }); - test('"fromMonth" should be 100 years ago', () => { - expect(fromMonth).toEqual(new Date(1921, 0, 1)); + test('"startMonth" should be 100 years ago', () => { + expect(startMonth).toEqual(new Date(1921, 0, 1)); }); - test('"toMonth" should be equal to "toYear"', () => { - expect(toMonth).toEqual(new Date(2022, 11, 31)); + test('"endMonth" should be equal to "toYear"', () => { + expect(endMonth).toEqual(new Date(2022, 11, 31)); }); }); }); diff --git a/src/helpers/getStartEndMonths.ts b/src/helpers/getStartEndMonths.ts index ca4d828e9a..0e41824156 100644 --- a/src/helpers/getStartEndMonths.ts +++ b/src/helpers/getStartEndMonths.ts @@ -8,34 +8,30 @@ import { startOfYear } from "date-fns/startOfYear"; import type { DayPickerProps, Mode } from "../types"; /** - * Return the `fromMonth` and `toMonth` prop values values parsing the DayPicker - * props. + * Return the `startMonth` and `endMonth` prop values values parsing the + * DayPicker props. */ export function getStartEndMonths( props: Pick< DayPickerProps, - "fromYear" | "toYear" | "fromMonth" | "toMonth" | "today" | "captionLayout" + | "fromYear" + | "toYear" + | "startMonth" + | "endMonth" + | "today" + | "captionLayout" > -): Pick, "fromMonth" | "toMonth"> { - const { fromYear, toYear } = props; - let { fromMonth, toMonth } = props; +): Pick, "startMonth" | "endMonth"> { + let { startMonth, endMonth } = props; const hasDropdowns = props.captionLayout?.startsWith("dropdown"); - if (fromMonth) { - fromMonth = startOfMonth(fromMonth); - } else if (fromYear) { - fromMonth = new Date(fromYear, 0, 1); - } else if (!fromMonth && hasDropdowns) { - fromMonth = startOfYear(addYears(props.today ?? new Date(), -100)); + if (startMonth) { + startMonth = startOfMonth(startMonth); } - if (toMonth) { - toMonth = endOfMonth(toMonth); - } else if (toYear) { - toMonth = new Date(toYear, 11, 31); - } else if (!toMonth && hasDropdowns) { - toMonth = endOfYear(props.today ?? new Date()); + if (endMonth) { + endMonth = endOfMonth(endMonth); } return { - fromMonth: fromMonth ? startOfDay(fromMonth) : undefined, - toMonth: toMonth ? startOfDay(toMonth) : undefined + startMonth: startMonth ? startOfDay(startMonth) : undefined, + endMonth: endMonth ? startOfDay(endMonth) : undefined }; } diff --git a/src/helpers/getStartMonth.test.ts b/src/helpers/getStartMonth.test.ts index f4c433d114..8eb6a591a9 100644 --- a/src/helpers/getStartMonth.test.ts +++ b/src/helpers/getStartMonth.test.ts @@ -2,7 +2,7 @@ import { addMonths, isSameMonth } from "date-fns"; import { getStartMonth } from "./getStartMonth"; -describe("when no toMonth is given", () => { +describe("when no endMonth is given", () => { describe("when month is in context", () => { const month = new Date(2010, 11, 12); it("return that month", () => { @@ -25,30 +25,30 @@ describe("when no toMonth is given", () => { }); }); }); -describe("when toMonth is given", () => { - describe("when toMonth is before the default initial date", () => { +describe("when endMonth is given", () => { + describe("when endMonth is before the default initial date", () => { const month = new Date(2010, 11, 12); - const toMonth = addMonths(month, -2); + const endMonth = addMonths(month, -2); describe("when the number of month is 1", () => { const numberOfMonths = 1; - it("return the toMonth", () => { + it("return the endMonth", () => { const startMonth = getStartMonth({ month, - toMonth, + endMonth, numberOfMonths }); - expect(isSameMonth(startMonth, toMonth)).toBe(true); + expect(isSameMonth(startMonth, endMonth)).toBe(true); }); }); describe("when the number of month is 3", () => { const numberOfMonths = 3; - it("return the toMonth plus the number of months", () => { + it("return the endMonth plus the number of months", () => { const startMonth = getStartMonth({ month, - toMonth, + endMonth, numberOfMonths }); - const expectedMonth = addMonths(toMonth, -1 * (numberOfMonths - 1)); + const expectedMonth = addMonths(endMonth, -1 * (numberOfMonths - 1)); expect(isSameMonth(startMonth, expectedMonth)).toBe(true); }); }); diff --git a/src/helpers/getStartMonth.ts b/src/helpers/getStartMonth.ts index 3cffd588bf..b641a74420 100644 --- a/src/helpers/getStartMonth.ts +++ b/src/helpers/getStartMonth.ts @@ -14,8 +14,8 @@ export function getStartMonth( PropsBase, | "fromYear" | "toYear" - | "fromMonth" - | "toMonth" + | "endMonth" + | "startMonth" | "month" | "defaultMonth" | "today" @@ -23,18 +23,24 @@ export function getStartMonth( > > ): Date { - const { month, defaultMonth, today, numberOfMonths = 1 } = props; + const { + month, + defaultMonth, + today, + numberOfMonths = 1, + startMonth, + endMonth + } = props; let initialMonth = month || defaultMonth || today || new Date(); - const { fromMonth, toMonth } = getStartEndMonths(props); // Fix the initialMonth if is after the to-date - if (toMonth && differenceInCalendarMonths(toMonth, initialMonth) < 0) { + if (startMonth && differenceInCalendarMonths(startMonth, initialMonth) < 0) { const offset = -1 * (numberOfMonths - 1); - initialMonth = addMonths(toMonth, offset); + initialMonth = addMonths(startMonth, offset); } // Fix the initialMonth if is before the from-date - if (fromMonth && differenceInCalendarMonths(initialMonth, fromMonth) < 0) { - initialMonth = fromMonth; + if (endMonth && differenceInCalendarMonths(initialMonth, endMonth) < 0) { + initialMonth = endMonth; } return startOfMonth(initialMonth); } diff --git a/src/types.ts b/src/types.ts index 54f37ce642..93ab0805e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,7 +44,7 @@ import { /** * The props for the {@link DayPicker} component. * - * @template T - The {@link Mode | selection mode}. Defaults to `"default"`. + * @template T - The selection mode. Defaults to `"default"`. * @template R - Whether the selection is required. Defaults to `false`. * @group Props */ @@ -129,14 +129,53 @@ export interface PropsBase { * @defaultValue 1 */ numberOfMonths?: number; - /** The earliest month to start the month navigation. */ - fromMonth?: Date; - /** The latest month to end the month navigation. */ + /** + * The earliest month to start the month navigation. + * + * @since 9.0.0 + */ + startMonth?: Date | undefined; + /** + * @private + * @deprecated This prop has been removed. Use `hidden={{ before: date }}` + * instead. + */ + fromDate?: Date | undefined; + /** + * @private + * @deprecated This prop has been renamed to `startMonth`. + */ + fromMonth?: Date | undefined; + /** + * @private + * @deprecated Use `startMonth` instead. E.g. `startMonth={new Date(year, + * 0)}`. + */ + fromYear?: number | undefined; + + /** + * The latest month to end the month navigation. + * + * @since 9.0.0 + */ + endMonth?: Date; + /** + * @private + * @deprecated This prop has been removed. Use `hidden={{ after: date }}` + * instead. + */ + toDate?: Date; + /** + * @private + * @deprecated This prop has been renamed to `endMonth`. + */ toMonth?: Date; - /** The earliest year to start the month navigation. */ - fromYear?: number; - /** The latest year to end the month navigation. */ + /** + * @private + * @deprecated Use `endMonth` instead. E.g. `endMonth={new Date(year, 0)}`. + */ toYear?: number; + /** Paginate the month navigation displaying the `numberOfMonths` at time. */ pagedNavigation?: boolean; /** @@ -636,3 +675,5 @@ export type ModifiersStyles = Record & /** The classnames to assign to each day element matching a modifier. */ export type ModifiersClassNames = Record & Partial>; + +export type test = DayPickerProps; diff --git a/website/docs/upgrading.mdx b/website/docs/upgrading.mdx index ae63fa920d..c8aa835c36 100644 --- a/website/docs/upgrading.mdx +++ b/website/docs/upgrading.mdx @@ -47,6 +47,16 @@ The formatters now return a string instead of a ReactNode. If you were using the + const MyComponent = () => 'My caption' }} />; ``` + +#### 4. DayPicker + +```diff +- import { DayPickerProps } from 'react-day-picker'; ++ import { DayPickerProps, Mode } from 'react-day-picker'; +- const props: DayPickerProps; ++ const props: DayPickerProps; +``` + #### 4. Rename deprecated typings Many typings have been deprecated in favor of clarity and shorter names. If you were using the typings, you may need to update your code or it will break in the next major version. @@ -56,14 +66,6 @@ Many typings have been deprecated in favor of clarity and shorter names. If you + import { PropsDefault } from 'react-day-picker/types'; ``` -##### DayPickerProps requires the Mode argument - -```diff -- import { DayPickerProps } from 'react-day-picker'; -+ import { DayPickerProps, Mode } from 'react-day-picker'; -- const props: DayPickerProps; // pass the `Mode` argument -+ const props: DayPickerProps; -``` --- From bf9d62a3fa50805f5816873f0f2aa3c0c737f776 Mon Sep 17 00:00:00 2001 From: Giampaolo Bellavite Date: Sat, 8 Jun 2024 07:19:29 -0500 Subject: [PATCH 2/6] Add deprecation support --- src/helpers/getDropdownYears.test.ts | 28 ++++++++++---------- src/helpers/getDropdownYears.ts | 18 ++++++------- src/helpers/getStartEndMonths.ts | 39 +++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/helpers/getDropdownYears.test.ts b/src/helpers/getDropdownYears.test.ts index 7acbc3ff98..4125f3c7c3 100644 --- a/src/helpers/getDropdownYears.test.ts +++ b/src/helpers/getDropdownYears.test.ts @@ -2,30 +2,30 @@ import { formatYearDropdown } from "../formatters"; import { getDropdownYears } from "./getDropdownYears"; -it("returns undefined if fromMonth is not defined", () => { +it("returns undefined if endMonth is not defined", () => { const result = getDropdownYears({ - toMonth: new Date(), - fromMonth: undefined, + startMonth: new Date(), + endMonth: undefined, formatters: { formatYearDropdown } }); expect(result).toBeUndefined(); }); -it("returns undefined if `toMonth` is not defined", () => { +it("returns undefined if `startMonth` is not defined", () => { const result = getDropdownYears({ - fromMonth: new Date(), - toMonth: undefined, + endMonth: new Date(), + startMonth: undefined, formatters: { formatYearDropdown } }); expect(result).toBeUndefined(); }); -it("returns an array of years between `fromMonth` and `toMonth`", () => { - const fromMonth = new Date(2020, 0, 1); - const toMonth = new Date(2022, 11, 31); +it("returns an array of years between `endMonth` and `startMonth`", () => { + const endMonth = new Date(2020, 0, 1); + const startMonth = new Date(2022, 11, 31); const result = getDropdownYears({ - fromMonth, - toMonth, + endMonth, + startMonth, formatters: { formatYearDropdown: (year: number): string => `Formatted ${year.toString()}` @@ -51,11 +51,11 @@ it("returns an array of years between `fromMonth` and `toMonth`", () => { ]); }); -it("handles same year for fromMonth and toMonth", () => { +it("handles same year for endMonth and startMonth", () => { const year = new Date(2021, 5, 15); const result = getDropdownYears({ - fromMonth: year, - toMonth: year, + endMonth: year, + startMonth: year, formatters: { formatYearDropdown: (year: number): string => `Formatted ${year.toString()}` diff --git a/src/helpers/getDropdownYears.ts b/src/helpers/getDropdownYears.ts index fc61cb0a39..ddf086301a 100644 --- a/src/helpers/getDropdownYears.ts +++ b/src/helpers/getDropdownYears.ts @@ -11,15 +11,15 @@ import type { Formatters, Mode } from "../types"; /** Return the years to show in the dropdown. */ export function getDropdownYears( - props: Pick, "fromMonth" | "toMonth"> & { + props: Pick, "startMonth" | "endMonth"> & { formatters: Pick; }, month?: number | undefined ): DropdownOption[] | undefined { - if (!props.fromMonth) return undefined; - if (!props.toMonth) return undefined; - const firstNavYear = startOfYear(props.fromMonth); - const lastNavYear = endOfYear(props.toMonth); + if (!props.startMonth) return undefined; + if (!props.endMonth) return undefined; + const firstNavYear = startOfYear(props.startMonth); + const lastNavYear = endOfYear(props.endMonth); const years: number[] = []; let year = firstNavYear; while (isBefore(year, lastNavYear) || isSameYear(year, lastNavYear)) { @@ -29,11 +29,11 @@ export function getDropdownYears( return years.map((value) => { const disabled = (month && - props.fromMonth && - new Date(value, month) < startOfMonth(props.fromMonth)) || + props.startMonth && + new Date(value, month) < startOfMonth(props.startMonth)) || (month && - props.toMonth && - new Date(value, month) > startOfMonth(props.toMonth)) || + props.endMonth && + new Date(value, month) > startOfMonth(props.endMonth)) || false; const label = props.formatters.formatYearDropdown(value); return { diff --git a/src/helpers/getStartEndMonths.ts b/src/helpers/getStartEndMonths.ts index 0e41824156..264ee133c3 100644 --- a/src/helpers/getStartEndMonths.ts +++ b/src/helpers/getStartEndMonths.ts @@ -8,30 +8,57 @@ import { startOfYear } from "date-fns/startOfYear"; import type { DayPickerProps, Mode } from "../types"; /** - * Return the `startMonth` and `endMonth` prop values values parsing the - * DayPicker props. + * Return the `fromMonth` and `toMonth` prop values values parsing the DayPicker + * props. */ export function getStartEndMonths( props: Pick< DayPickerProps, - | "fromYear" - | "toYear" | "startMonth" | "endMonth" | "today" | "captionLayout" + // Deprecated: + | "fromYear" + | "toYear" + | "fromMonth" + | "toMonth" > ): Pick, "startMonth" | "endMonth"> { let { startMonth, endMonth } = props; + + // Handle deprecated code + const { fromYear, toYear, fromMonth, toMonth } = props; + if (!startMonth && fromMonth) { + startMonth = fromMonth; + } + if (!startMonth && fromYear) { + startMonth = new Date(fromYear, 0, 1); + } + if (!endMonth && toMonth) { + endMonth = toMonth; + } + if (!endMonth && toYear) { + endMonth = new Date(toYear, 11, 31); + } + const hasDropdowns = props.captionLayout?.startsWith("dropdown"); if (startMonth) { startMonth = startOfMonth(startMonth); + } else if (fromYear) { + startMonth = new Date(fromYear, 0, 1); + } else if (!startMonth && hasDropdowns) { + startMonth = startOfYear(addYears(props.today ?? new Date(), -100)); } if (endMonth) { endMonth = endOfMonth(endMonth); + } else if (toYear) { + endMonth = new Date(toYear, 11, 31); + } else if (!endMonth && hasDropdowns) { + endMonth = endOfYear(props.today ?? new Date()); } return { - startMonth: startMonth ? startOfDay(startMonth) : undefined, - endMonth: endMonth ? startOfDay(endMonth) : undefined + startMonth: startMonth ? startOfDay(startMonth) : startMonth, + endMonth: endMonth ? startOfDay(endMonth) : endMonth }; } From 6f92621688704ac95e2035bf00729fdc111fb469 Mon Sep 17 00:00:00 2001 From: Giampaolo Bellavite Date: Sat, 8 Jun 2024 08:21:26 -0500 Subject: [PATCH 3/6] Rename to V9DeprecatedProps --- src/contexts/props.tsx | 13 ++----------- src/types.ts | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/contexts/props.tsx b/src/contexts/props.tsx index 7e5d8afdef..457b340f4a 100644 --- a/src/contexts/props.tsx +++ b/src/contexts/props.tsx @@ -5,7 +5,7 @@ import React, { useId } from "react"; -import { startOfMonth } from "date-fns"; +import { V9DeprecatedProps } from "react-day-picker/types"; import { getDataAttributes } from "../helpers/getDataAttributes"; import { getDefaultClassNames } from "../helpers/getDefaultClassNames"; @@ -24,15 +24,6 @@ import type { SelectHandler } from "../types"; -/** @private */ -export type DeprecatedProps = - | "fromDate" - | "toDate" - | "fromMonth" - | "toMonth" - | "fromYear" - | "toYear"; - /** * Holds the props passed to the DayPicker component, with some optional props * set to meaningful defaults. @@ -46,7 +37,7 @@ export type DeprecatedProps = export interface PropsContext< T extends Mode = "default", R extends boolean = false -> extends Omit { +> extends Omit { classNames: ClassNames; /** The `data-*` attributes passed to ``. */ dataAttributes: DataAttributes; diff --git a/src/types.ts b/src/types.ts index 93ab0805e4..b521bedcd5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -677,3 +677,23 @@ export type ModifiersClassNames = Record & Partial>; export type test = DayPickerProps; + +/** + * The props that have been deprecated since version 9.0.0. + * + * @since 9.0.0 + * @see https://react-day-picker.js.org/next/upgrading + */ +export type V9DeprecatedProps = + /** Use `hidden` prop instead. */ + | "fromDate" + /** Use `hidden` prop instead. */ + | "toDate" + /** Use `startMonth` instead. */ + | "fromMonth" + /** Use `endMonth` instead. */ + | "toMonth" + /** Use `startMonth` instead. */ + | "fromYear" + /** Use `endMonth` instead. */ + | "toYear"; From 5deb6afa7c0f8fec9ff02bea3f3ca5861c7b30ea Mon Sep 17 00:00:00 2001 From: Giampaolo Bellavite Date: Sat, 8 Jun 2024 08:22:13 -0500 Subject: [PATCH 4/6] docs: update upgrading guide --- src/types-deprecated.ts | 41 ++++---- .../advanced-guides/custom-components.mdx | 30 +++++- website/docs/upgrading.mdx | 93 +++++++++++++++---- 3 files changed, 119 insertions(+), 45 deletions(-) diff --git a/src/types-deprecated.ts b/src/types-deprecated.ts index 553db6578e..dad669c212 100644 --- a/src/types-deprecated.ts +++ b/src/types-deprecated.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Calendar } from "./components/Calendar"; -import { MonthCaption } from "./components/MonthCaption"; +import { MonthCaption, MonthCaptionProps } from "./components/MonthCaption"; import { Week, type WeekProps } from "./components/Week"; import { useCalendar } from "./contexts/calendar"; import { PropsContext, useProps } from "./contexts/props"; @@ -44,11 +44,10 @@ export const Root = Calendar; export const Caption = MonthCaption; /** - * @deprecated This type has been renamed. Use `Parameters[0]` instead. + * @deprecated This type has been renamed. Use `MonthCaptionProps` instead. * @protected */ -export type CaptionProps = Parameters[0]; +export type CaptionProps = MonthCaptionProps; /** * @deprecated This component has been removed. @@ -134,22 +133,21 @@ export type SelectRangeEventHandler = SelectHandler<"range", false>; export type DayPickerProviderProps = any; /** - * @deprecated This type has been renamed to `useProps` + * @deprecated This type has been renamed to `useProps`. * @protected * @group Hooks */ export const useDayPicker = useProps; /** - * @deprecated This type has been renamed to `useProps` + * @deprecated This type has been renamed to `useProps`. * @protected * @group Hooks */ export const useNavigation = useCalendar; /** - * @deprecated This hook has been removed. To customize the rendering of a day, - * use the `htmlAttributes` prop in a custom `Day` component. + * @deprecated This hook has been removed. Use a custom `Day` component instead. * @protected * @group Hooks * @see https://react-day-picker.js.org/advanced-guides/custom-components @@ -187,16 +185,14 @@ export type WeekdayLabel = typeof labelWeekday; export type WeekNumberLabel = typeof labelWeekNumber; /** - * @deprecated The event handler when a day is clicked. Use - * {@link DayMouseEventHandler} instead. + * @deprecated Use {@link DayMouseEventHandler} instead. * @protected */ export type DayClickEventHandler = DayEventHandler; /** - * @deprecated The event handler when a day is focused. This type will be - * removed. Use `DayEventHandler` - * instead. + * @deprecated This type will be removed. Use `DayEventHandler` instead. * @protected */ export type DayFocusEventHandler = DayEventHandler< @@ -204,36 +200,35 @@ export type DayFocusEventHandler = DayEventHandler< >; /** - * @deprecated The event handler when a day gets a keyboard event. This type - * will be removed. Use `DayEventHandler` instead. + * @deprecated This type will be removed. Use + * `DayEventHandler` instead. * @protected */ export type DayKeyboardEventHandler = DayEventHandler; /** - * @deprecated The event handler when a day gets a mouse event. This type will - * be removed. Use `DayEventHandler` instead. + * @deprecated This type will be removed. Use + * `DayEventHandler` instead. * @protected */ export type DayMouseEventHandler = DayEventHandler; /** - * @deprecated The event handler when a day gets a pointer event. This type will - * be removed. Use `DayEventHandler` instead. + * @deprecated This type will be removed. Use + * `DayEventHandler` instead. * @protected */ export type DayPointerEventHandler = DayEventHandler; /** - * @deprecated The event handler when a day gets a touch event. This type will - * be removed. Use `DayEventHandler` instead. + * @deprecated This type will be removed. Use + * `DayEventHandler` instead. * @protected */ export type DayTouchEventHandler = DayEventHandler; /** - * @deprecated The type has been renamed and needs a `Mode` argument. Use - * `PropsContext` instead. + * @deprecated The type has been renamed. Use `PropsContext` instead. * @protected */ export type DayPickerContext = PropsContext; diff --git a/website/docs/advanced-guides/custom-components.mdx b/website/docs/advanced-guides/custom-components.mdx index f98dd0e941..6259d03ae9 100644 --- a/website/docs/advanced-guides/custom-components.mdx +++ b/website/docs/advanced-guides/custom-components.mdx @@ -6,12 +6,14 @@ sidebar_position: 4 Use the `components` prop to swap the components used to render DayPicker. A list of the components that can be customized is available in the [Components Reference](../api#components). -:::tip Advanced Feature +:::warning Advanced Feature -- Custom components may not have a stable API yet and may break in a next release. +- This feature requires basic understanding of the output generated by DayPicker. - Get familiar with the [API reference](../api#components) and the [DayPicker Anatomy](../using-daypicker/anatomy.mdx) first. +- Make sure you don't break [accessibility](../using-daypicker/accessibility.mdx) when customizing components. +- Custom components may not have a stable API yet and may break in a minor release. -::: + ::: --- @@ -38,6 +40,28 @@ export function MyDatePicker() { +### Extending the Default Components + +You can also import the default components to add custom behavior. Just make sure you pass the default component to the root. + +For example, to add a custom class to the `Day` component: + +```tsx title="./CustomDay.tsx" +import { DayPicker, Day, type DayProps } from "react-day-picker"; + +function CustomDay(props: DayProps) { + return ( + +
{props.children}
+
+ ); +} + +export function MyDatePicker() { + return ; +} +``` + ## DayPicker Hooks When creating custom components, you will find useful the [DayPicker hooks](../api/index.md#hooks). These utilities provide access to the internal state and methods of the DayPicker component. diff --git a/website/docs/upgrading.mdx b/website/docs/upgrading.mdx index c8aa835c36..ab47c28e3f 100644 --- a/website/docs/upgrading.mdx +++ b/website/docs/upgrading.mdx @@ -14,50 +14,74 @@ This document is still a work in progress. Please help us complete this guide by ::: -### Checklist - Follow these steps to upgrade your project from v8 to v9: -#### 1. Upgrade to the next version +### 1. Upgrade to the next version ```bash npm2yarn npm install react-day-picker@next date-fns ``` -#### 2. Update test selectors +### 2. Check for removed props + +The following props have been removed. Check in your code if you are using them. + +| Removed prop | Notes | +| ------------ | -------------------------------------------------------------------------------------------- | +| ~`fromDate`~ | Replace it with the `hidden` prop and the `before` [Matcher](./api/type-aliases/Matcher.md). | +| ~`toDate`~ | Replace it with the `hidden` prop and the `after` [Matcher](./api/type-aliases/Matcher.md). | + +```diff +- ++