Skip to content

Commit 496cb6e

Browse files
committed
Enhance inline DatePicker component with disabled state support
- Added a `disabled` prop to the DatePicker, affecting all date selection components (Day, Month, Year, etc.) to visually indicate disabled states. - Updated styles to reflect disabled states, including cursor and color changes. - Modified utility functions to check for disabled dates. - Ensured that all date-related components respect the disabled state, preventing user interaction when disabled. - Updated tests to verify the disabled functionality across various scenarios. This change improves accessibility and user experience by clearly indicating when the DatePicker is not interactive. Closes #5620
1 parent 55fe987 commit 496cb6e

File tree

9 files changed

+181
-15
lines changed

9 files changed

+181
-15
lines changed

docs-site/src/components/Examples/index.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import MonthDropdownShort from "../../examples/monthDropdownShort?raw";
6161
import MonthYearDropdown from "../../examples/monthYearDropdown?raw";
6262
import YearSelectDropdown from "../../examples/yearSelectDropdown?raw";
6363
import Inline from "../../examples/inline?raw";
64+
import InlineDisabled from "../../examples/disabledInline?raw";
6465
import InlineVisible from "../../examples/inlineVisible?raw";
6566
import OpenToDate from "../../examples/openToDate?raw";
6667
import FixedCalendar from "../../examples/fixedCalendar?raw";
@@ -360,6 +361,10 @@ export default class exampleComponents extends React.Component {
360361
title: "Inline version",
361362
component: Inline,
362363
},
364+
{
365+
title: "Disabled Inline version",
366+
component: InlineDisabled,
367+
},
363368
{
364369
title: "Button to show Inline version",
365370
component: InlineVisible,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
() => {
2+
const [selectedDate, setSelectedDate] = useState(new Date());
3+
return (
4+
<DatePicker
5+
selected={selectedDate}
6+
onChange={(date) => setSelectedDate(date)}
7+
inline
8+
disabled
9+
/>
10+
);
11+
};

src/calendar.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
468468
};
469469

