Skip to content
Merged
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
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 formattedDate = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
return dayjs(formattedDate).isSame(dayjs(minDate))
? false
: dayjs(formattedDate).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 formattedDate = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
return dayjs(formattedDate).isSame(maxDate)
? false
: dayjs(formattedDate).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 formattedDate = `${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(formattedDate).isAfter(dateRange.startDate) &&
dayjs(formattedDate).isBefore(dateRange.endDate)
) {
matchingCount++;
}
if (
dayjs(formattedDate).isSame(dateRange.startDate) ||
dayjs(formattedDate).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
Loading