Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dates disabling/restriction props #30

Merged
merged 12 commits into from
Jan 9, 2023
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
- ✅ TypeScript support
- ✅ Localization(i18n)
- ✅ Date formatting
- ⬜ Disable specific dates
- ✅ Disable specific dates
- ✅ Minimum Date and Maximum Date
- ⬜ Custom shortcuts

## Documentation
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-tailwindcss-datepicker",
"version": "1.3.3",
"name": "nloomis_react-tailwindcss-datepicker",
NoahMLoomis marked this conversation as resolved.
Show resolved Hide resolved
"version": "1.9.0",
NoahMLoomis marked this conversation as resolved.
Show resolved Hide resolved
"description": "A modern React Datepicker using Tailwind CSS 3",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand All @@ -16,6 +16,7 @@
"pret:fix": "prettier -w .",
"format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc",
"build": "npm run pret && npm run lint && npm run clean && rollup -c",
"pub": "npm run build && npm publish",
"dev": "next dev -p 8888"
},
"repository": {
Expand Down
113 changes: 112 additions & 1 deletion pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export default function Playground() {
const [containerClassName, setContainerClassName] = useState("");
const [displayFormat, setDisplayFormat] = useState("YYYY-MM-DD");
const [readOnly, setReadOnly] = useState(false);
const [startFrom, setStartFrom] = useState("2023-03-01");
const [startFrom, setStartFrom] = useState("2023-01-02");
const [minDate, setMinDate] = useState("");
const [maxDate, setMaxDate] = useState("");
const [disabledDates, setDisabledDates] = useState([]);
const [newDisabledDates, setNewDisabledDates] = useState({ startDate: "", endDate: "" });

return (
<div className="px-4 py-8">
Expand Down Expand Up @@ -58,6 +62,9 @@ export default function Playground() {
containerClassName={containerClassName}
displayFormat={displayFormat}
readOnly={readOnly}
minDate={minDate}
maxDate={maxDate}
disabledDates={disabledDates}
/>
</div>

Expand Down Expand Up @@ -207,6 +214,19 @@ export default function Playground() {
}}
/>
</div>
<div className="mb-2">
<label className="block" htmlFor="minDate">
Minimum Date
</label>
<input
className="rounded border px-4 py-2 w-full border-gray-200"
id="minDate"
value={minDate}
onChange={e => {
setMinDate(e.target.value);
}}
/>
</div>
</div>
<div className="w-full sm:w-1/3 pr-2 flex flex-col">
<div className="mb-2">
Expand Down Expand Up @@ -261,6 +281,97 @@ export default function Playground() {
}}
/>
</div>
<div className="mb-2">
<label className="block" htmlFor="maxDate">
Maximum Date
</label>
<input
className="rounded border px-4 py-2 w-full border-gray-200"
id="maxDate"
value={maxDate}
onChange={e => {
setMaxDate(e.target.value);
}}
/>
</div>
</div>
<div className="w-full grid sm:grid-cols-3">
<div className="sm:col-start-2 sm:col-span-2 p-2 border-t grid grid-cols-2">
<h1 className="mb-2 text-lg font-semibold text-center col-span-3">
Disable Dates
</h1>
<div className="mb-2 sm:col-span-2 mr-2">
<label className="block" htmlFor="startDate">
Start Date
</label>
<input
className="rounded border px-4 py-2 border-gray-200 sm:w-full w-3/4"
id="startDate"
value={newDisabledDates.startDate}
onChange={e => {
setNewDisabledDates(prev => {
return {
...prev,
startDate: e.target.value
};
});
}}
/>
</div>
<div className="mb-2">
<label className="block" htmlFor="endDate">
End Date
</label>
<input
className="rounded border px-4 py-2 border-gray-200 sm:w-full w-3/4"
id="endDate"
value={newDisabledDates.endDate}
onChange={e => {
setNewDisabledDates(prev => {
return {
...prev,
endDate: e.target.value
};
});
}}
/>
</div>
<div className="mb-2 col-span-3">
<button
onClick={() => {
if (
newDisabledDates.startDate !== "" &&
newDisabledDates.endDate !== ""
) {
setDisabledDates(prev => [...prev, newDisabledDates]);
setNewDisabledDates({ startDate: "", endDate: "" });
}
}}
className="w-full bg-black text-white text-lg text-center p-2 rounded-lg"
>
Add
</button>
</div>
<div className="mb-2 grid col-span-3 grid-col-2">
{disabledDates.map((range, index) => (
<div className="mb-2 p-2" key={index}>
<button
className="bg-red-500 text-white text-center rounded-xl p-2"
onClick={() => {
setDisabledDates(
disabledDates.filter(r => r !== range)
);
}}
>
Delete
</button>
<span className="pl-2">
{range.startDate} - {range.endDate}
</span>
</div>
))}
</div>
</div>
</div>
</div>
<div className="flex flex-row flex-wrap items-center justify-center w-full">
Expand Down
122 changes: 110 additions & 12 deletions src/components/Calendar/Days.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import React, { useCallback, useContext } from "react";

import { BG_COLOR } from "../../constants";
import DatepickerContext from "../../contexts/DatepickerContext";
import { formatDate, getTextColorByPrimaryColor, nextMonth, previousMonth } from "../../helpers";
import {
formatDate,
getTextColorByPrimaryColor,
nextMonth,
previousMonth,
classNames as cn
} from "../../helpers";

const isBetween = require("dayjs/plugin/isBetween");
dayjs.extend(isBetween);
Expand All @@ -29,8 +35,16 @@ const Days: React.FC<Props> = ({
onClickNextDays
}) => {
// Contexts
const { primaryColor, period, changePeriod, dayHover, changeDayHover } =
useContext(DatepickerContext);
const {
primaryColor,
period,
changePeriod,
dayHover,
changeDayHover,
minDate,
maxDate,
disabledDates
} = useContext(DatepickerContext);

// Functions
const currentDateClass = useCallback(
Expand Down Expand Up @@ -139,16 +153,97 @@ const Days: React.FC<Props> = ({
[calendarData.date, currentDateClass, dayHover, period.end, period.start, primaryColor]
);

const buttonCass = useCallback(
(day: number) => {
const baseClass = "flex items-center justify-center w-12 h-12 lg:w-10 lg:h-10";
return `${baseClass}${
!activeDateData(day).active
? ` ${hoverClassByDay(day)}`
: activeDateData(day).className
const isDateTooEarly = useCallback(
(day: number, type: string) => {
if (!minDate) {
return false;
}
const object = {
previous: previousMonth(calendarData.date),
current: calendarData.date,
next: nextMonth(calendarData.date)
};
const newDate = object[type as keyof typeof object];
const newHover = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
return dayjs(newHover).isSame(dayjs(minDate))
? false
: dayjs(newHover).isBefore(dayjs(minDate));
},
[calendarData.date, minDate]
);

const isDateTooLate = useCallback(
(day: number, type: string) => {
if (!maxDate) {
return false;
}
const object = {
previous: previousMonth(calendarData.date),
current: calendarData.date,
next: nextMonth(calendarData.date)
};
const newDate = object[type as keyof typeof object];
const newHover = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
return dayjs(newHover).isSame(maxDate)
? false
: dayjs(newHover).isAfter(dayjs(maxDate));
},
[calendarData.date, maxDate]
);

const isDateDisabled = useCallback(
(day: number, type: string) => {
if (isDateTooEarly(day, type) || isDateTooLate(day, type)) {
return true;
}
const object = {
previous: previousMonth(calendarData.date),
current: calendarData.date,
next: nextMonth(calendarData.date)
};
const newDate = object[type as keyof typeof object];
const newHover = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;

if (!disabledDates || disabledDates?.length <= 0) {
return false;
}

let matchingCount = 0;
disabledDates?.forEach(dateRange => {
if (
dayjs(newHover).isAfter(dateRange.startDate) &&
dayjs(newHover).isBefore(dateRange.endDate)
) {
matchingCount++;
}
if (
dayjs(newHover).isSame(dateRange.startDate) ||
dayjs(newHover).isSame(dateRange.endDate)
) {
matchingCount++;
}
});
return matchingCount > 0;
},
[calendarData.date, isDateTooEarly, isDateTooLate]
);

const buttonClass = useCallback(
(day: number, type: string) => {
const baseClass = "flex items-center justify-center w-12 h-12 lg:w-10 lg:h-10";
return cn(
baseClass,
!activeDateData(day).active ? hoverClassByDay(day) : activeDateData(day).className,
isDateDisabled(day, type) && "line-through"
);
},
[activeDateData, hoverClassByDay]
[activeDateData, hoverClassByDay, isDateDisabled]
);

const hoverDay = useCallback(
Expand Down Expand Up @@ -192,6 +287,7 @@ const Days: React.FC<Props> = ({
<button
type="button"
key={index}
disabled={isDateDisabled(item, "previous")}
className="flex items-center justify-center text-gray-400 h-12 w-12 lg:w-10 lg:h-10"
onClick={() => onClickPreviousDays(item)}
onMouseOver={() => {
Expand All @@ -206,7 +302,8 @@ const Days: React.FC<Props> = ({
<button
type="button"
key={index}
className={buttonCass(item)}
disabled={isDateDisabled(item, "current")}
className={`${buttonClass(item, "current")}`}
onClick={() => {
onClickDay(item);
}}
Expand All @@ -222,6 +319,7 @@ const Days: React.FC<Props> = ({
<button
type="button"
key={index}
disabled={isDateDisabled(index, "previous")}
className="flex items-center justify-center text-gray-400 h-12 w-12 lg:w-10 lg:h-10"
onClick={() => {
onClickNextDays(item);
Expand Down
8 changes: 4 additions & 4 deletions src/components/Calendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ const Calendar: React.FC<Props> = ({
);
}, [current, date, previous]);

const hiddeMonths = useCallback(() => {
const hideMonths = useCallback(() => {
if (showMonths) {
setShowMonths(false);
}
}, [showMonths]);

const hiddeYears = useCallback(() => {
const hideYears = useCallback(() => {
if (showYears) {
setShowYears(false);
}
Expand Down Expand Up @@ -254,7 +254,7 @@ const Calendar: React.FC<Props> = ({
<RoundedButton
onClick={() => {
setShowMonths(!showMonths);
hiddeYears();
hideYears();
}}
>
<>{shortString(calendarData.date.locale(i18n).format("MMM"))}</>
Expand All @@ -265,7 +265,7 @@ const Calendar: React.FC<Props> = ({
<RoundedButton
onClick={() => {
setShowYears(!showYears);
hiddeMonths();
hideMonths();
}}
>
<>{calendarData.date.year()}</>
Expand Down