Skip to content

Commit 8dbe933

Browse files
committed
[Fix 🐛] #24 Disable specific days with disabledDates
1 parent e02bb63 commit 8dbe933

File tree

4 files changed

+159
-78
lines changed

4 files changed

+159
-78
lines changed

src/components/Calendar/Days.tsx

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, { useCallback, useContext } from "react";
55
import { BG_COLOR, TEXT_COLOR } from "../../constants";
66
import DatepickerContext from "../../contexts/DatepickerContext";
77
import { formatDate, nextMonth, previousMonth, classNames as cn } from "../../helpers";
8+
import { Period } from "../../types";
89

910
dayjs.extend(isBetween);
1011

@@ -240,39 +241,127 @@ const Days: React.FC<Props> = ({
240241
[activeDateData, hoverClassByDay, isDateDisabled]
241242
);
242243

244+
const checkIfHoverPeriodContainsDisabledPeriod = useCallback(
245+
(hoverPeriod: Period) => {
246+
if (!Array.isArray(disabledDates)) {
247+
return false;
248+
}
249+
for (let i = 0; i < disabledDates.length; i++) {
250+
if (
251+
dayjs(hoverPeriod.start).isBefore(disabledDates[i].startDate) &&
252+
dayjs(hoverPeriod.end).isAfter(disabledDates[i].endDate)
253+
) {
254+
return true;
255+
}
256+
}
257+
return false;
258+
},
259+
[disabledDates]
260+
);
261+
262+
const getMetaData = useCallback(() => {
263+
return {
264+
previous: previousMonth(calendarData.date),
265+
current: calendarData.date,
266+
next: nextMonth(calendarData.date)
267+
};
268+
}, [calendarData.date]);
269+
243270
const hoverDay = useCallback(
244271
(day: number, type: string) => {
245-
const object = {
246-
previous: previousMonth(calendarData.date),
247-
current: calendarData.date,
248-
next: nextMonth(calendarData.date)
249-
};
272+
const object = getMetaData();
250273
const newDate = object[type as keyof typeof object];
251274
const newHover = `${newDate.year()}-${newDate.month() + 1}-${
252275
day >= 10 ? day : "0" + day
253276
}`;
254277

255278
if (period.start && !period.end) {
279+
const hoverPeriod = { ...period, end: newHover };
256280
if (dayjs(newHover).isBefore(dayjs(period.start))) {
257-
changePeriod({
258-
start: null,
259-
end: period.start
260-
});
281+
hoverPeriod.start = newHover;
282+
hoverPeriod.end = period.start;
283+
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
284+
changePeriod({
285+
start: null,
286+
end: period.start
287+
});
288+
}
289+
}
290+
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
291+
changeDayHover(newHover);
261292
}
262-
changeDayHover(newHover);
263293
}
264294

265295
if (!period.start && period.end) {
296+
const hoverPeriod = { ...period, start: newHover };
266297
if (dayjs(newHover).isAfter(dayjs(period.end))) {
267-
changePeriod({
268-
start: period.end,
269-
end: null
270-
});
298+
hoverPeriod.start = period.end;
299+
hoverPeriod.end = newHover;
300+
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
301+
changePeriod({
302+
start: period.end,
303+
end: null
304+
});
305+
}
306+
}
307+
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
308+
changeDayHover(newHover);
271309
}
272-
changeDayHover(newHover);
273310
}
274311
},
275-
[calendarData.date, changeDayHover, changePeriod, period.end, period.start]
312+
[
313+
changeDayHover,
314+
changePeriod,
315+
checkIfHoverPeriodContainsDisabledPeriod,
316+
getMetaData,
317+
period
318+
]
319+
);
320+
321+
const handleClickDay = useCallback(
322+
(day: number, type: "previous" | "current" | "next") => {
323+
function continueClick() {
324+
if (type === "previous") {
325+
onClickPreviousDays(day);
326+
}
327+
328+
if (type === "current") {
329+
onClickDay(day);
330+
}
331+
332+
if (type === "next") {
333+
onClickNextDays(day);
334+
}
335+
}
336+
337+
if (disabledDates?.length) {
338+
const object = getMetaData();
339+
const newDate = object[type as keyof typeof object];
340+
const clickDay = `${newDate.year()}-${newDate.month() + 1}-${
341+
day >= 10 ? day : "0" + day
342+
}`;
343+
344+
if (period.start && !period.end) {
345+
dayjs(clickDay).isSame(dayHover) && continueClick();
346+
} else if (!period.start && period.end) {
347+
dayjs(clickDay).isSame(dayHover) && continueClick();
348+
} else {
349+
continueClick();
350+
}
351+
} else {
352+
continueClick();
353+
}
354+
},
355+
[
356+
dayHover,
357+
disabledDates?.length,
358+
getMetaData,
359+
onClickDay,
360+
onClickNextDays,
361+
onClickPreviousDays,
362+
period.end,
363+
period.start
364+
]
276365
);
277366

