import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
import dayjs, { Dayjs } from "@calcom/dayjs";
import { useEmbedStyles } from "@calcom/embed-core/embed-iframe";
import classNames from "@calcom/lib/classNames";
import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
import { Button, Icon } from "@calcom/ui";
import { SkeletonText } from "../../..";
export type DatePickerProps = {
/** which day of the week to render the calendar. Usually Sunday (=0) or Monday (=1) - default: Sunday */
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
/** Fires whenever a selected date is changed. */
onChange: (date: Dayjs) => void;
/** Fires when the month is changed. */
onMonthChange?: (date: Dayjs) => void;
/** which date is currently selected (not tracked from here) */
selected?: Dayjs | null;
/** defaults to current date. */
minDate?: Dayjs;
/** Furthest date selectable in the future, default = UNLIMITED */
maxDate?: Dayjs;
/** locale, any IETF language tag, e.g. "hu-HU" - defaults to Browser settings */
locale: string;
/** Defaults to [], which dates are not bookable. Array of valid dates like: ["2022-04-23", "2022-04-24"] */
excludedDates?: string[];
/** defaults to all, which dates are bookable (inverse of excludedDates) */
includedDates?: string[];
/** allows adding classes to the container */
className?: string;
/** Shows a small loading spinner next to the month name */
isLoading?: boolean;
};
export const Day = ({
date,
active,
disabled,
...props
}: JSX.IntrinsicElements["button"] & {
active: boolean;
date: Dayjs;
}) => {
const enabledDateButtonEmbedStyles = useEmbedStyles("enabledDateButton");
const disabledDateButtonEmbedStyles = useEmbedStyles("disabledDateButton");
return (
);
};
const NoAvailabilityOverlay = ({
month,
nextMonthButton,
}: {
month: string | null;
nextMonthButton: () => void;
}) => {
const { t } = useLocale();
return (
{t("no_availability_in_month", { month: month })}
);
};
const Days = ({
minDate = dayjs.utc(),
excludedDates = [],
browsingDate,
weekStart,
DayComponent = Day,
selected,
month,
nextMonthButton,
...props
}: Omit & {
DayComponent?: React.FC>;
browsingDate: Dayjs;
weekStart: number;
month: string | null;
nextMonthButton: () => void;
}) => {
// Create placeholder elements for empty days in first week
const weekdayOfFirst = browsingDate.day();
const currentDate = minDate.utcOffset(browsingDate.utcOffset());
const availableDates = (includedDates: string[] | undefined) => {
const dates = [];
const lastDateOfMonth = browsingDate.date(daysInMonth(browsingDate));
for (
let date = currentDate;
date.isBefore(lastDateOfMonth) || date.isSame(lastDateOfMonth, "day");
date = date.add(1, "day")
) {
// even if availableDates is given, filter out the passed included dates
if (includedDates && !includedDates.includes(yyyymmdd(date))) {
continue;
}
dates.push(yyyymmdd(date));
}
return dates;
};
const includedDates = currentDate.isSame(browsingDate, "month")
? availableDates(props.includedDates)
: props.includedDates;
const days: (Dayjs | null)[] = Array((weekdayOfFirst - weekStart + 7) % 7).fill(null);
for (let day = 1, dayCount = daysInMonth(browsingDate); day <= dayCount; day++) {
const date = browsingDate.set("date", day);
days.push(date);
}
return (
<>
{days.map((day, idx) => (
{day === null ? (
) : props.isLoading ? (
) : (
{
props.onChange(day);
setTimeout(() => {
window.scrollTo({
top: 360,
behavior: "smooth",
});
}, 500);
}}
disabled={
(includedDates && !includedDates.includes(yyyymmdd(day))) ||
excludedDates.includes(yyyymmdd(day))
}
active={selected ? yyyymmdd(selected) === yyyymmdd(day) : false}
/>
)}
))}
{!props.isLoading && includedDates && includedDates?.length === 0 && (
)}
>
);
};
const DatePicker = ({
weekStart = 0,
className,
locale,
selected,
onMonthChange,
...passThroughProps
}: DatePickerProps & Partial>) => {
const browsingDate = passThroughProps.browsingDate || dayjs().startOf("month");
const { i18n } = useLocale();
const changeMonth = (newMonth: number) => {
if (onMonthChange) {
onMonthChange(browsingDate.add(newMonth, "month"));
}
};
const month = browsingDate
? new Intl.DateTimeFormat(i18n.language, { month: "long" }).format(
new Date(browsingDate.year(), browsingDate.month())
)
: null;
return (
{browsingDate ? (
<>
{month}{" "}
{browsingDate.format("YYYY")}
>
) : (
)}
{weekdayNames(locale, weekStart, "short").map((weekDay) => (
{weekDay}
))}
changeMonth(+1)}
/>
);
};
export default DatePicker;