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 { SkeletonText } from "@calcom/ui/v2"; 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; /** 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, ...props }: JSX.IntrinsicElements["button"] & { active: boolean; date: Dayjs }) => { const enabledDateButtonEmbedStyles = useEmbedStyles("enabledDateButton"); const disabledDateButtonEmbedStyles = useEmbedStyles("disabledDateButton"); return ( {date.date()} {date.isToday() && ( . )} ); }; const Days = ({ // minDate, excludedDates = [], includedDates, browsingDate, weekStart, DayComponent = Day, selected, ...props }: Omit & { DayComponent?: React.FC>; browsingDate: Dayjs; weekStart: number; }) => { // Create placeholder elements for empty days in first week const weekdayOfFirst = browsingDate.day(); 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); window.scrollTo({ top: 360, behavior: "smooth", }); }} disabled={ (includedDates && !includedDates.includes(yyyymmdd(day))) || excludedDates.includes(yyyymmdd(day)) } active={selected ? yyyymmdd(selected) === yyyymmdd(day) : false} /> )} ))} > ); }; 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.toISOString())) : null; return ( {browsingDate ? ( <> {month}{" "} {browsingDate.format("YYYY")} > ) : ( )} changeMonth(-1)} className={classNames( "group p-1 opacity-50 hover:opacity-100 ltr:mr-2 rtl:ml-2", !browsingDate.isAfter(dayjs()) && "disabled:text-bookinglighter hover:opacity-50" )} disabled={!browsingDate.isAfter(dayjs())} data-testid="decrementMonth"> changeMonth(+1)} data-testid="incrementMonth"> {weekdayNames(locale, weekStart, "short").map((weekDay) => ( {weekDay} ))} ); }; export default DatePicker;