Repository: Hacker0x01/react-datepicker
Pull request #4817 (2024-05-24 02:51:30)

Allow for the keyboard preselection to skip excluded dates/weeks/months
---
name: Allow for skipping of excluded dates/weeks/months
about: Create a pull request to improve this repository
title: Allow for skipping of excluded dates/weeks/months
labels: ""
assignees: ""
---

## Description
**Linked issue**: #4731 

**Problem**
Currently, the date picker only stops when the user is about to navigate to an excluded month, which, if let's say there are multiple disabled dates side by side spanning the whole row, the user is unable to navigate to the months after it. This only happens on the month picker.

![image](https://github.com/Hacker0x01/react-datepicker/assets/49902829/ab8f2657-4187-4a05-ad0c-d4813cff8785)

**Changes**
I have made recursive functions in the keydown functionality of the month and day calendar to get a new day/month if the date is disabled/excluded. Even though this is only a month picker thing, I have changed the behavior of the day calendar as well to make it consistent.

## Screenshots

**Day**

https://github.com/Hacker0x01/react-datepicker/assets/49902829/5637ad57-2773-4d69-ba58-a9714167bfed

**Week**

https://github.com/Hacker0x01/react-datepicker/assets/49902829/ccae4796-7db5-4292-b552-52cc5c74cb72

**Month**

https://github.com/Hacker0x01/react-datepicker/assets/49902829/54e07d27-1298-4562-a337-f03ab33c6675

## To reviewers
If this behavior should be made optional, please let me know

## Contribution checklist
- [x] I have followed the [contributing guidelines](https://github.com/Hacker0x01/react-datepicker/blob/main/CONTRIBUTING.md).
- [x] I have added sufficient test coverage for my changes.
- [x] I have formatted my code with Prettier and checked for linting issues with ESLint for code readability.



Patch:
diff --git a/docs-site/src/components/Examples/index.js b/docs-site/src/components/Examples/index.js
index b7120c0e..3b4716ef 100644
--- a/docs-site/src/components/Examples/index.js
+++ b/docs-site/src/components/Examples/index.js
@@ -31,6 +31,7 @@ import ExcludeDates from "../../examples/excludeDates";
 import ExcludedWithMessage from "../../examples/excludeDatesWithMessage";
 import ExcludeDateIntervals from "../../examples/excludeDateIntervals";
 import ExcludeDatesMonthPicker from "../../examples/excludeDatesMonthPicker";
+import ExcludeDatesRangeMonthPicker from "../../examples/excludeDatesRangeMonthPicker";
 import HighlightDates from "../../examples/highlightDates";
 import HolidayDates from "../../examples/holidayDates";
 import HighlightDatesRanges from "../../examples/highlightDatesRanges";
@@ -81,6 +82,7 @@ import TimeInput from "../../examples/timeInput";
 import StrictParsing from "../../examples/strictParsing";
 import MonthPicker from "../../examples/monthPicker";
 import WeekPicker from "../../examples/weekPicker";
+import ExcludeWeeks from "../../examples/excludeWeeks";
 import monthPickerFullName from "../../examples/monthPickerFullName";
 import monthPickerTwoColumns from "../../examples/monthPickerTwoColumns";
 import monthPickerFourColumns from "../../examples/monthPickerFourColumns";
@@ -286,6 +288,10 @@ export default class exampleComponents extends React.Component {
       title: "Exclude Months in Month Picker",
       component: ExcludeDatesMonthPicker,
     },
+    {
+      title: "Exclude Months in Range Month Picker",
+      component: ExcludeDatesRangeMonthPicker,
+    },
     {
       title: "Exclude Times",
       component: ExcludeTimes,
@@ -540,6 +546,10 @@ export default class exampleComponents extends React.Component {
       title: "Week Picker",
       component: WeekPicker,
     },
+    {
+      title: "Exclude Weeks",
+      component: ExcludeWeeks,
+    },
     {
       title: "External Form",
       component: ExternalForm,
diff --git a/docs-site/src/examples/excludeDatesMonthPicker.js b/docs-site/src/examples/excludeDatesMonthPicker.js
index ffb75efb..13a0a0a2 100644
--- a/docs-site/src/examples/excludeDatesMonthPicker.js
+++ b/docs-site/src/examples/excludeDatesMonthPicker.js
@@ -1,13 +1,11 @@
 () => {
-  const [startDate, setStartDate] = useState(1659312000000);
+  const [startDate, setStartDate] = useState(new Date("2024-08-01"));
   return (
     <DatePicker
       selected={startDate}
       onChange={(date) => setStartDate(date)}
       dateFormat="MM/yyyy"
-      excludeDates={[
-        1661990400000, 1664582400000, 1667260800000, 1672531200000,
-      ]}
+      excludeDates={[new Date("2024-05-01"), new Date("2024-06-01")]}
       showMonthYearPicker
     />
   );
diff --git a/docs-site/src/examples/excludeDatesRangeMonthPicker.js b/docs-site/src/examples/excludeDatesRangeMonthPicker.js
new file mode 100644
index 00000000..c9239446
--- /dev/null
+++ b/docs-site/src/examples/excludeDatesRangeMonthPicker.js
@@ -0,0 +1,30 @@
+() => {
+  const defaultStartDate = new Date("2024-08-01");
+  const defaultEndDate = new Date("2024-10-01");
+  const [startDate, setStartDate] = useState(defaultStartDate);
+  const [endDate, setEndDate] = useState(defaultEndDate);
+
+  const handleChange = ([newStartDate, newEndDate]) => {
+    setStartDate(newStartDate);
+    setEndDate(newEndDate);
+  };
+
+  return (
+    <DatePicker
+      selected={startDate}
+      startDate={startDate}
+      endDate={endDate}
+      onChange={handleChange}
+      excludeDates={[
+        new Date("2024-05-01"),
+        new Date("2024-02-01"),
+        new Date("2024-01-01"),
+        new Date("2024-11-01"),
+      ]}
+      dateFormat="MM/yyyy"
+      placeholderText="Select a month other than the disabled months"
+      showMonthYearPicker
+      selectsRange
+    />
+  );
+};
diff --git a/docs-site/src/examples/excludeWeeks.js b/docs-site/src/examples/excludeWeeks.js
new file mode 100644
index 00000000..0694be1b
--- /dev/null
+++ b/docs-site/src/examples/excludeWeeks.js
@@ -0,0 +1,17 @@
+() => {
+  const [startDate, setStartDate] = useState(new Date("2021/02/22"));
+  return (
+    <DatePicker
+      selected={startDate}
+      onChange={(date) => setStartDate(date)}
+      dateFormat="I/R"
+      locale="en-GB"
+      excludeDateIntervals={[
+        { start: "2021/02/08", end: "2021/02/14" },
+        { start: "2021/01/18", end: "2021/01/24" },
+      ]}
+      showWeekNumbers
+      showWeekPicker
+    />
+  );
+};
diff --git a/src/date_utils.ts b/src/date_utils.ts
index 311f10e4..470cc6a8 100644
--- a/src/date_utils.ts
+++ b/src/date_utils.ts
@@ -69,6 +69,21 @@ interface LocaleObj
 
 export type Locale = string | LocaleObj;
 
+export enum KeyType {
+  ArrowUp = "ArrowUp",
+  ArrowDown = "ArrowDown",
+  ArrowLeft = "ArrowLeft",
+  ArrowRight = "ArrowRight",
+  PageUp = "PageUp",
+  PageDown = "PageDown",
+  Home = "Home",
+  End = "End",
+  Enter = "Enter",
+  Space = " ",
+  Tab = "Tab",
+  Escape = "Escape",
+}
+
 function getLocaleScope() {
   // Use this cast to avoid messing with users globalThis (like window) and the rest of keys in the globalThis object we don't care about
   const scope = (typeof window !== "undefined"
@@ -881,6 +896,38 @@ export function isMonthInRange(
   return false;
 }
 
+/**
+ * To check if a date's month and year are disabled/excluded
+ * @param date Date to check
+ * @returns {boolean} true if month and year are disabled/excluded, false otherwise
+ */
+export function isMonthYearDisabled(
+  date: Date,
+  {
+    minDate,
+    maxDate,
+    excludeDates,
+    includeDates,
+  }: Pick<
+    DateFilterOptions,
+    "minDate" | "maxDate" | "excludeDates" | "includeDates"
+  > = {},
+): boolean {
+  return (
+    isOutOfBounds(date, { minDate, maxDate }) ||
+    (excludeDates &&
+      excludeDates.some((excludedDate) =>
+        isSameMonth(
+          excludedDate instanceof Date ? excludedDate : excludedDate.date,
+          date,
+        ),
+      )) ||
+    (includeDates &&
+      !includeDates.some((includedDate) => isSameMonth(includedDate, date))) ||
+    false
+  );
+}
+
 export function isQuarterDisabled(
   quarter: Date,
   {
@@ -1505,6 +1552,5 @@ export function isDateBefore(date: Date, dateToCompare: Date): boolean {
 export function isSpaceKeyDown(
   event: React.KeyboardEvent<HTMLDivElement>,
 ): boolean {
-  const SPACE_KEY = " ";
-  return event.key === SPACE_KEY;
+  return event.key === KeyType.Space;
 }
diff --git a/src/day.tsx b/src/day.tsx
index bda588f1..e2a1d504 100644
--- a/src/day.tsx
+++ b/src/day.tsx
@@ -20,6 +20,7 @@ import {
   type DateNumberType,
   type Locale,
   type HolidaysMap,
+  KeyType,
 } from "./date_utils";
 
 interface DayProps
@@ -167,7 +168,7 @@ export default class Day extends Component<DayProps> {
 
   handleOnKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
     const eventKey = event.key;
-    if (eventKey === " ") {
+    if (eventKey === KeyType.Space) {
       event.preventDefault();
       event.key = "Enter";
     }
diff --git a/src/index.tsx b/src/index.tsx
index 68645280..3657aa86 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -53,6 +53,7 @@ import {
   getEndOfDay,
   type HighlightDate,
   type HolidayItem,
+  KeyType,
 } from "./date_utils";
 import PopperComponent from "./popper_component";
 import Portal from "./portal";
@@ -845,9 +846,9 @@ export default class DatePicker extends Component<
       !this.props.preventOpenOnFocus
     ) {
       if (
-        eventKey === "ArrowDown" ||
-        eventKey === "ArrowUp" ||
-        eventKey === "Enter"
+        eventKey === KeyType.ArrowDown ||
+        eventKey === KeyType.ArrowUp ||
+        eventKey === KeyType.Enter
       ) {
         this.onInputClick();
       }
@@ -856,7 +857,7 @@ export default class DatePicker extends Component<
 
     // if calendar is open, these keys will focus the selected item
     if (this.state.open) {
-      if (eventKey === "ArrowDown" || eventKey === "ArrowUp") {
+      if (eventKey === KeyType.ArrowDown || eventKey === KeyType.ArrowUp) {
         event.preventDefault();
         const selectorString = this.props.showTimeSelectOnly
           ? ".react-datepicker__time-list-item[tabindex='0']"
@@ -922,95 +923,170 @@ export default class DatePicker extends Component<
 
   // keyDown events passed down to day.jsx
   onDayKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
+    const {
+      minDate,
+      maxDate,
+      disabledKeyboardNavigation,
+      showWeekPicker,
+      shouldCloseOnSelect,
+      locale,
+      calendarStartDay,
+      adjustDateOnChange,
+      inline,
+    } = this.props;
     this.props.onKeyDown?.(event);
-    const eventKey = event.key;
+    if (disabledKeyboardNavigation) return;
+    const eventKey = event.key as KeyType;
     const isShiftKeyActive = event.shiftKey;
 
     const copy = newDate(this.state.preSelection);
-    if (eventKey === "Enter") {
-      event.preventDefault();
-      this.handleSelect(copy, event);
-      !this.props.shouldCloseOnSelect && this.setPreSelection(copy);
-    } else if (eventKey === "Escape") {
-      event.preventDefault();
 
-      this.setOpen(false);
-      if (!this.inputOk()) {
-        this.props.onInputError?.({ code: 1, msg: INPUT_ERR_1 });
-      }
-    } else if (!this.props.disabledKeyboardNavigation) {
-      let newSelection;
+    const calculateNewDate = (eventKey: KeyType, date: Date): Date => {
+      let newCalculatedDate = date;
       switch (eventKey) {
-        case "ArrowLeft":
-          if (this.props.showWeekPicker) {
-            newSelection = subWeeks(copy, 1);
-          } else {
-            newSelection = subDays(copy, 1);
-          }
-          break;
-        case "ArrowRight":
-          if (this.props.showWeekPicker) {
-            newSelection = addWeeks(copy, 1);
-          } else {
-            newSelection = addDays(copy, 1);
-          }
+        case KeyType.ArrowRight:
+          newCalculatedDate = showWeekPicker
+            ? addWeeks(date, 1)
+            : addDays(date, 1);
           break;
-        case "ArrowUp":
-          newSelection = subWeeks(copy, 1);
+        case KeyType.ArrowLeft:
+          newCalculatedDate = showWeekPicker
+            ? subWeeks(date, 1)
+            : subDays(date, 1);
           break;
-        case "ArrowDown":
-          newSelection = addWeeks(copy, 1);
+        case KeyType.ArrowUp:
+          newCalculatedDate = subWeeks(date, 1);
           break;
-        case "PageUp":
-          newSelection = isShiftKeyActive
-            ? subYears(copy, 1)
-            : subMonths(copy, 1);
+        case KeyType.ArrowDown:
+          newCalculatedDate = addWeeks(date, 1);
           break;
-        case "PageDown":
-          newSelection = isShiftKeyActive
-            ? addYears(copy, 1)
-            : addMonths(copy, 1);
+        case KeyType.PageUp:
+          newCalculatedDate = isShiftKeyActive
+            ? subYears(date, 1)
+            : subMonths(date, 1);
           break;
-        case "Home":
-          newSelection = getStartOfWeek(
-            copy,
-            this.props.locale,
-            this.props.calendarStartDay,
-          );
+        case KeyType.PageDown:
+          newCalculatedDate = isShiftKeyActive
+            ? addYears(date, 1)
+            : addMonths(date, 1);
           break;
-        case "End":
-          newSelection = getEndOfWeek(copy);
+        case KeyType.Home:
+          newCalculatedDate = getStartOfWeek(date, locale, calendarStartDay);
           break;
-        default:
-          newSelection = null;
+        case KeyType.End:
+          newCalculatedDate = getEndOfWeek(date);
           break;
       }
-      if (!newSelection) {
-        if (this.props.onInputError) {
-          this.props.onInputError({ code: 1, msg: INPUT_ERR_1 });
+      return newCalculatedDate;
+    };
+
+    const getNewDate = (eventKey: KeyType, date: Date): Date => {
+      const MAX_ITERATIONS = 40;
+      let eventKeyCopy = eventKey;
+      let validDateFound = false;
+      let iterations = 0;
+      let newSelection = calculateNewDate(eventKey, date);
+
+      while (!validDateFound) {
+        if (iterations >= MAX_ITERATIONS) {
+          newSelection = date;
+          break;
         }
-        return;
+        // if minDate exists and the new selection is before the min date, get the nearest date that isn't disabled
+        if (minDate && newSelection < minDate) {
+          eventKeyCopy = KeyType.ArrowRight;
+          newSelection = isDayDisabled(minDate, this.props)
+            ? calculateNewDate(eventKeyCopy, newSelection)
+            : minDate;
+        }
+
+        // if maxDate exists and the new selection is after the max date, get the nearest date that isn't disabled
+        if (maxDate && newSelection > maxDate) {
+          eventKeyCopy = KeyType.ArrowLeft;
+          newSelection = isDayDisabled(maxDate, this.props)
+            ? calculateNewDate(eventKeyCopy, newSelection)
+            : maxDate;
+        }
+
+        if (isDayDisabled(newSelection, this.props)) {
+          // if PageUp and Home is pressed to a disabled date, it will try to find the next available date after
+          if (
+            eventKeyCopy === KeyType.PageUp ||
+            eventKeyCopy === KeyType.Home
+          ) {
+            eventKeyCopy = KeyType.ArrowRight;
+          }
+
+          // if PageDown and End is pressed to a disabled date, it will try to find the next available date before
+          if (
+            eventKeyCopy === KeyType.PageDown ||
+            eventKeyCopy === KeyType.End
+          ) {
+            eventKeyCopy = KeyType.ArrowLeft;
+          }
+          newSelection = calculateNewDate(eventKeyCopy, newSelection);
+        } else {
+          validDateFound = true;
+        }
+        iterations++;
       }
+
+      return newSelection;
+    };
+
+    if (eventKey === KeyType.Enter) {
       event.preventDefault();
-      this.setState({ lastPreSelectChange: PRESELECT_CHANGE_VIA_NAVIGATE });
-      if (this.props.adjustDateOnChange) {
-        this.setSelected(newSelection);
+      this.handleSelect(copy, event);
+      !shouldCloseOnSelect && this.setPreSelection(copy);
+      return;
+    } else if (eventKey === KeyType.Escape) {
+      event.preventDefault();
+
+      this.setOpen(false);
+      if (!this.inputOk()) {
+        this.props.onInputError?.({ code: 1, msg: INPUT_ERR_1 });
       }
-      this.setPreSelection(newSelection);
-      // need to figure out whether month has changed to focus day in inline version
-      if (this.props.inline) {
-        const prevMonth = getMonth(copy);
-        const newMonth = getMonth(newSelection);
-        const prevYear = getYear(copy);
-        const newYear = getYear(newSelection);
-
-        if (prevMonth !== newMonth || prevYear !== newYear) {
-          // month has changed
-          this.setState({ shouldFocusDayInline: true });
-        } else {
-          // month hasn't changed
-          this.setState({ shouldFocusDayInline: false });
-        }
+      return;
+    }
+
+    let newSelection = null;
+    switch (eventKey) {
+      case KeyType.ArrowLeft:
+      case KeyType.ArrowRight:
+      case KeyType.ArrowUp:
+      case KeyType.ArrowDown:
+      case KeyType.PageUp:
+      case KeyType.PageDown:
+      case KeyType.Home:
+      case KeyType.End:
+        newSelection = getNewDate(eventKey, copy);
+        break;
+    }
+    if (!newSelection) {
+      if (this.props.onInputError) {
+        this.props.onInputError({ code: 1, msg: INPUT_ERR_1 });
+      }
+      return;
+    }
+    event.preventDefault();
+    this.setState({ lastPreSelectChange: PRESELECT_CHANGE_VIA_NAVIGATE });
+    if (adjustDateOnChange) {
+      this.setSelected(newSelection);
+    }
+    this.setPreSelection(newSelection);
+    // need to figure out whether month has changed to focus day in inline version
+    if (inline) {
+      const prevMonth = getMonth(copy);
+      const newMonth = getMonth(newSelection);
+      const prevYear = getYear(copy);
+      const newYear = getYear(newSelection);
+
+      if (prevMonth !== newMonth || prevYear !== newYear) {
+        // month has changed
+        this.setState({ shouldFocusDayInline: true });
+      } else {
+        // month hasn't changed
+        this.setState({ shouldFocusDayInline: false });
       }
     }
   };
diff --git a/src/month.tsx b/src/month.tsx
index 331d5fdc..cdae9b73 100644
--- a/src/month.tsx
+++ b/src/month.tsx
@@ -2,6 +2,7 @@ import { clsx } from "clsx";
 import React, { Component, createRef } from "react";
 
 import {
+  KeyType,
   addDays,
   addMonths,
   addQuarters,
@@ -20,6 +21,7 @@ import {
   isDayExcluded,
   isMonthDisabled,
   isMonthInRange,
+  isMonthYearDisabled,
   isQuarterDisabled,
   isQuarterInRange,
   isSameMonth,
@@ -509,94 +511,181 @@ export default class Month extends Component<MonthProps> {
   };
 
   handleMonthNavigation = (newMonth: number, newDate: Date) => {
-    if (this.isDisabled(newDate) || this.isExcluded(newDate)) {
-      return;
-    }
-
     this.props.setPreSelection?.(newDate);
 
     this.MONTH_REFS[newMonth]?.current?.focus();
   };
 
-  onMonthKeyDown = (
+  handleKeyboardNavigation = (
     event: React.KeyboardEvent<HTMLDivElement>,
+    eventKey: KeyType,
     month: number,
   ) => {
     const {
       selected,
       preSelection,
-      disabledKeyboardNavigation,
-      showTwoColumnMonthYearPicker,
-      showFourColumnMonthYearPicker,
       setPreSelection,
-      handleOnMonthKeyDown,
+      minDate,
+      maxDate,
+      showFourColumnMonthYearPicker,
+      showTwoColumnMonthYearPicker,
     } = this.props;
-    const eventKey = event.key;
-    if (eventKey !== "Tab") {
-      // preventDefault on tab event blocks focus change
-      event.preventDefault();
-    }
-    if (!disabledKeyboardNavigation) {
-      const monthColumnsLayout = getMonthColumnsLayout(
-        showFourColumnMonthYearPicker,
-        showTwoColumnMonthYearPicker,
-      );
+    if (!preSelection) return;
+
+    const monthColumnsLayout = getMonthColumnsLayout(
+      showFourColumnMonthYearPicker,
+      showTwoColumnMonthYearPicker,
+    );
 
-      const verticalOffset =
-        MONTH_COLUMNS[monthColumnsLayout]?.verticalNavigationOffset;
+    const verticalOffset = this.getVerticalOffset(monthColumnsLayout);
 
-      const monthsGrid = MONTH_COLUMNS[monthColumnsLayout]?.grid;
+    const monthsGrid = MONTH_COLUMNS[monthColumnsLayout]?.grid;
 
+    const calculateNewDateAndMonth = (
+      eventKey: KeyType,
+      date: Date,
+      month: number,
+    ): { newCalculatedDate: Date; newCalculatedMonth: number } => {
+      let newCalculatedDate = date;
+      let newCalculatedMonth = month;
       switch (eventKey) {
-        case "Enter":
-          if (!this.isMonthDisabled(month)) {
-            this.onMonthClick(event, month);
-            setPreSelection?.(selected);
-          }
-          break;
-        case "ArrowRight":
-          if (!preSelection) {
-            break;
-          }
-          this.handleMonthNavigation(
-            month === 11 ? 0 : month + MONTH_NAVIGATION_HORIZONTAL_OFFSET,
-            addMonths(preSelection, MONTH_NAVIGATION_HORIZONTAL_OFFSET),
+        case KeyType.ArrowRight:
+          newCalculatedDate = addMonths(
+            date,
+            MONTH_NAVIGATION_HORIZONTAL_OFFSET,
           );
+          newCalculatedMonth =
+            month === 11 ? 0 : month + MONTH_NAVIGATION_HORIZONTAL_OFFSET;
           break;
-        case "ArrowLeft":
-          if (!preSelection) {
-            break;
-          }
-          this.handleMonthNavigation(
-            month === 0 ? 11 : month - MONTH_NAVIGATION_HORIZONTAL_OFFSET,
-            subMonths(preSelection, MONTH_NAVIGATION_HORIZONTAL_OFFSET),
+        case KeyType.ArrowLeft:
+          newCalculatedDate = subMonths(
+            date,
+            MONTH_NAVIGATION_HORIZONTAL_OFFSET,
           );
+          newCalculatedMonth =
+            month === 0 ? 11 : month - MONTH_NAVIGATION_HORIZONTAL_OFFSET;
           break;
-        case "ArrowUp":
-          if (!preSelection) {
-            break;
-          }
-          this.handleMonthNavigation(
-            // Check if month on the first row
-            monthsGrid?.[0]?.includes(month)
-              ? month + 12 - (verticalOffset ?? 0)
-              : month - (verticalOffset ?? 0),
-            subMonths(preSelection, verticalOffset ?? 0),
-          );
+        case KeyType.ArrowUp:
+          newCalculatedDate = subMonths(date, verticalOffset);
+          newCalculatedMonth = monthsGrid?.[0]?.includes(month)
+            ? month + 12 - verticalOffset
+            : month - verticalOffset;
           break;
-        case "ArrowDown":
-          if (!preSelection) {
-            break;
-          }
-          this.handleMonthNavigation(
-            // Check if month on the last row
-            monthsGrid?.[monthsGrid.length - 1]?.includes(month)
-              ? month - 12 + (verticalOffset ?? 0)
-              : month + (verticalOffset ?? 0),
-            addMonths(preSelection, verticalOffset ?? 0),
-          );
+        case KeyType.ArrowDown:
+          newCalculatedDate = addMonths(date, verticalOffset);
+          newCalculatedMonth = monthsGrid?.[monthsGrid.length - 1]?.includes(
+            month,
+          )
+            ? month - 12 + verticalOffset
+            : month + verticalOffset;
           break;
       }
+
+      return { newCalculatedDate, newCalculatedMonth };
+    };
+
+    const getNewDateAndMonth = (
+      eventKey: KeyType,
+      selectedDate: Date,
+      month: number,
+    ): { newCalculatedDate: Date; newCalculatedMonth: number } => {
+      const MAX_ITERATIONS = 40;
+      let eventKeyCopy = eventKey;
+      let validDateFound = false;
+      let iterations = 0;
+      let { newCalculatedDate, newCalculatedMonth } = calculateNewDateAndMonth(
+        eventKeyCopy,
+        selectedDate,
+        month,
+      );
+
+      while (!validDateFound) {
+        if (iterations >= MAX_ITERATIONS) {
+          newCalculatedDate = selectedDate;
+          newCalculatedMonth = month;
+          break;
+        }
+        // if minDate exists and the new month is before the minimum month, it will try to find the next available month after
+        if (minDate && newCalculatedDate < minDate) {
+          eventKeyCopy = KeyType.ArrowRight;
+          const obj = calculateNewDateAndMonth(
+            eventKeyCopy,
+            newCalculatedDate,
+            newCalculatedMonth,
+          );
+          newCalculatedDate = obj.newCalculatedDate;
+          newCalculatedMonth = obj.newCalculatedMonth;
+        }
+
+        // if maxDate exists and the new month is after the maximum month, it will try to find the next available month before
+        if (maxDate && newCalculatedDate > maxDate) {
+          eventKeyCopy = KeyType.ArrowLeft;
+          const obj = calculateNewDateAndMonth(
+            eventKeyCopy,
+            newCalculatedDate,
+            newCalculatedMonth,
+          );
+          newCalculatedDate = obj.newCalculatedDate;
+          newCalculatedMonth = obj.newCalculatedMonth;
+        }
+
+        if (isMonthYearDisabled(newCalculatedDate, this.props)) {
+          const obj = calculateNewDateAndMonth(
+            eventKeyCopy,
+            newCalculatedDate,
+            newCalculatedMonth,
+          );
+          newCalculatedDate = obj.newCalculatedDate;
+          newCalculatedMonth = obj.newCalculatedMonth;
+        } else {
+          validDateFound = true;
+        }
+        iterations++;
+      }
+
+      return { newCalculatedDate, newCalculatedMonth };
+    };
+
+    if (eventKey === KeyType.Enter) {
+      if (!this.isMonthDisabled(month)) {
+        this.onMonthClick(event, month);
+        setPreSelection?.(selected);
+      }
+      return;
+    }
+
+    const { newCalculatedDate, newCalculatedMonth } = getNewDateAndMonth(
+      eventKey,
+      preSelection,
+      month,
+    );
+
+    switch (eventKey) {
+      case KeyType.ArrowRight:
+      case KeyType.ArrowLeft:
+      case KeyType.ArrowUp:
+      case KeyType.ArrowDown:
+        this.handleMonthNavigation(newCalculatedMonth, newCalculatedDate);
+        break;
+    }
+  };
+
+  getVerticalOffset = (monthColumnsLayout: string) => {
+    return MONTH_COLUMNS[monthColumnsLayout]?.verticalNavigationOffset ?? 0;
+  };
+
+  onMonthKeyDown = (
+    event: React.KeyboardEvent<HTMLDivElement>,
+    month: number,
+  ) => {
+    const { disabledKeyboardNavigation, handleOnMonthKeyDown } = this.props;
+    const eventKey = event.key as KeyType;
+    if (eventKey !== KeyType.Tab) {
+      // preventDefault on tab event blocks focus change
+      event.preventDefault();
+    }
+    if (!disabledKeyboardNavigation) {
+      this.handleKeyboardNavigation(event, eventKey, month);
     }
 
     handleOnMonthKeyDown && handleOnMonthKeyDown(event);
@@ -642,11 +731,11 @@ export default class Month extends Component<MonthProps> {
     const eventKey = event.key;
     if (!this.props.disabledKeyboardNavigation) {
       switch (eventKey) {
-        case "Enter":
+        case KeyType.Enter:
           this.onQuarterClick(event, quarter);
           this.props.setPreSelection?.(this.props.selected);
           break;
-        case "ArrowRight":
+        case KeyType.ArrowRight:
           if (!this.props.preSelection) {
             break;
           }
@@ -655,7 +744,7 @@ export default class Month extends Component<MonthProps> {
             addQuarters(this.props.preSelection, 1),
           );
           break;
-        case "ArrowLeft":
+        case KeyType.ArrowLeft:
           if (!this.props.preSelection) {
             break;
           }
diff --git a/src/time.tsx b/src/time.tsx
index 5bfb8d94..d7c97d1d 100644
--- a/src/time.tsx
+++ b/src/time.tsx
@@ -15,6 +15,7 @@ import {
   getSeconds,
   type Locale,
   type TimeFilterOptions,
+  KeyType,
 } from "./date_utils";
 
 interface TimeProps
@@ -152,13 +153,13 @@ export default class Time extends Component<TimeProps, TimeState> {
     event: React.KeyboardEvent<HTMLLIElement>,
     time: Date,
   ): void => {
-    if (event.key === " ") {
+    if (event.key === KeyType.Space) {
       event.preventDefault();
-      event.key = "Enter";
+      event.key = KeyType.Enter;
     }
 
     if (
-      (event.key === "ArrowUp" || event.key === "ArrowLeft") &&
+      (event.key === KeyType.ArrowUp || event.key === KeyType.ArrowLeft) &&
       event.target instanceof HTMLElement &&
       event.target.previousSibling
     ) {
@@ -167,7 +168,7 @@ export default class Time extends Component<TimeProps, TimeState> {
         event.target.previousSibling.focus();
     }
     if (
-      (event.key === "ArrowDown" || event.key === "ArrowRight") &&
+      (event.key === KeyType.ArrowDown || event.key === KeyType.ArrowRight) &&
       event.target instanceof HTMLElement &&
       event.target.nextSibling
     ) {
@@ -176,7 +177,7 @@ export default class Time extends Component<TimeProps, TimeState> {
         event.target.nextSibling.focus();
     }
 
-    if (event.key === "Enter") {
+    if (event.key === KeyType.Enter) {
       this.handleClick(time);
     }
     this.props.handleOnKeyDown?.(event);
diff --git a/src/week_number.tsx b/src/week_number.tsx
index 9e213df2..ca3ca2f3 100644
--- a/src/week_number.tsx
+++ b/src/week_number.tsx
@@ -1,7 +1,7 @@
 import { clsx } from "clsx";
 import React, { Component, createRef } from "react";
 
-import { isSameDay } from "./date_utils";
+import { KeyType, isSameDay } from "./date_utils";
 
 interface WeekNumberProps {
   weekNumber: number;
@@ -45,7 +45,7 @@ export default class WeekNumber extends Component<WeekNumberProps> {
 
   handleOnKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
     const eventKey = event.key;
-    if (eventKey === " ") {
+    if (eventKey === KeyType.Space) {
       event.preventDefault();
       event.key = "Enter";
     }
diff --git a/src/year.tsx b/src/year.tsx
index 3b1a6c3b..95a8f4fb 100644
--- a/src/year.tsx
+++ b/src/year.tsx
@@ -17,6 +17,7 @@ import {
   newDate,
   setYear,
   subYears,
+  KeyType,
 } from "./date_utils";
 
 const VERTICAL_NAVIGATION_OFFSET = 3;
@@ -254,14 +255,14 @@ export default class Year extends Component<YearProps> {
 
     if (!this.props.disabledKeyboardNavigation) {
       switch (key) {
-        case "Enter":
+        case KeyType.Enter:
           if (this.props.selected == null) {
             break;
           }
           this.onYearClick(event, y);
           this.props.setPreSelection?.(this.props.selected);
           break;
-        case "ArrowRight":
+        case KeyType.ArrowRight:
           if (this.props.preSelection == null) {
             break;
           }
@@ -270,7 +271,7 @@ export default class Year extends Component<YearProps> {
             addYears(this.props.preSelection, 1),
           );
           break;
-        case "ArrowLeft":
+        case KeyType.ArrowLeft:
           if (this.props.preSelection == null) {
             break;
           }
@@ -279,7 +280,7 @@ export default class Year extends Component<YearProps> {
             subYears(this.props.preSelection, 1),
           );
           break;
-        case "ArrowUp": {
+        case KeyType.ArrowUp: {
           if (
             date === undefined ||
             yearItemNumber === undefined ||
@@ -309,7 +310,7 @@ export default class Year extends Component<YearProps> {
           );
           break;
         }
-        case "ArrowDown": {
+        case KeyType.ArrowDown: {
           if (
             date === undefined ||
             yearItemNumber === undefined ||


Test patch:
diff --git a/test/date_utils_test.test.ts b/test/date_utils_test.test.ts
index ff4262a9..69bf837c 100644
--- a/test/date_utils_test.test.ts
+++ b/test/date_utils_test.test.ts
@@ -50,6 +50,7 @@ import {
   isDateBefore,
   getMidnightDate,
   registerLocale,
+  isMonthYearDisabled,
 } from "../src/date_utils";
 
 registerLocale("pt-BR", ptBR);
@@ -1356,4 +1357,51 @@ describe("date_utils", () => {
       }).toThrow();
     });
   });
+
+  describe("isMonthYearDisabled", () => {
+    const props = {
+      minDate: new Date(2023, 2, 1),
+      maxDate: new Date(2023, 11, 31),
+      excludeDates: [new Date(2023, 5, 1)],
+    };
+
+    it("should return true if month is disabled", () => {
+      const date = new Date(new Date(2023, 5, 1));
+      expect(isMonthYearDisabled(date, props)).toBe(true);
+    });
+
+    it("should return true if month is before the min date", () => {
+      const date = new Date(new Date(2023, 1, 1));
+      expect(isMonthYearDisabled(date, props)).toBe(true);
+    });
+
+    it("should return true if month is after the max date", () => {
+      const date = new Date(new Date(2024, 1, 1));
+      expect(isMonthYearDisabled(date, props)).toBe(true);
+    });
+
+    it("should return false if month is not disabled", () => {
+      const date = new Date(new Date(2023, 3, 1));
+      expect(isMonthYearDisabled(date, props)).toBe(false);
+    });
+
+    it("should return false if month is in include dates", () => {
+      const date = new Date(2024, 2, 1);
+      expect(
+        isMonthYearDisabled(date, { includeDates: [new Date(2024, 2, 1)] }),
+      ).toBe(false);
+    });
+
+    it("should return false if two dates have the same month but different years", () => {
+      const date = new Date(2021, 5, 1);
+      expect(
+        isMonthYearDisabled(date, { excludeDates: [new Date(2023, 5, 1)] }),
+      ).toBe(false);
+    });
+
+    it("should return false by default", () => {
+      const date = new Date(new Date(2023, 3, 1));
+      expect(isMonthYearDisabled(date)).toBe(false);
+    });
+  });
 });
diff --git a/test/datepicker_test.test.js b/test/datepicker_test.test.js
index b9608812..084b17af 100644
--- a/test/datepicker_test.test.js
+++ b/test/datepicker_test.test.js
@@ -1054,6 +1054,245 @@ describe("DatePicker", () => {
       utils.formatDate(data.instance.state.preSelection, data.testFormat),
     ).toBe(utils.formatDate(data.copyM, data.testFormat));
   });
+
+  // excluded stuff
+  it("should skip over excluded date when ArrowRight is pressed", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-05-02")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowRight"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-05-03"), data.testFormat));
+  });
+
+  it("should skip over excluded date when ArrowLeft is pressed", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-04-30")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowLeft"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-04-29"), data.testFormat));
+  });
+
+  it("should skip over excluded date when ArrowUp is pressed", () => {
+    const date = new Date("2024-05-22");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-05-15")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowUp"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-05-08"), data.testFormat));
+  });
+
+  it("should skip over excluded date when ArrowDown is pressed", () => {
+    const date = new Date("2024-05-08");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-05-15")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowDown"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-05-22"), data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to dates before minDate with ArrowLeft", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      minDate: date,
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowLeft"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to dates after maxDate with ArrowRight", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      maxDate: date,
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowRight"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to dates before minDate with PageUp and Home", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      minDate: date,
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageUp"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("Home"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
+  it("using keyboard, with PageUp pressed, should navigate to the date after the excluded date", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-04-01")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageUp"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-04-02"), data.testFormat));
+  });
+
+  it("using keyboard, with Home pressed, should navigate to the date after the excluded date", () => {
+    const date = new Date("2024-05-11");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-05-05")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("Home"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-05-06"), data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to dates after maxDate with PageDown and End", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      maxDate: date,
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageDown"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("End"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
+  it("using keyboard, with PageDown pressed, should navigate to the date before the excluded date", () => {
+    const date = new Date("2024-05-01");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-06-01")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageDown"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-05-31"), data.testFormat));
+  });
+
+  it("using keyboard, with End pressed, should navigate to the date before the excluded date", () => {
+    const date = new Date("2024-05-05");
+    const data = getOnInputKeyDownStuff({
+      excludeDates: [new Date("2024-05-11")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("End"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(new Date("2024-05-10"), data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to excluded date when excluded date and minDate are the same (edge case)", () => {
+    const date = new Date("2024-05-03");
+    const data = getOnInputKeyDownStuff({
+      minDate: new Date("2024-05-02"),
+      excludeDates: [new Date("2024-05-02")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageUp"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to excluded date when excluded date and maxDate are the same (edge case)", () => {
+    const date = new Date("2024-05-03");
+    const data = getOnInputKeyDownStuff({
+      maxDate: new Date("2024-05-04"),
+      excludeDates: [new Date("2024-05-04")],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageDown"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
+  it("using keyboard, should not navigate to any date when there are no dates enabled (edge case)", () => {
+    const date = new Date("2024-05-03");
+    const data = getOnInputKeyDownStuff({
+      minDate: date,
+      maxDate: date,
+      excludeDates: [date],
+      selected: date,
+      preSelection: date,
+    });
+
+    fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+    fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageDown"));
+    expect(
+      utils.formatDate(data.instance.state.preSelection, data.testFormat),
+    ).toBe(utils.formatDate(date, data.testFormat));
+  });
+
   it("should call onMonthChange when keyboard navigation moves preSelection to different month", () => {
     const onMonthChangeSpy = jest.fn();
     const opts = { onMonthChange: onMonthChangeSpy };
@@ -3102,4 +3341,188 @@ describe("DatePicker", () => {
       expect(onYearMouseLeaveSpy).toHaveBeenCalled();
     });
   });
+
+  describe("Week picker", () => {
+    describe("Keyboard navigation", () => {
+      it("should navigate to the previous week when pressing ArrowUp", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowUp"));
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-02-01"), data.testFormat));
+      });
+      it("should navigate to the previous week when pressing ArrowLeft", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("ArrowLeft"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-02-01"), data.testFormat));
+      });
+      it("should navigate to the next week when pressing ArrowDown", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("ArrowDown"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-02-15"), data.testFormat));
+      });
+      it("should navigate to the next week when pressing ArrowRight", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("ArrowRight"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-02-15"), data.testFormat));
+      });
+      it("should skip excluded week when pressing ArrowUp", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+          excludeDateIntervals: [
+            { start: new Date("2021-02-01"), end: new Date("2021-02-07") },
+          ],
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("ArrowUp"));
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-01-25"), data.testFormat));
+      });
+      it("should skip excluded week when pressing ArrowLeft", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+          excludeDateIntervals: [
+            { start: new Date("2021-02-01"), end: new Date("2021-02-07") },
+          ],
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("ArrowLeft"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-01-25"), data.testFormat));
+      });
+      it("should skip excluded week when pressing ArrowDown", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+          excludeDateIntervals: [
+            { start: new Date("2021-02-15"), end: new Date("2021-02-21") },
+          ],
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("ArrowDown"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-02-22"), data.testFormat));
+      });
+      it("should skip excluded week when pressing ArrowRight", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+          excludeDateIntervals: [
+            { start: new Date("2021-02-15"), end: new Date("2021-02-21") },
+          ],
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("ArrowRight"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-02-22"), data.testFormat));
+      });
+      it("should move to the next available week after pressing PageUp to a disabled date", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+          excludeDateIntervals: [
+            { start: new Date("2021-01-04"), end: new Date("2021-01-10") },
+          ],
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(getSelectedDayNode(data.instance), getKey("PageUp"));
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-01-15"), data.testFormat));
+      });
+      it("should move to the previous available week after pressing PageDown to a disabled date", () => {
+        const date = new Date("2021-02-08");
+        const data = getOnInputKeyDownStuff({
+          showWeekPicker: true,
+          selected: date,
+          preSelection: date,
+          excludeDateIntervals: [
+            { start: new Date("2021-03-08"), end: new Date("2021-03-14") },
+          ],
+        });
+
+        fireEvent.keyDown(data.dateInput, getKey("ArrowDown"));
+        fireEvent.keyDown(
+          getSelectedDayNode(data.instance),
+          getKey("PageDown"),
+        );
+        expect(
+          utils.formatDate(data.instance.state.preSelection, data.testFormat),
+        ).toBe(utils.formatDate(new Date("2021-03-01"), data.testFormat));
+      });
+    });
+  });
 });
diff --git a/test/month_test.test.js b/test/month_test.test.js
index 11b90232..c6958aec 100644
--- a/test/month_test.test.js
+++ b/test/month_test.test.js
@@ -1176,10 +1176,37 @@ describe("Month", () => {
     });
   });
 
-  describe("Keyboard navigation", () => {
+  describe("Quarters keyboard navigation", () => {
     const renderQuarters = (props) =>
       render(<Month showQuarterYearPicker {...props} />).container;
 
+    it("should select Q2 when pressing Enter", () => {
+      let preSelected = false;
+      const setPreSelection = (param) => {
+        preSelected = param;
+      };
+
+      const quartersComponent = renderQuarters({
+        selected: utils.newDate("2015-04-01"),
+        day: utils.newDate("2015-04-01"),
+        setPreSelection: setPreSelection,
+        preSelection: utils.newDate("2015-04-01"),
+      });
+      fireEvent.keyDown(
+        quartersComponent.querySelector(".react-datepicker__quarter-2"),
+        getKey("Tab"),
+      );
+
+      fireEvent.keyDown(
+        quartersComponent.querySelector(".react-datepicker__quarter-2"),
+        getKey("Enter"),
+      );
+
+      expect(preSelected.toString()).toBe(
+        utils.newDate("2015-04-01").toString(),
+      );
+    });
+
     it("should trigger setPreSelection and set Q3 as pre-selected on arrowRight", () => {
       let preSelected = false;
       const setPreSelection = (param) => {
@@ -2034,6 +2061,188 @@ describe("Month", () => {
         `Can't select this January 2015`,
       );
     });
+
+    describe("skip over excluded dates", () => {
+      it("should skip over excluded date when pressed arrow right", () => {
+        const excludeDates = [utils.newDate("2015-08-01")];
+        let preSelected = false;
+        const setPreSelection = (param) => {
+          preSelected = param;
+        };
+        const selected = utils.newDate("2015-07-01");
+
+        const monthComponent = renderMonth({
+          selected,
+          day: selected,
+          setPreSelection,
+          preSelection: selected,
+          excludeDates,
+        });
+
+        fireEvent.keyDown(
+          monthComponent.querySelector(".react-datepicker__month-6"),
+          getKey("ArrowRight"),
+        );
+
+        expect(preSelected.toString()).toBe(
+          utils.newDate("2015-09-01").toString(),
+        );
+      });
+
+      it("should skip over excluded date when pressed arrow left", () => {
+        const excludeDates = [utils.newDate("2015-08-01")];
+        let preSelected = false;
+        const setPreSelection = (param) => {
+          preSelected = param;
+        };
+        const selected = utils.newDate("2015-09-01");
+
+        const monthComponent = renderMonth({
+          selected,
+          day: selected,
+          setPreSelection,
+          preSelection: selected,
+          excludeDates,
+        });
+
+        fireEvent.keyDown(
+          monthComponent.querySelector(".react-datepicker__month-8"),
+          getKey("ArrowLeft"),
+        );
+
+        expect(preSelected.toString()).toBe(
+          utils.newDate("2015-07-01").toString(),
+        );
+      });
+
+      it("should skip over excluded date when pressed arrow up", () => {
+        const excludeDates = [utils.newDate("2015-08-01")];
+        let preSelected = false;
+        const setPreSelection = (param) => {
+          preSelected = param;
+        };
+        const selected = utils.newDate("2015-11-01");
+
+        const monthComponent = renderMonth({
+          selected,
+          day: selected,
+          setPreSelection,
+          preSelection: selected,
+          excludeDates,
+        });
+
+        fireEvent.keyDown(
+          monthComponent.querySelector(".react-datepicker__month-10"),
+          getKey("ArrowUp"),
+        );
+
+        expect(preSelected.toString()).toBe(
+          utils.newDate("2015-05-01").toString(),
+        );
+      });
+
+      it("should skip over excluded date when pressed arrow down", () => {
+        const excludeDates = [utils.newDate("2015-08-01")];
+        let preSelected = false;
+        const setPreSelection = (param) => {
+          preSelected = param;
+        };
+        const selected = utils.newDate("2015-05-01");
+
+        const monthComponent = renderMonth({
+          selected,
+          day: selected,
+          setPreSelection,
+          preSelection: selected,
+          excludeDates,
+        });
+
+        fireEvent.keyDown(
+          monthComponent.querySelector(".react-datepicker__month-4"),
+          getKey("ArrowDown"),
+        );
+
+        expect(preSelected.toString()).toBe(
+          utils.newDate("2015-11-01").toString(),
+        );
+      });
+
+      it("should not navigate to any date when all dates are disabled (edge case)", () => {
+        const date = utils.newDate("2015-05-01");
+        let preSelected = false;
+        const setPreSelection = (param) => {
+          preSelected = param;
+        };
+
+        const monthComponent = renderMonth({
+          selected: date,
+          day: date,
+          setPreSelection,
+          preSelection: date,
+          minDate: date,
+          maxDate: date,
+          excludeDates: [date],
+        });
+
+        fireEvent.keyDown(
+          monthComponent.querySelector(".react-datepicker__month-4"),
+          getKey("ArrowDown"),
+        );
+
+        expect(preSelected.toString()).toBe(
+          utils.newDate("2015-05-01").toString(),
+        );
+      });
+    });
+
+    it("should prevent navigation when cursor is next to minimum date and the left arrow is pressed", () => {
+      let preSelected = utils.newDate("2015-03-01");
+      const setPreSelection = (param) => {
+        preSelected = param;
+      };
+
+      const monthComponent = renderMonth({
+        selected: preSelected,
+        day: utils.newDate("2015-03-01"),
+        setPreSelection: setPreSelection,
+        preSelection: preSelected,
+        minDate: utils.newDate("2015-03-01"),
+      });
+
+      fireEvent.keyDown(
+        monthComponent.querySelector(".react-datepicker__month-2"),
+        getKey("ArrowLeft"),
+      );
+
+      expect(preSelected.toString()).toBe(
+        utils.newDate("2015-03-01").toString(),
+      );
+    });
+
+    it("should prevent navigation when cursor is next to maximum date and right arrow is pressed", () => {
+      let preSelected = utils.newDate("2015-08-01");
+      const setPreSelection = (param) => {
+        preSelected = param;
+      };
+
+      const monthComponent = renderMonth({
+        selected: utils.newDate("2015-08-01"),
+        day: utils.newDate("2015-08-01"),
+        setPreSelection: setPreSelection,
+        preSelection: preSelected,
+        minDate: utils.newDate("2015-03-01"),
+        maxDate: utils.newDate("2015-08-01"),
+      });
+
+      fireEvent.keyDown(
+        monthComponent.querySelector(".react-datepicker__month-8"),
+        getKey("ArrowRight"),
+      );
+
+      expect(preSelected.toString()).toBe(
+        utils.newDate("2015-08-01").toString(),
+      );
+    });
   });
 
   describe("if keyboard navigation is disabled", () => {