Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/allow-undefined-classnames.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"react-day-picker": patch
"@daypicker/react": patch
---

fix: allow `undefined` in `ClassNames` values so `classNames` type-checks under `exactOptionalPropertyTypes`.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ export function getClassNamesForModifiers(
(previousValue, [key]) => {
if (modifiersClassNames[key]) {
previousValue.push(modifiersClassNames[key as string]);
} else if (classNames[DayFlag[key as DayFlag]]) {
previousValue.push(classNames[DayFlag[key as DayFlag]]);
} else if (classNames[SelectionState[key as SelectionState]]) {
previousValue.push(classNames[SelectionState[key as SelectionState]]);
} else {
const dayFlagClassName = classNames[DayFlag[key as DayFlag]];
if (dayFlagClassName) {
previousValue.push(dayFlagClassName);
} else {
const selectionClassName =
classNames[SelectionState[key as SelectionState]];
if (selectionClassName) {
previousValue.push(selectionClassName);
}
}
}
return previousValue;
},
Expand Down
8 changes: 7 additions & 1 deletion packages/react-day-picker/src/types/props.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import type { useDayPicker } from "../useDayPicker";

import type { DayPickerProps } from "./props";
import type { DateRange, Matcher } from "./shared";
import type { ClassNames, DateRange, Matcher } from "./shared";

// Keep public class name outputs assignable to string for existing consumers.
void (getDefaultClassNames().root satisfies string);

Check failure on line 11 in packages/react-day-picker/src/types/props.test.tsx

View workflow job for this annotation

GitHub Actions / typecheck

Type 'string | undefined' does not satisfy the expected type 'string'.

type ContextRootClass = ReturnType<typeof useDayPicker>["classNames"]["root"];
void (true satisfies ContextRootClass extends string ? true : false);

Check failure on line 14 in packages/react-day-picker/src/types/props.test.tsx

View workflow job for this annotation

GitHub Actions / typecheck

Type 'true' does not satisfy the expected type 'false'.

type PlainDateLike = {
year: number;
Expand All @@ -27,6 +27,12 @@
getDate(): number;
};

// ClassNames values must include `undefined` so `Partial<ClassNames>` accepts
// `string | undefined` under `exactOptionalPropertyTypes` (issue #3015).
void (true satisfies undefined extends ClassNames[keyof ClassNames]
? true
: false);

const date = new Date(2024, 0, 15);
const month = new Date(2024, 0, 1);
const endMonth = new Date(2024, 11, 1);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-day-picker/src/types/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export type MonthChangeEventHandler = (month: Date) => void;
* };
*/
export type ClassNames = {
[key in UI | SelectionState | DayFlag | Animation]: string;
[key in UI | SelectionState | DayFlag | Animation]: string | undefined;
};

/**
Expand Down
34 changes: 18 additions & 16 deletions packages/react-day-picker/src/useAnimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ export function useAnimation(

// remove animation classes from the new month snapshots
const captionEl = queryCaptionEl(currentMonthElSnapshot);
if (captionEl) {
if (captionEl && captionAnimationClass) {
captionEl.classList.remove(captionAnimationClass);
}

const weeksEl = queryWeeksEl(currentMonthElSnapshot);
if (weeksEl) {
if (weeksEl && weeksAnimationClass) {
weeksEl.classList.remove(weeksAnimationClass);
}
});
Expand Down Expand Up @@ -172,12 +172,12 @@ export function useAnimation(
currentMonthEl.style.position = "relative";
currentMonthEl.style.overflow = "hidden";
const captionEl = queryCaptionEl(currentMonthEl);
if (captionEl) {
if (captionEl && captionAnimationClass) {
captionEl.classList.add(captionAnimationClass);
}

const weeksEl = queryWeeksEl(currentMonthEl);
if (weeksEl) {
if (weeksEl && weeksAnimationClass) {
weeksEl.classList.add(weeksAnimationClass);
}
// animate new displayed month end
Expand All @@ -192,10 +192,10 @@ export function useAnimation(
navEl.style.zIndex = "";
}

if (captionEl) {
if (captionEl && captionAnimationClass) {
captionEl.classList.remove(captionAnimationClass);
}
if (weeksEl) {
if (weeksEl && weeksAnimationClass) {
weeksEl.classList.remove(weeksAnimationClass);
}
currentMonthEl.style.position = "";
Expand All @@ -220,21 +220,23 @@ export function useAnimation(

const previousCaptionEl = queryCaptionEl(previousMonthEl);
if (previousCaptionEl) {
previousCaptionEl.classList.add(
isAfterPreviousMonth
? classNames[Animation.caption_before_exit]
: classNames[Animation.caption_after_exit],
);
const captionExitClass = isAfterPreviousMonth
? classNames[Animation.caption_before_exit]
: classNames[Animation.caption_after_exit];
if (captionExitClass) {
previousCaptionEl.classList.add(captionExitClass);
}
previousCaptionEl.addEventListener("animationend", cleanUp);
}

const previousWeeksEl = queryWeeksEl(previousMonthEl);
if (previousWeeksEl) {
previousWeeksEl.classList.add(
isAfterPreviousMonth
? classNames[Animation.weeks_before_exit]
: classNames[Animation.weeks_after_exit],
);
const weeksExitClass = isAfterPreviousMonth
? classNames[Animation.weeks_before_exit]
: classNames[Animation.weeks_after_exit];
if (weeksExitClass) {
previousWeeksEl.classList.add(weeksExitClass);
}
}

currentMonthEl.insertBefore(previousMonthEl, currentMonthEl.firstChild);
Expand Down
Loading