470470
header = (date: Date = this.state.date): React.ReactElement[] => {
471+
const disabled = this.props.disabled;
471472
const startOfWeek = getStartOfWeek(
472473
date,
473474
this.props.locale,
@@ -477,7 +478,11 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
477478
const dayNames: React.ReactElement[] = [];
478479
if (this.props.showWeekNumbers) {
479480
dayNames.push(
480-
<div key="W" className="react-datepicker__day-name" role="columnheader">
481+
<div
482+
key="W"
483+
className={`react-datepicker__day-name ${disabled ? "react-datepicker__day-name--disabled" : ""}`}
484+
role="columnheader"
485+
>
481486
<span className="react-datepicker__sr-only">Week number</span>
482487
<span aria-hidden="true">{this.props.weekLabel || "#"}</span>
483488
</div>,
@@ -496,7 +501,11 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
496501
<div
497502
key={offset}
498503
role="columnheader"
499-
className={clsx("react-datepicker__day-name", weekDayClassName)}
504+
className={clsx(
505+
"react-datepicker__day-name",
506+
weekDayClassName,
507+
disabled ? "react-datepicker__day-name--disabled" : "",
508+
)}
500509
>
501510
<span className="react-datepicker__sr-only">
502511
{formatDate(day, "EEEE", this.props.locale)}
@@ -551,6 +560,9 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
551560

552561
let allPrevDaysDisabled;
553562
switch (true) {
563+
case this.props.disabled:
564+
allPrevDaysDisabled = true;
565+
break;
554566
case this.props.showMonthYearPicker:
555567
allPrevDaysDisabled = yearDisabledBefore(this.state.date, this.props);
556568
break;
@@ -662,6 +674,9 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
662674

663675
let allNextDaysDisabled: boolean;
664676
switch (true) {
677+
case this.props.disabled:
678+
allNextDaysDisabled = true;
679+
break;
665680
case this.props.showMonthYearPicker:
666681
allNextDaysDisabled = yearDisabledAfter(this.state.date, this.props);
667682
break;

src/date_utils.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,10 @@ export interface DateFilterOptions {
728728
yearItemNumber?: number;
729729
}
730730

731+
export type DateFilterOptionsWithDisabled = DateFilterOptions & {
732+
disabled?: boolean;
733+
};
734+
731735
/**
732736
* Checks if a day is disabled.
733737
*
@@ -745,8 +749,13 @@ export function isDayDisabled(
745749
includeDates,
746750
includeDateIntervals,
747751
filterDate,
748-
}: DateFilterOptions = {},
752+
disabled,
753+
}: DateFilterOptionsWithDisabled = {},
749754
): boolean {
755+
if (disabled) {
756+
return true;
757+
}
758+
750759
return (
751760
isOutOfBounds(day, { minDate, maxDate }) ||
752761
(excludeDates &&
@@ -898,11 +907,21 @@ export function isQuarterDisabled(
898907
excludeDates,
899908
includeDates,
900909
filterDate,
910+
disabled,
901911
}: Pick<
902-
DateFilterOptions,
903-
"minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate"
912+
DateFilterOptionsWithDisabled,
913+
| "minDate"
914+
| "maxDate"
915+
| "excludeDates"
916+
| "includeDates"
917+
| "filterDate"
918+
| "disabled"
904919
> = {},
905920
): boolean {
921+
if (disabled) {
922+
return true;
923+
}
924+
906925
return (
907926
isOutOfBounds(quarter, { minDate, maxDate }) ||
908927
excludeDates?.some((excludeDate) =>
@@ -941,11 +960,21 @@ export function isYearDisabled(
941960
excludeDates,
942961
includeDates,
943962
filterDate,
963+
disabled,
944964
}: Pick<
945-
DateFilterOptions,
946-
"minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate"
965+
DateFilterOptionsWithDisabled,
966+
| "minDate"
967+
| "maxDate"
968+
| "excludeDates"
969+
| "includeDates"
970+
| "filterDate"
971+
| "disabled"
947972
> = {},
948973
): boolean {
974+
if (disabled) {
975+
return true;
976+
}
977+
949978
const date = new Date(year, 0, 1);
950979
return (
951980
isOutOfBounds(date, {

src/day.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
getDayOfWeekCode,
1717
getStartOfWeek,
1818
formatDate,
19-
type DateFilterOptions,
19+
type DateFilterOptionsWithDisabled,
2020
type DateNumberType,
2121
type Locale,
2222
type HolidaysMap,
@@ -25,14 +25,15 @@ import {
2525

2626
interface DayProps
2727
extends Pick<
28-
DateFilterOptions,
28+
DateFilterOptionsWithDisabled,
2929
| "minDate"
3030
| "maxDate"
3131
| "excludeDates"
3232
| "excludeDateIntervals"
3333
| "includeDateIntervals"
3434
| "includeDates"
3535
| "filterDate"
36+
| "disabled"
3637
> {
3738
ariaLabelPrefixWhenEnabled?: string;
3839
ariaLabelPrefixWhenDisabled?: string;
@@ -210,6 +211,7 @@ export default class Day extends Component<DayProps> {
210211
includeDateIntervals: this.props.includeDateIntervals,
211212
includeDates: this.props.includeDates,
212213
filterDate: this.props.filterDate,
214+
disabled: this.props.disabled,
213215
});
214216

215217
isExcluded = () =>

src/month.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export default class Month extends Component<MonthProps> {
250250
includeDateIntervals: this.props.includeDateIntervals,
251251
includeDates: this.props.includeDates,
252252
filterDate: this.props.filterDate,
253+
disabled: this.props.disabled,
253254
});
254255

255256
isExcluded = (day: Date) =>
@@ -783,8 +784,17 @@ export default class Month extends Component<MonthProps> {
783784
isDisabled: boolean;
784785
labelDate: Date;
785786
} => {
786-
const { day, minDate, maxDate, excludeDates, includeDates } = this.props;
787+
const { day, disabled, minDate, maxDate, excludeDates, includeDates } =
788+
this.props;
787789
const labelDate = setMonth(day, month);
790+
791+
if (disabled) {
792+
return {
793+
isDisabled: true,
794+
labelDate: setMonth(day, month),
795+
};
796+
}
797+
788798
return {
789799
isDisabled:
790800
((minDate || maxDate || excludeDates || includeDates) &&
@@ -919,10 +929,16 @@ export default class Month extends Component<MonthProps> {
919929
filterDate,
920930
preSelection,
921931
disabledKeyboardNavigation,
932+
disabled,
922933
} = this.props;
923934

924935
const isDisabled =
925-
(minDate || maxDate || excludeDates || includeDates || filterDate) &&
936+
(minDate ||
937+
maxDate ||
938+
excludeDates ||
939+
includeDates ||
940+
filterDate ||
941+
disabled) &&
926942
isQuarterDisabled(setQuarter(day, q), this.props);
927943

928944
return clsx(

src/stylesheets/datepicker.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ h2.react-datepicker__current-month {
399399
line-height: $datepicker__item-size;
400400
text-align: center;
401401
margin: $datepicker__day-margin;
402+
403+
&--disabled {
404+
cursor: default;
405+
color: $datepicker__muted-color;
406+
}
402407
}
403408

404409
.react-datepicker__day,

src/test/datepicker_test.test.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4883,4 +4883,76 @@ describe("DatePicker", () => {
48834883
expect(eventObject.target?.value).toBe(inputValue);
48844884
});
48854885
});
4886+
4887+
describe("disabled", () => {
4888+
const validateAllDisabled = (container: HTMLElement, className: string) => {
4889+
const allDays = Array.from(container.querySelectorAll(`.${className}`));
4890+
expect(allDays.length).toBeGreaterThan(0);
4891+
expect(
4892+
allDays.every((day) =>
4893+
day.classList.contains(`${className}--disabled`),
4894+
),
4895+
).toBe(true);
4896+
};
4897+
4898+
const validateNonExistence = (
4899+
container: HTMLElement,
4900+
querySelector: string,
4901+
) => {
4902+
const element = container.querySelector(querySelector);
4903+
expect(element).toBeFalsy();
4904+
};
4905+
4906+
it("should disable all days and headers in DatePicker when disabled prop is true", () => {
4907+
const { container } = render(<DatePicker inline disabled />);
4908+
4909+
validateAllDisabled(container, "react-datepicker__day");
4910+
validateAllDisabled(container, "react-datepicker__day-name");
4911+
validateNonExistence(container, "react-datepicker__navigation");
4912+
});
4913+
4914+
it("should disable all days and headers in DatePicker Range Selector when disabled prop is true", () => {
4915+
const { container } = render(<DatePicker selectsRange inline disabled />);
4916+
4917+
validateAllDisabled(container, "react-datepicker__day");
4918+
validateAllDisabled(container, "react-datepicker__day-name");
4919+
validateNonExistence(container, "react-datepicker__navigation");
4920+
});
4921+
4922+
it("should disable all days and headers in MonthPicker when disabled prop is true", () => {
4923+
const { container } = render(
4924+
<DatePicker showMonthYearPicker inline disabled />,
4925+
);
4926+
4927+
validateAllDisabled(container, "react-datepicker__month-text");
4928+
validateNonExistence(container, "react-datepicker__navigation");
4929+
});
4930+
4931+
it("should disable all days and headers in YearPicker when disabled prop is true", () => {
4932+
const { container } = render(
4933+
<DatePicker showYearPicker inline disabled />,
4934+
);
4935+
4936+
validateAllDisabled(container, "react-datepicker__year-text");
4937+
validateNonExistence(container, "react-datepicker__navigation");
4938+
});
4939+
4940+
it("should disable all days and headers in WeekPicker when disabled prop is true", () => {
4941+
const { container } = render(
4942+
<DatePicker showWeekNumbers showWeekPicker inline disabled />,
4943+
);
4944+
4945+
validateAllDisabled(container, "react-datepicker__day");
4946+
validateNonExistence(container, "react-datepicker__navigation");
4947+
});
4948+
4949+
it("should disable all days and headers in QuarterPicker when disabled prop is true", () => {
4950+
const { container } = render(
4951+
<DatePicker showQuarterYearPicker inline disabled />,
4952+
);
4953+
4954+
validateAllDisabled(container, "react-datepicker__quarter-text");
4955+
validateNonExistence(container, "react-datepicker__navigation");
4956+
});
4957+
});
48864958
});

src/year.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { clsx } from "clsx";
22
import React, { Component, createRef } from "react";
33

44
import {
5-
type DateFilterOptions,
5+
type DateFilterOptionsWithDisabled,
66
addYears,
77
getStartOfYear,
88
getYear,
@@ -24,8 +24,13 @@ const VERTICAL_NAVIGATION_OFFSET = 3;
2424

2525
interface YearProps
2626
extends Pick<
27-
DateFilterOptions,
28-
"minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate"
27+
DateFilterOptionsWithDisabled,
28+
| "minDate"
29+
| "maxDate"
30+
| "excludeDates"
31+
| "includeDates"
32+
| "filterDate"
33+
| "disabled"
2934
> {
3035
clearSelectingDate?: VoidFunction;
3136
date?: Date;
@@ -381,6 +386,7 @@ export default class Year extends Component<YearProps> {
381386
getYearClassNames = (y: number) => {
382387
const {
383388
date,
389+
disabled,
384390
minDate,
385391
maxDate,
386392
excludeDates,
@@ -396,7 +402,12 @@ export default class Year extends Component<YearProps> {
396402
{
397403
"react-datepicker__year-text--selected": this.isSelectedYear(y),
398404
"react-datepicker__year-text--disabled":
399-
(minDate || maxDate || excludeDates || includeDates || filterDate) &&
405+
(minDate ||
406+
maxDate ||
407+
excludeDates ||
408+
includeDates ||
409+
filterDate ||
410+
disabled) &&
400411
isYearDisabled(y, this.props),
401412
"react-datepicker__year-text--keyboard-selected":
402413
this.isKeyboardSelected(y),

0 commit comments

Comments
 (0)