278367
return (
@@ -283,7 +372,7 @@ const Days: React.FC<Props> = ({
283372
key={index}
284373
disabled={isDateDisabled(item, "previous")}
285374
className="flex items-center justify-center text-gray-400 h-12 w-12 lg:w-10 lg:h-10"
286-
onClick={() => onClickPreviousDays(item)}
375+
onClick={() => handleClickDay(item, "previous")}
287376
onMouseOver={() => {
288377
hoverDay(item, "previous");
289378
}}
@@ -298,9 +387,7 @@ const Days: React.FC<Props> = ({
298387
key={index}
299388
disabled={isDateDisabled(item, "current")}
300389
className={`${buttonClass(item, "current")}`}
301-
onClick={() => {
302-
onClickDay(item);
303-
}}
390+
onClick={() => handleClickDay(item, "current")}
304391
onMouseOver={() => {
305392
hoverDay(item, "current");
306393
}}
@@ -315,9 +402,7 @@ const Days: React.FC<Props> = ({
315402
key={index}
316403
disabled={isDateDisabled(index, "next")}
317404
className="flex items-center justify-center text-gray-400 h-12 w-12 lg:w-10 lg:h-10"
318-
onClick={() => {
319-
onClickNextDays(item);
320-
}}
405+
onClick={() => handleClickDay(item, "next")}
321406
onMouseOver={() => {
322407
hoverDay(item, "next");
323408
}}

src/components/Datepicker.tsx

Lines changed: 5 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,59 +9,11 @@ import { COLORS, DATE_FORMAT, DEFAULT_COLOR, LANGUAGE } from "../constants";
99
import DatepickerContext from "../contexts/DatepickerContext";
1010
import { formatDate, nextMonth, previousMonth } from "../helpers";
1111
import useOnClickOutside from "../hooks";
12-
import {
13-
Period,
14-
DateValueType,
15-
DateType,
16-
DateRangeType,
17-
ClassNamesTypeProp,
18-
ClassNameParam
19-
} from "../types";
12+
import { Period, DatepickerType } from "../types";
2013

2114
import { Arrow, VerticalDash } from "./utils";
2215

23-
interface Props {
24-
primaryColor?: string;
25-
value: DateValueType;
26-
onChange: (value: DateValueType, e?: HTMLInputElement | null | undefined) => void;
27-
useRange?: boolean;
28-
showFooter?: boolean;
29-
showShortcuts?: boolean;
30-
configs?: {
31-
shortcuts?: {
32-
today?: string;
33-
yesterday?: string;
34-
past?: (period: number) => string;
35-
currentMonth?: string;
36-
pastMonth?: string;
37-
} | null;
38-
footer?: {
39-
cancel?: string;
40-
apply?: string;
41-
} | null;
42-
} | null;
43-
asSingle?: boolean;
44-
placeholder?: string;
45-
separator?: string;
46-
startFrom?: Date | null;
47-
i18n?: string;
48-
disabled?: boolean;
49-
classNames?: ClassNamesTypeProp | undefined;
50-
inputClassName?: ((args?: ClassNameParam) => string) | string | null;
51-
toggleClassName?: string | null;
52-
toggleIcon?: ((open: ClassNameParam) => React.ReactNode) | undefined;
53-
inputId?: string;
54-
inputName?: string;
55-
containerClassName?: ((args?: ClassNameParam) => string) | string | null;
56-
displayFormat?: string;
57-
readOnly?: boolean;
58-
minDate?: DateType | null;
59-
maxDate?: DateType | null;
60-
disabledDates?: DateRangeType[] | null;
61-
startWeekOn?: string | null;
62-
}
63-
64-
const Datepicker: React.FC<Props> = ({
16+
const Datepicker: React.FC<DatepickerType> = ({
6517
primaryColor = "blue",
6618
value = null,
6719
onChange,
@@ -90,9 +42,9 @@ const Datepicker: React.FC<Props> = ({
9042
classNames = undefined
9143
}) => {
9244
// Ref
93-
const containerRef = useRef<HTMLDivElement>(null);
94-
const calendarContainerRef = useRef<HTMLDivElement>(null);
95-
const arrowRef = useRef<HTMLDivElement>(null);
45+
const containerRef = useRef<HTMLDivElement | null>(null);
46+
const calendarContainerRef = useRef<HTMLDivElement | null>(null);
47+
const arrowRef = useRef<HTMLDivElement | null>(null);
9648

9749
// State
9850
const [firstDate, setFirstDate] = useState<dayjs.Dayjs>(

src/components/Footer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dayjs from "dayjs";
22
import React, { useCallback, useContext } from "react";
33

4+
import { DATE_FORMAT } from "../constants";
45
import DatepickerContext from "../contexts/DatepickerContext";
56

67
import { PrimaryButton, SecondaryButton } from "./utils";
@@ -33,8 +34,8 @@ const Footer: React.FC = () => {
3334
onClick={() => {
3435
if (period.start && period.end) {
3536
changeDatepickerValue({
36-
startDate: dayjs(period.start).format("YYYY-MM-DD"),
37-
endDate: dayjs(period.end).format("YYYY-MM-DD")
37+
startDate: dayjs(period.start).format(DATE_FORMAT),
38+
endDate: dayjs(period.end).format(DATE_FORMAT)
3839
});
3940
hideDatepicker();
4041
}

src/types/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React from "react";
2+
13
export interface Period {
24
start: string | null;
35
end: string | null;
@@ -43,3 +45,44 @@ export type ClassNamesTypeProp = {
4345
};
4446

4547
export type ClassNameParam = ClassNameParam[] | string | number | boolean | undefined;
48+
49+
export interface DatepickerType {
50+
primaryColor?: string;
51+
value: DateValueType;
52+
onChange: (value: DateValueType, e?: HTMLInputElement | null | undefined) => void;
53+
useRange?: boolean;
54+
showFooter?: boolean;
55+
showShortcuts?: boolean;
56+
configs?: {
57+
shortcuts?: {
58+
today?: string;
59+
yesterday?: string;
60+
past?: (period: number) => string;
61+
currentMonth?: string;
62+
pastMonth?: string;
63+
} | null;
64+
footer?: {
65+
cancel?: string;
66+
apply?: string;
67+
} | null;
68+
} | null;
69+
asSingle?: boolean;
70+
placeholder?: string;
71+
separator?: string;
72+
startFrom?: Date | null;
73+
i18n?: string;
74+
disabled?: boolean;
75+
classNames?: ClassNamesTypeProp | undefined;
76+
inputClassName?: ((args?: ClassNameParam) => string) | string | null;
77+
toggleClassName?: string | null;
78+
toggleIcon?: ((open: ClassNameParam) => React.ReactNode) | undefined;
79+
inputId?: string;
80+
inputName?: string;
81+
containerClassName?: ((args?: ClassNameParam) => string) | string | null;
82+
displayFormat?: string;
83+
readOnly?: boolean;
84+
minDate?: DateType | null;
85+
maxDate?: DateType | null;
86+
disabledDates?: DateRangeType[] | null;
87+
startWeekOn?: string | null;
88+
}

0 commit comments

Comments
 (0)