Repository: gpbl/react-day-picker
Pull request #2195 (2024-06-06 12:36:36)

style: improve selection modes types


Patch:
diff --git a/examples/Dialog.tsx b/examples/Dialog.tsx
index 7b6c64e32..a6004e56a 100644
--- a/examples/Dialog.tsx
+++ b/examples/Dialog.tsx
@@ -1,7 +1,7 @@
 import React, { useEffect, useId, useRef, useState } from "react";
 
 import { format, isValid, parse } from "date-fns";
-import { DayPicker } from "react-day-picker";
+import { DayPicker, type SelectHandler } from "react-day-picker";
 
 export function Dialog() {
   const dialogRef = useRef<HTMLDialogElement>(null);
@@ -45,7 +45,7 @@ export function Dialog() {
    * Function to handle the DayPicker select event: update the input value and
    * the selected date, and set the month.
    */
-  const handleDayPickerSelect = (date: Date) => {
+  const handleDayPickerSelect: SelectHandler<"single"> = (date) => {
     if (!date) {
       setInputValue("");
       setSelectedDate(undefined);
diff --git a/examples/RangeShiftKey.tsx b/examples/RangeShiftKey.tsx
index bc4cec326..dcec46679 100644
--- a/examples/RangeShiftKey.tsx
+++ b/examples/RangeShiftKey.tsx
@@ -31,7 +31,9 @@ function DayWithShiftKey(props: DayProps) {
 }
 
 export function RangeShiftKey() {
-  const [range, setRange] = React.useState<DateRange>({ from: undefined });
+  const [range, setRange] = React.useState<DateRange | undefined>({
+    from: undefined
+  });
 
   let footer = <p>Please pick a day.</p>;
 
diff --git a/src/DayPicker.tsx b/src/DayPicker.tsx
index d08f54264..2d3dcd12d 100644
--- a/src/DayPicker.tsx
+++ b/src/DayPicker.tsx
@@ -8,12 +8,15 @@ import type { DayPickerProps, Mode } from "./types";
  * DayPicker is a React component to create date pickers, calendars, and date
  * inputs for web applications.
  *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Components
  * @see http://daypicker.dev
  */
-export function DayPicker<T extends Mode = "default">(
-  props: DayPickerProps<T>
-) {
+export function DayPicker<
+  T extends Mode = "default",
+  R extends boolean = false
+>(props: DayPickerProps<T, R>) {
   return (
     <ContextProviders {...props}>
       <Calendar />
diff --git a/src/contexts/props.tsx b/src/contexts/props.tsx
index 026cece94..d8f57fd07 100644
--- a/src/contexts/props.tsx
+++ b/src/contexts/props.tsx
@@ -26,11 +26,16 @@ import type {
  * Holds the props passed to the DayPicker component, with some optional props
  * set to meaningful defaults.
  *
- * Access the Props context using the {@link useProps} hook.
+ * Access this context using the {@link useProps} hook.
  *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Contexts
  */
-export interface PropsContext<T extends Mode> extends PropsBase {
+export interface PropsContext<
+  T extends Mode = "default",
+  R extends boolean = false
+> extends PropsBase {
   classNames: ClassNames;
   /** The `data-*` attributes passed to `<DayPicker />`. */
   dataAttributes: DataAttributes;
@@ -46,14 +51,14 @@ export interface PropsContext<T extends Mode> extends PropsBase {
   toDate: Date | undefined;
   today: Date;
   /** The currently selected value. */
-  selected: Selected<Mode> | undefined;
+  selected: Selected<Mode, R> | undefined;
   /** The default selected value. */
-  defaultSelected: Selected<Mode> | undefined;
+  defaultSelected: Selected<Mode, R> | undefined;
   /** The function that handles the day selection. */
-  onSelect: SelectHandler<Mode> | undefined;
+  onSelect: SelectHandler<Mode, R> | undefined;
 }
 
-const propsContext = createContext<PropsContext<Mode> | null>(null);
+const propsContext = createContext<PropsContext<Mode, boolean> | null>(null);
 
 /**
  * Provide the props to the DayPicker components. Must be used as root of the
@@ -61,8 +66,8 @@ const propsContext = createContext<PropsContext<Mode> | null>(null);
  *
  * @private
  */
-export const PropsProvider = <T extends Mode>(
-  props: PropsWithChildren<DayPickerProps<T>>
+export const PropsProvider = <T extends Mode, R extends boolean>(
+  props: PropsWithChildren<DayPickerProps<T, R>>
 ) => {
   const reactId = useId();
   const { children, ...restProps } = props;
diff --git a/src/contexts/root.tsx b/src/contexts/root.tsx
index 5f18313ec..f33c4e7f8 100644
--- a/src/contexts/root.tsx
+++ b/src/contexts/root.tsx
@@ -15,8 +15,8 @@ import { SelectionProvider } from "./selection";
  * @private
  */
 export const ContextProviders: FunctionComponent<
-  PropsWithChildren<DayPickerProps<Mode>>
-> = <T extends Mode>(props: PropsWithChildren<DayPickerProps<T>>) => {
+  PropsWithChildren<DayPickerProps<Mode, boolean>>
+> = <T extends Mode>(props: PropsWithChildren<DayPickerProps<T, boolean>>) => {
   const { children, ...dayPickerProps } = props;
 
   return (
diff --git a/src/contexts/selection.tsx b/src/contexts/selection.tsx
index e5267825e..122df8116 100644
--- a/src/contexts/selection.tsx
+++ b/src/contexts/selection.tsx
@@ -20,11 +20,16 @@ import { useProps } from "./props";
  *
  * Access the selection context using the {@link useSelection} hook.
  *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Contexts
  */
-export interface SelectionContext<T extends Mode> {
+export interface SelectionContext<
+  T extends Mode = "default",
+  R extends boolean = false
+> {
   /** The currently selected value. */
-  selected: Selected<T> | undefined;
+  selected: Selected<T, R>;
   /** Set the selected days. */
   setSelected: (
     date: Date,
@@ -50,7 +55,7 @@ export interface SelectionContext<T extends Mode> {
   isMiddleOfRange: (date: Date) => boolean;
 }
 
-const contextValue: SelectionContext<Mode> = {
+const contextValue: SelectionContext<Mode, boolean> = {
   selected: undefined,
   setSelected: () => undefined,
   isSelected: () => false,
@@ -59,7 +64,8 @@ const contextValue: SelectionContext<Mode> = {
   isMiddleOfRange: () => false
 };
 
-const selectionContext = createContext<SelectionContext<Mode>>(contextValue);
+const selectionContext =
+  createContext<SelectionContext<Mode, boolean>>(contextValue);
 
 /** @private */
 export function SelectionProvider(providerProps: PropsWithChildren) {
@@ -112,11 +118,14 @@ export function SelectionProvider(providerProps: PropsWithChildren) {
         selected = value;
       } else {
         // Remove the date from the selection
-        selected = value?.filter((day) => !isSameDay(day, date)) || [];
+        selected =
+          value?.filter((day: Date | undefined) => {
+            return Boolean(day && date && !isSameDay(day, date));
+          }) || ([] as Date[]);
       }
     } else if (max !== undefined && value?.length === max) {
       // Max value reached, reset the selection to date
-      selected = [date];
+      selected = date ? [date] : [];
     } else {
       // Add the date to the selection
       selected = [...(value ?? []), date];
@@ -140,7 +149,7 @@ export function SelectionProvider(providerProps: PropsWithChildren) {
     if (value !== undefined && !isDateRange(value)) {
       return;
     }
-    const selected = addToRange(date, value);
+    const selected = date ? addToRange(date, value) : undefined;
 
     if (min) {
       if (
@@ -238,13 +247,18 @@ export function SelectionProvider(providerProps: PropsWithChildren) {
  *
  * Use this hook from the custom components passed via the `components` prop.
  *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Hooks
  * @see https://react-day-picker.js.org/advanced-guides/custom-components
  */
-export function useSelection<T extends Mode>() {
+export function useSelection<
+  T extends Mode = "default",
+  R extends boolean = false
+>() {
   const context = useContext(selectionContext);
   if (!context) {
     throw new Error(`useSelection must be used within a SelectionProvider.`);
   }
-  return context as SelectionContext<T>;
+  return context as SelectionContext<T, R>;
 }
diff --git a/src/helpers/getDates.ts b/src/helpers/getDates.ts
index cafe7ecd9..f0d0c9263 100644
--- a/src/helpers/getDates.ts
+++ b/src/helpers/getDates.ts
@@ -29,7 +29,7 @@ export function getDates(
   displayMonths: Date[],
   maxDate?: Date | undefined,
   options?: Pick<
-    DayPickerProps<Mode>,
+    DayPickerProps<Mode, boolean>,
     "ISOWeek" | "fixedWeeks" | "locale" | "weekStartsOn"
   >
 ): Date[] {
diff --git a/src/helpers/getDropdownMonths.ts b/src/helpers/getDropdownMonths.ts
index fdf8ed069..974de6fd7 100644
--- a/src/helpers/getDropdownMonths.ts
+++ b/src/helpers/getDropdownMonths.ts
@@ -9,7 +9,7 @@ import { Formatters, Mode } from "../types";
 
 /** Return the months to show in the dropdown. */
 export function getDropdownMonths(
-  props: Pick<PropsContext<Mode>, "fromDate" | "toDate" | "locale"> & {
+  props: Pick<PropsContext<Mode, boolean>, "fromDate" | "toDate" | "locale"> & {
     formatters: Pick<Formatters, "formatMonthDropdown">;
   }
 ): DropdownOption[] | undefined {
diff --git a/src/helpers/getDropdownYears.ts b/src/helpers/getDropdownYears.ts
index b90839817..ee97f9d1b 100644
--- a/src/helpers/getDropdownYears.ts
+++ b/src/helpers/getDropdownYears.ts
@@ -10,22 +10,19 @@ import type { Formatters, Mode } from "../types";
 
 /** Return the years to show in the dropdown. */
 export function getDropdownYears(
-  dayPicker: Pick<PropsContext<Mode>, "fromDate" | "toDate"> & {
+  props: Pick<PropsContext<Mode>, "fromDate" | "toDate"> & {
     formatters: Pick<Formatters, "formatYearDropdown">;
   }
 ): DropdownOption[] | undefined {
-  if (!dayPicker.fromDate) return undefined;
-  if (!dayPicker.toDate) return undefined;
-  const firstNavYear = startOfYear(dayPicker.fromDate);
-  const lastNavYear = endOfYear(dayPicker.toDate);
+  if (!props.fromDate) return undefined;
+  if (!props.toDate) return undefined;
+  const firstNavYear = startOfYear(props.fromDate);
+  const lastNavYear = endOfYear(props.toDate);
   const years: number[] = [];
   let year = firstNavYear;
   while (isBefore(year, lastNavYear) || isSameYear(year, lastNavYear)) {
     years.push(year.getFullYear());
     year = addYears(year, 1);
   }
-  return years.map((year) => [
-    year,
-    dayPicker.formatters.formatYearDropdown(year)
-  ]);
+  return years.map((year) => [year, props.formatters.formatYearDropdown(year)]);
 }
diff --git a/src/helpers/getFromToDate.ts b/src/helpers/getFromToDate.ts
index 5dde7181a..71feeb769 100644
--- a/src/helpers/getFromToDate.ts
+++ b/src/helpers/getFromToDate.ts
@@ -13,7 +13,7 @@ import type { DayPickerProps, Mode } from "../types";
  */
 export function getFromToDate(
   props: Pick<
-    DayPickerProps<Mode>,
+    DayPickerProps<Mode, boolean>,
     | "fromYear"
     | "toYear"
     | "fromDate"
@@ -23,7 +23,7 @@ export function getFromToDate(
     | "today"
     | "captionLayout"
   >
-): Pick<DayPickerProps<Mode>, "fromDate" | "toDate"> {
+): Pick<DayPickerProps<Mode, boolean>, "fromDate" | "toDate"> {
   const { fromYear, toYear, fromMonth, toMonth } = props;
   let { fromDate, toDate } = props;
   const hasDropdowns = props.captionLayout?.startsWith("dropdown");
diff --git a/src/helpers/getInitialMonth.ts b/src/helpers/getInitialMonth.ts
index de08298e4..e4befa598 100644
--- a/src/helpers/getInitialMonth.ts
+++ b/src/helpers/getInitialMonth.ts
@@ -4,7 +4,9 @@ import type { PropsContext } from "../contexts/props";
 import type { Mode } from "../types";
 
 /** Return the initial month according to the given options. */
-export function getInitialMonth(context: Partial<PropsContext<Mode>>): Date {
+export function getInitialMonth(
+  context: Partial<PropsContext<Mode, boolean>>
+): Date {
   const { month, defaultMonth, today } = context;
   let initialMonth = month || defaultMonth || today || new Date();
 
diff --git a/src/helpers/getMonths.ts b/src/helpers/getMonths.ts
index bc83ef003..704d8c8e2 100644
--- a/src/helpers/getMonths.ts
+++ b/src/helpers/getMonths.ts
@@ -17,7 +17,7 @@ export function getMonths(
   /** The dates to display in the calendar. */
   dates: Date[],
   options: Pick<
-    DayPickerProps<Mode>,
+    DayPickerProps<Mode, boolean>,
     | "fixedWeeks"
     | "ISOWeek"
     | "locale"
diff --git a/src/helpers/getNextFocus.tsx b/src/helpers/getNextFocus.tsx
index d463624af..bdc642326 100644
--- a/src/helpers/getNextFocus.tsx
+++ b/src/helpers/getNextFocus.tsx
@@ -9,7 +9,7 @@ import { dateMatchModifiers } from "../utils/dateMatchModifiers";
 import { getPossibleFocusDate } from "./getPossibleFocusDate";
 
 export type Options = Pick<
-  PropsContext<Mode>,
+  PropsContext<Mode, boolean>,
   "modifiers" | "locale" | "ISOWeek" | "weekStartsOn" | "fromDate" | "toDate"
 >;
 
@@ -19,7 +19,7 @@ export function getNextFocus(
   /** The date that is currently focused. */
   focused: CalendarDay,
   options: Pick<
-    PropsContext<Mode>,
+    PropsContext<Mode, boolean>,
     | "disabled"
     | "hidden"
     | "modifiers"
diff --git a/src/helpers/getPossibleFocusDate.ts b/src/helpers/getPossibleFocusDate.ts
index 943d8034d..38deb90ad 100644
--- a/src/helpers/getPossibleFocusDate.ts
+++ b/src/helpers/getPossibleFocusDate.ts
@@ -19,7 +19,7 @@ export function getPossibleFocusDate(
   moveDir: MoveFocusDir,
   focusedDate: Date,
   options: Pick<
-    PropsContext<Mode>,
+    PropsContext<Mode, boolean>,
     "locale" | "ISOWeek" | "weekStartsOn" | "fromDate" | "toDate"
   >
 ): Date {
diff --git a/src/helpers/useControlledValue.ts b/src/helpers/useControlledValue.ts
index 655ae9e05..0e8845b53 100644
--- a/src/helpers/useControlledValue.ts
+++ b/src/helpers/useControlledValue.ts
@@ -11,6 +11,8 @@ export type DispatchStateAction<T> = React.Dispatch<React.SetStateAction<T>>;
  *
  * If the value is controlled, pass the controlled value as the second argument,
  * which will always be returned as `value`.
+ *
+ * @template T - The type of the value.
  */
 export function useControlledValue<T>(
   defaultValue: T,
diff --git a/src/types-deprecated.ts b/src/types-deprecated.ts
index b04e6c2ba..553db6578 100644
--- a/src/types-deprecated.ts
+++ b/src/types-deprecated.ts
@@ -76,19 +76,19 @@ export type RowProps = WeekProps;
  * @deprecated This type has been renamed. Use `PropsSingle` instead.
  * @protected
  */
-export type DayPickerSingleProps = PropsSingle;
+export type DayPickerSingleProps = PropsSingle<boolean>;
 
 /**
  * @deprecated This type has been renamed. Use `PropsMulti` instead.
  * @protected
  */
-export type DayPickerMultipleProps = PropsMulti;
+export type DayPickerMultipleProps = PropsMulti<boolean>;
 
 /**
  * @deprecated This type has been renamed. Use `PropsRange` instead.
  * @protected
  */
-export type DayPickerRangeProps = PropsRange;
+export type DayPickerRangeProps = PropsRange<boolean>;
 
 /**
  * @deprecated This type will be removed.
@@ -112,20 +112,20 @@ export type Modifier = string;
  * @deprecated This type will be removed. Use `SelectHandler<"single">` instead.
  * @protected
  */
-export type SelectSingleEventHandler = SelectHandler<"single">;
+export type SelectSingleEventHandler = SelectHandler<"single", false>;
 
 /**
  * @deprecated This type will be removed. Use `SelectHandler<"multiple">`
  *   instead.
  * @protected
  */
-export type SelectMultipleEventHandler = SelectHandler<"multiple">;
+export type SelectMultipleEventHandler = SelectHandler<"multiple", false>;
 
 /**
  * @deprecated This type will be removed. Use `SelectHandler<"range">` instead.
  * @protected
  */
-export type SelectRangeEventHandler = SelectHandler<"range">;
+export type SelectRangeEventHandler = SelectHandler<"range", false>;
 
 /**
  * @deprecated This type is not used anymore.
@@ -236,4 +236,4 @@ export type DayTouchEventHandler = DayEventHandler<React.TouchEvent>;
  *   `PropsContext` instead.
  * @protected
  */
-export type DayPickerContext = PropsContext<Mode>;
+export type DayPickerContext = PropsContext;
diff --git a/src/types.ts b/src/types.ts
index 8d7cd5a8e..8e36b732b 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -44,14 +44,25 @@ import {
 /**
  * The props for the {@link DayPicker} component.
  *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Props
  */
-export type DayPickerProps<T extends Mode> = PropsBase & {
+export type DayPickerProps<
+  T extends Mode = "default",
+  R extends boolean = false
+> = PropsBase & {
+  /** The selection mode. */
   mode?: T | undefined;
+  /** Makes the selection required. */
+  required?: R | undefined;
 } & {
-    single: PropsSingle;
-    multiple: PropsMulti;
-    range: PropsRange;
+    /** Props for the single selection mode. */
+    single: PropsSingle<R>;
+    /** Props for the multi selection mode. */
+    multiple: PropsMulti<R>;
+    /** Props for the range selection mode. */
+    range: PropsRange<R>;
     default: object;
   }[T];
 
@@ -63,6 +74,8 @@ export type DayPickerProps<T extends Mode> = PropsBase & {
  * - `range`: use DayPicker to select a range of days
  * - `default`: disable any built-in selection behavior. Customize what is
  *   selected by using `onDayClick` and `modifiers`.
+ *
+ * @see https://react-day-picker.js.org/next/using-daypicker/selection-modes
  */
 export type Mode = "single" | "multiple" | "range" | "default";
 
@@ -72,7 +85,7 @@ export type Mode = "single" | "multiple" | "range" | "default";
  * @group Props
  */
 export interface PropsBase {
-  /** The CSS class to add to the container element. */
+  /** Class name to add to the root element */
   className?: string;
   /**
    * Change the class names used by DayPicker.
@@ -83,7 +96,7 @@ export interface PropsBase {
   classNames?: Partial<ClassNames>;
   /** Change the class name for the day matching the `modifiers`. */
   modifiersClassNames?: ModifiersClassNames;
-  /** Style to apply to the container element. */
+  /** Style to apply to the root element. */
   style?: CSSProperties;
   /** Change the inline styles of the HTML elements. */
   styles?: Partial<Styles>;
@@ -318,57 +331,56 @@ export interface PropsBase {
 /**
  * Props for the single selection mode, when `mode="single"`.
  *
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Props
  */
-export interface PropsSingle {
+export interface PropsSingle<R extends boolean = false> {
   /** Makes the selection required. */
-  required?: boolean;
+  required?: R | undefined;
   /** The selected Date. */
-  selected?: Selected<"single"> | undefined;
+  selected?: Selected<"single", R>;
   /** The initially selected value when not controlled. */
-  defaultSelected?: Selected<"single"> | undefined;
+  defaultSelected?: Selected<"single", R>;
   /** The callback called when the user selects a day. */
-  onSelect?: SelectHandler<"single"> | undefined;
+  onSelect?: SelectHandler<"single", R> | undefined;
 }
 
 /**
  * Props for the multi selection mode, when `mode="multiple"`.
  *
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Props
  */
-export interface PropsMulti {
+export interface PropsMulti<R extends boolean = false> {
   /** The selected dates. */
-  selected?: Selected<"multiple"> | undefined;
+  selected?: Selected<"multiple", R>;
   /** The initially selected values when not controlled. */
-  defaultSelected?: Selected<"multiple"> | undefined;
+  defaultSelected?: Selected<"multiple", R>;
   /** The callback called when the user selects a day. */
-  onSelect?: SelectHandler<"multiple"> | undefined;
-  /**
-   * Makes the selection required.
-   *
-   * @deprecated Use `min={1}` instead
-   */
-  required?: boolean;
+  onSelect?: SelectHandler<"multiple", R>;
+  /** Makes the selection required. */
+  required?: R | undefined;
   /** The minimum number of days that can be selected. */
-  min?: number;
+  min?: number | undefined;
   /** The maximum number of days that can be selected. */
-  max?: number;
+  max?: number | undefined;
 }
 
 /**
  * Props for the range selection mode, when `mode="range"`.
  *
+ * @template R - Whether the selection is required. Defaults to `false`.
  * @group Props
  */
-export interface PropsRange {
+export interface PropsRange<R extends boolean = false> {
   /** The selected range. */
-  selected?: Selected<"range"> | undefined;
+  selected?: Selected<"range", R>;
   /** The initially selected range when not controlled. */
-  defaultSelected?: Selected<"range"> | undefined;
+  defaultSelected?: Selected<"range", R>;
   /** The callback called when the user selects a day. */
-  onSelect?: SelectHandler<"range"> | undefined;
+  onSelect?: SelectHandler<"range", R>;
   /** Makes the selection required. */
-  required?: boolean;
+  required?: R;
   /** The minimum number of days that can be selected. */
   min?: number;
   /** The maximum number of days that can be selected. */
@@ -383,19 +395,35 @@ export interface PropsDefault extends PropsBase {
   mode?: undefined | "default";
 }
 
-/** The selected value when in selection mode. */
-export type Selected<T extends Mode> = T extends "single"
-  ? Date
+/**
+ * The selected value when in selection mode.
+ *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
+ */
+export type Selected<
+  T extends Mode = "default",
+  R extends boolean = false
+> = T extends "single"
+  ? Date | (R extends true ? Date : undefined)
   : T extends "multiple"
-    ? Date[]
+    ? Date[] | (R extends true ? Date[] : undefined)
     : T extends "range"
-      ? DateRange
+      ? DateRange | (R extends true ? DateRange : undefined)
       : undefined;
 
-/** The callback called when the user select a days from the calendar. */
-export type SelectHandler<T extends Mode> = (
+/**
+ * The callback called when the user select a days from the calendar. *
+ *
+ * @template T - The {@link Mode | selection mode}. Defaults to `"default"`.
+ * @template R - Whether the selection is required. Defaults to `false`.
+ */
+export type SelectHandler<
+  T extends Mode = "default",
+  R extends boolean = false
+> = (
   /** The new selected value. */
-  selected: Selected<T>,
+  selected: Selected<T, R>,
   /** The date that triggered the selection. */
   date: Date,
   /** The modifiers for the day that triggered the selection. */
@@ -462,10 +490,6 @@ export type Labels = {
 /**
  * A value or a function that matches a specific day.
  *
- * Matchers are passed to DayPicker via {@link PropsBase.disabled},
- * {@link PropsBase.hidden]] or [[DayPickerProps.selected} and are used to
- * determine if a day should get a {@link Modifier}.
- *
  * Matchers can be of different types:
  *
  * ```tsx
@@ -553,7 +577,11 @@ export type DayOfWeek = { dayOfWeek: number[] };
 /** A record with `data-*` attributes passed to `<DayPicker />`. */
 export type DataAttributes = Record<`data-${string}`, unknown>;
 
-/** The event handler triggered when interacting with a day. */
+/**
+ * The event handler triggered when interacting with a day.
+ *
+ * @template T - The event type that triggered the day event.
+ */
 export type DayEventHandler<T> = (
   /** The date that has triggered the event. */
   date: Date,
@@ -581,17 +609,17 @@ export type Styles = {
   [uiElement in UI]: CSSProperties | undefined;
 };
 
-/** Maps {@link UI} elements to their class names. */
+/**
+ * Maps {@link UI}, {@link DayModifier}, {@link CalendarFlag}, {@link ChevronFlag},
+ * and {@link WeekNumberFlag} elements to their class names.
+ */
 export type ClassNames = {
-  [element in UI]: string;
-} & {
-  [state in DayModifier]: string;
-} & {
-  [flag in CalendarFlag]: string;
-} & {
-  [flag in ChevronFlag]: string;
-} & {
-  [flag in WeekNumberFlag]: string;
+  [key in
+    | UI
+    | DayModifier
+    | CalendarFlag
+    | ChevronFlag
+    | WeekNumberFlag]: string;
 };
 
 /** The modifiers that are internally used by DayPicker. */


Test patch:
diff --git a/src/helpers/getNextFocus.test.tsx b/src/helpers/getNextFocus.test.tsx
index 111a374df..b1254d58b 100644
--- a/src/helpers/getNextFocus.test.tsx
+++ b/src/helpers/getNextFocus.test.tsx
@@ -8,7 +8,7 @@ import type { Mode } from "../types";
 import { getNextFocus } from "./getNextFocus";
 
 const defaultDayPicker: Pick<
-  PropsContext<Mode>,
+  PropsContext<Mode, boolean>,
   "disabled" | "hidden" | "fromDate" | "toDate"
 > = {
   disabled: [],
diff --git a/src/helpers/getPossibleFocusDate.test.ts b/src/helpers/getPossibleFocusDate.test.ts
index 6e3296de3..b5a7d03c1 100644
--- a/src/helpers/getPossibleFocusDate.test.ts
+++ b/src/helpers/getPossibleFocusDate.test.ts
@@ -19,7 +19,7 @@ import { getPossibleFocusDate } from "./getPossibleFocusDate";
 
 const baseDate = new Date(2023, 0, 1); // Jan 1, 2023
 const options: Pick<
-  PropsContext<Mode>,
+  PropsContext<Mode, boolean>,
   "locale" | "ISOWeek" | "weekStartsOn" | "fromDate" | "toDate"
 > = {
   locale: undefined,