"use client"; import { differenceInCalendarDays } from "date-fns"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { type Dispatch, type HTMLAttributes, type ReactNode, type SetStateAction, type TableHTMLAttributes, useCallback, useMemo, useState, } from "react"; import { DayPicker, type DayPickerProps, labelNext, labelPrevious, useDayPicker, } from "react-day-picker"; import { Button, buttonVariants } from "~/components/ui/button"; import { cn } from "~/lib/utils"; type CalendarProps = DayPickerProps & { /** * In the year view, the number of years to display at once. * @default 12 */ yearRange?: number; /** * Wether to show the year switcher in the caption. * @default true */ showYearSwitcher?: boolean; monthsClassName?: string; monthCaptionClassName?: string; weekdaysClassName?: string; weekdayClassName?: string; monthClassName?: string; captionClassName?: string; captionLabelClassName?: string; buttonNextClassName?: string; buttonPreviousClassName?: string; navClassName?: string; monthGridClassName?: string; weekClassName?: string; dayClassName?: string; dayButtonClassName?: string; rangeStartClassName?: string; rangeEndClassName?: string; selectedClassName?: string; todayClassName?: string; outsideClassName?: string; disabledClassName?: string; rangeMiddleClassName?: string; hiddenClassName?: string; }; type NavView = "days" | "years"; /** * A custom calendar component built on top of react-day-picker. * @param props The props for the calendar. * @default yearRange 12 * @returns */ function Calendar({ className, showOutsideDays = true, showYearSwitcher = true, yearRange = 12, numberOfMonths, ...props }: CalendarProps) { const [navView, setNavView] = useState("days"); const [displayYears, setDisplayYears] = useState<{ from: number; to: number; }>( useMemo(() => { const currentYear = new Date().getFullYear(); return { from: currentYear - Math.floor(yearRange / 2 - 1), to: currentYear + Math.ceil(yearRange / 2), }; }, [yearRange]), ); const { onPrevClick, startMonth, endMonth } = props; const columnsDisplayed = navView === "years" ? 1 : numberOfMonths; const _monthsClassName = cn("relative flex", props.monthsClassName); const _monthCaptionClassName = cn( "relative mx-10 flex h-7 items-center justify-center", props.monthCaptionClassName, ); const _weekdaysClassName = cn("flex flex-row", props.weekdaysClassName); const _weekdayClassName = cn( "w-8 text-sm font-normal text-muted-foreground", props.weekdayClassName, ); const _monthClassName = cn("w-full", props.monthClassName); const _captionClassName = cn( "relative flex items-center justify-center pt-1", props.captionClassName, ); const _captionLabelClassName = cn( "truncate text-sm font-medium", props.captionLabelClassName, props.captionLayout === "dropdown" && "flex items-center text-sm font-medium border border-neutral-200 rounded-md px-2 py-1", ); const buttonNavClassName = buttonVariants({ className: "absolute size-7 bg-transparent p-0 opacity-50 hover:opacity-100", variant: "outline", }); const _buttonNextClassName = cn( buttonNavClassName, "right-0", props.buttonNextClassName, ); const _buttonPreviousClassName = cn( buttonNavClassName, "left-0", props.buttonPreviousClassName, ); const _navClassName = cn("flex items-start", props.navClassName); const _monthGridClassName = cn("mx-auto mt-4", props.monthGridClassName); const _weekClassName = cn("mt-2 flex w-max items-start", props.weekClassName); const _dayClassName = cn( "flex size-8 flex-1 items-center justify-center p-0 text-sm", props.dayClassName, ); const _dayButtonClassName = cn( buttonVariants({ variant: "ghost" }), "size-8 rounded-md p-0 font-normal transition-none aria-selected:opacity-100", props.dayButtonClassName, ); const buttonRangeClassName = "bg-accent [&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground"; const _rangeStartClassName = cn( buttonRangeClassName, "rounded-s-md", props.rangeStartClassName, ); const _rangeEndClassName = cn( buttonRangeClassName, "rounded-e-md", props.rangeEndClassName, ); const _rangeMiddleClassName = cn( "bg-accent !text-foreground [&>button]:bg-transparent [&>button]:!text-foreground [&>button]:hover:bg-transparent [&>button]:hover:!text-foreground", props.rangeMiddleClassName, ); const _selectedClassName = cn( "[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground", props.selectedClassName, ); const _todayClassName = cn( "[&>button]:bg-accent [&>button]:text-accent-foreground", props.todayClassName, ); const _outsideClassName = cn( "text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", props.outsideClassName, ); const _disabledClassName = cn( "text-muted-foreground opacity-50", props.disabledClassName, ); const _hiddenClassName = cn("invisible flex-1", props.hiddenClassName); const _dropdownOverrideClassName = cn("rounded-md p-2 shadow-xs bg-card"); return ( ( // // ), CaptionLabel: (props) => ( ), Chevron: ({ orientation }) => { const Icon = orientation === "left" ? ChevronLeft : ChevronRight; return ; }, MonthGrid: ({ className, children, ...props }) => ( {children} ), Nav: ({ className }) => (