{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar",
  "type": "registry:ui",
  "title": "Calendar",
  "description": "A date field component that enables users to choose and edit a date.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": ["@scrollxui/button"],
  "dependencies": [
    "@radix-ui/react-slot",
    "class-variance-authority",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/calendar.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport type { Dispatch, SetStateAction } from 'react';\nimport {\n  ChevronDownIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n} from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\n\ninterface DateRange {\n  from: Date | undefined;\n  to?: Date | undefined;\n}\n\ninterface Modifiers {\n  [key: string]:\n    | Date[]\n    | ((date: Date) => boolean)\n    | { dayOfWeek?: number[]; before?: Date; after?: Date };\n}\n\ninterface FormatOptions {\n  locale?: string;\n  [key: string]: unknown;\n}\n\ninterface CalendarBaseProps {\n  className?: string;\n  classNames?: {\n    [key: string]: string;\n  };\n\n  showOutsideDays?: boolean;\n  showWeekNumber?: boolean;\n  numberOfMonths?: number;\n  captionLayout?: 'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years';\n  fromYear?: number;\n  toYear?: number;\n  fromMonth?: Date;\n  toMonth?: Date;\n  yearOrder?: 'asc' | 'desc';\n\n  disabled?:\n    | Date[]\n    | ((date: Date) => boolean)\n    | { before?: Date; after?: Date; dayOfWeek?: number[] };\n\n  modifiers?: Modifiers;\n  modifiersClassNames?: { [key: string]: string };\n\n  locale?: string;\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n  firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;\n\n  labels?: {\n    labelMonthDropdown?: () => string;\n    labelYearDropdown?: () => string;\n    labelNext?: () => string;\n    labelPrevious?: () => string;\n  };\n\n  formatters?: {\n    formatCaption?: (date: Date, options?: FormatOptions) => string;\n    formatDay?: (date: Date) => string;\n    formatMonthDropdown?: (date: Date) => string;\n    formatYearDropdown?: (date: Date) => string;\n    formatWeekNumber?: (weekNumber: number) => string;\n    formatWeekdayName?: (date: Date) => string;\n  };\n\n  onMonthChange?: (date: Date) => void;\n  onDayClick?: (date: Date, modifiers: string[]) => void;\n  onDayMouseEnter?: (date: Date, modifiers: string[]) => void;\n  onDayMouseLeave?: (date: Date, modifiers: string[]) => void;\n\n  footer?: React.ReactNode;\n  components?: {\n    DayButton?: React.ComponentType<CalendarDayButtonProps>;\n    [key: string]: React.ComponentType<CalendarDayButtonProps> | undefined;\n  };\n\n  animate?: boolean;\n  dir?: 'ltr' | 'rtl';\n  autoFocus?: boolean;\n  defaultMonth?: Date;\n\n  ISOWeek?: boolean;\n  fixedWeeks?: boolean;\n\n  buttonVariant?: string;\n}\n\ninterface CalendarProps extends CalendarBaseProps {\n  mode?: 'single' | 'multiple' | 'range';\n  selected?: Date | Date[] | DateRange;\n  onSelect?:\n    | ((date: Date | undefined) => void)\n    | ((dates: Date[] | undefined) => void)\n    | ((range: DateRange | undefined) => void)\n    | Dispatch<SetStateAction<Date[]>>;\n  required?: boolean;\n  max?: number;\n}\n\ninterface CalendarDayButtonProps {\n  day: { date: Date };\n  modifiers: { [key: string]: boolean };\n  children: React.ReactNode;\n  onClick?: () => void;\n  onMouseEnter?: () => void;\n  onMouseLeave?: () => void;\n  disabled?: boolean;\n  className?: string;\n  style?: React.CSSProperties;\n  'data-date'?: string;\n  'data-modifiers'?: string;\n}\n\nconst months = [\n  'January',\n  'February',\n  'March',\n  'April',\n  'May',\n  'June',\n  'July',\n  'August',\n  'September',\n  'October',\n  'November',\n  'December',\n];\n\nconst shortMonths = [\n  'Jan',\n  'Feb',\n  'Mar',\n  'Apr',\n  'May',\n  'Jun',\n  'Jul',\n  'Aug',\n  'Sep',\n  'Oct',\n  'Nov',\n  'Dec',\n];\n\nconst weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];\n\nconst CalendarDayButton: React.FC<CalendarDayButtonProps> = ({\n  day,\n  modifiers,\n  children,\n  onClick,\n  onMouseEnter,\n  onMouseLeave,\n  disabled,\n  className,\n  style,\n  ...props\n}) => {\n  return (\n    <Button\n      variant='ghost'\n      size='icon'\n      onClick={onClick}\n      onMouseEnter={onMouseEnter}\n      onMouseLeave={onMouseLeave}\n      disabled={disabled}\n      className={className}\n      style={style}\n      {...props}\n    >\n      {children}\n    </Button>\n  );\n};\n\nfunction Calendar(props: CalendarProps) {\n  const {\n    className,\n    classNames = {},\n    mode = 'single',\n    selected,\n    onSelect,\n    showOutsideDays = true,\n    showWeekNumber = false,\n    numberOfMonths = 1,\n    captionLayout = 'label',\n    fromYear = new Date().getFullYear() - 100,\n    toYear = new Date().getFullYear(),\n    fromMonth,\n    toMonth,\n    yearOrder = 'desc',\n    disabled,\n    modifiers = {},\n    modifiersClassNames = {},\n    locale = 'en-US',\n    weekStartsOn = 0,\n    firstWeekContainsDate = 1,\n    labels = {},\n    formatters = {},\n    onMonthChange,\n    onDayClick,\n    onDayMouseEnter,\n    onDayMouseLeave,\n    footer,\n    components = {},\n    animate = false,\n    dir = 'ltr',\n    autoFocus = false,\n    defaultMonth,\n    ISOWeek = false,\n    fixedWeeks = false,\n    ...restProps\n  } = props;\n\n  const [currentDates, setCurrentDates] = React.useState(() => {\n    const baseDate =\n      defaultMonth || (selected instanceof Date ? selected : new Date());\n    return Array.from({ length: numberOfMonths }, (_, index) => {\n      const date = new Date(baseDate);\n      date.setMonth(date.getMonth() + index);\n      return date;\n    });\n  });\n\n  const [showMonthPicker, setShowMonthPicker] = React.useState(false);\n  const [showYearPicker, setShowYearPicker] = React.useState(false);\n  const [hoveredDate, setHoveredDate] = React.useState<Date | null>(null);\n\n  React.useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      const target = event.target as Element;\n      if (!target.closest('[data-calendar-container]')) {\n        setShowMonthPicker(false);\n        setShowYearPicker(false);\n      }\n    };\n\n    if (showMonthPicker || showYearPicker) {\n      document.addEventListener('mousedown', handleClickOutside);\n      return () =>\n        document.removeEventListener('mousedown', handleClickOutside);\n    }\n  }, [showMonthPicker, showYearPicker]);\n\n  const getMonthNames = () => {\n    if (formatters.formatMonthDropdown) {\n      return shortMonths.map((_, index) =>\n        formatters.formatMonthDropdown!(new Date(2023, index, 1)),\n      );\n    }\n    return shortMonths;\n  };\n\n  const getWeekDayNames = () => {\n    const days = [...weekDays];\n    for (let i = 0; i < weekStartsOn; i++) {\n      days.push(days.shift()!);\n    }\n    return days;\n  };\n\n  const getDaysInMonth = (year: number, month: number) => {\n    return new Date(year, month + 1, 0).getDate();\n  };\n\n  const getFirstDayOfMonth = (year: number, month: number) => {\n    const firstDay = new Date(year, month, 1).getDay();\n    return (firstDay - weekStartsOn + 7) % 7;\n  };\n\n  const getWeekNumber = (date: Date): number => {\n    if (ISOWeek) {\n      const d = new Date(\n        Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),\n      );\n      const dayNum = d.getUTCDay() || 7;\n      d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n      const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n      return Math.ceil(\n        ((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7,\n      );\n    }\n\n    const target = new Date(date.valueOf());\n    const dayNr = (date.getDay() + 6) % 7;\n    target.setDate(target.getDate() - dayNr + 3);\n    const jan4 = new Date(target.getFullYear(), 0, 4);\n    const dayDiff = (target.getTime() - jan4.getTime()) / 86400000;\n    return 1 + Math.ceil(dayDiff / 7);\n  };\n\n  const isDateDisabled = (date: Date): boolean => {\n    if (!disabled) return false;\n\n    if (Array.isArray(disabled)) {\n      return disabled.some((d) => d.toDateString() === date.toDateString());\n    }\n\n    if (typeof disabled === 'function') {\n      return disabled(date);\n    }\n\n    if (typeof disabled === 'object') {\n      if (disabled.before && date < disabled.before) return true;\n      if (disabled.after && date > disabled.after) return true;\n      if (disabled.dayOfWeek && disabled.dayOfWeek.includes(date.getDay()))\n        return true;\n    }\n\n    return false;\n  };\n\n  const getDayModifiers = (\n    date: Date,\n    currentMonth: number,\n    currentYear: number,\n  ) => {\n    const dayModifiers: { [key: string]: boolean } = {};\n\n    dayModifiers.today = isToday(date);\n    dayModifiers.selected = isSelected(date);\n    dayModifiers.disabled = isDateDisabled(date);\n    dayModifiers.outside = !isCurrentMonth(date, currentMonth, currentYear);\n    dayModifiers.hovered = hoveredDate\n      ? date.toDateString() === hoveredDate.toDateString()\n      : false;\n\n    if (\n      mode === 'range' &&\n      selected &&\n      typeof selected === 'object' &&\n      'from' in selected\n    ) {\n      const range = selected as DateRange;\n      if (range.from) {\n        dayModifiers.range_start =\n          date.toDateString() === range.from.toDateString();\n      }\n      if (range.to) {\n        dayModifiers.range_end =\n          date.toDateString() === range.to.toDateString();\n      }\n      if (range.from && range.to && date > range.from && date < range.to) {\n        dayModifiers.range_middle = true;\n      }\n\n      if (range.from && !range.to && hoveredDate) {\n        const startDate = range.from < hoveredDate ? range.from : hoveredDate;\n        const endDate = range.from < hoveredDate ? hoveredDate : range.from;\n\n        if (date > startDate && date < endDate) {\n          dayModifiers.range_middle_preview = true;\n        }\n        if (date.toDateString() === endDate.toDateString()) {\n          dayModifiers.range_end_preview = true;\n        }\n      }\n    }\n\n    Object.entries(modifiers).forEach(([key, modifier]) => {\n      if (Array.isArray(modifier)) {\n        dayModifiers[key] = modifier.some(\n          (d) => d.toDateString() === date.toDateString(),\n        );\n      } else if (typeof modifier === 'function') {\n        dayModifiers[key] = modifier(date);\n      } else if (typeof modifier === 'object') {\n        let hasModifier = false;\n        if (modifier.dayOfWeek && modifier.dayOfWeek.includes(date.getDay())) {\n          hasModifier = true;\n        }\n        if (modifier.before && date < modifier.before) {\n          hasModifier = true;\n        }\n        if (modifier.after && date > modifier.after) {\n          hasModifier = true;\n        }\n        dayModifiers[key] = hasModifier;\n      }\n    });\n\n    return dayModifiers;\n  };\n\n  const generateCalendarDays = (currentMonth: number, currentYear: number) => {\n    const daysInMonth = getDaysInMonth(currentYear, currentMonth);\n    const firstDay = getFirstDayOfMonth(currentYear, currentMonth);\n    const days = [];\n\n    if (showOutsideDays || firstDay > 0) {\n      const prevMonth = currentMonth === 0 ? 11 : currentMonth - 1;\n      const prevYear = currentMonth === 0 ? currentYear - 1 : currentYear;\n      const daysInPrevMonth = getDaysInMonth(prevYear, prevMonth);\n\n      for (let i = firstDay - 1; i >= 0; i--) {\n        const date = new Date(prevYear, prevMonth, daysInPrevMonth - i);\n        days.push({\n          day: daysInPrevMonth - i,\n          date,\n          isCurrentMonth: false,\n          weekNumber:\n            showWeekNumber && i === firstDay - 1 ? getWeekNumber(date) : null,\n        });\n      }\n    }\n\n    for (let day = 1; day <= daysInMonth; day++) {\n      const date = new Date(currentYear, currentMonth, day);\n      days.push({\n        day,\n        date,\n        isCurrentMonth: true,\n        weekNumber:\n          showWeekNumber && date.getDay() === weekStartsOn\n            ? getWeekNumber(date)\n            : null,\n      });\n    }\n\n    const totalCells = fixedWeeks ? 42 : Math.ceil(days.length / 7) * 7;\n    const nextMonth = currentMonth === 11 ? 0 : currentMonth + 1;\n    const nextYear = currentMonth === 11 ? currentYear + 1 : currentYear;\n\n    for (let day = 1; days.length < totalCells; day++) {\n      const date = new Date(nextYear, nextMonth, day);\n      if (showOutsideDays) {\n        days.push({\n          day,\n          date,\n          isCurrentMonth: false,\n          weekNumber:\n            showWeekNumber && date.getDay() === weekStartsOn\n              ? getWeekNumber(date)\n              : null,\n        });\n      }\n    }\n\n    return days;\n  };\n\n  const isCurrentMonth = (\n    date: Date,\n    currentMonth: number,\n    currentYear: number,\n  ) => {\n    return (\n      date.getMonth() === currentMonth && date.getFullYear() === currentYear\n    );\n  };\n\n  const isSelected = (date: Date): boolean => {\n    if (!selected) return false;\n\n    if (mode === 'single' && selected instanceof Date) {\n      return date.toDateString() === selected.toDateString();\n    }\n\n    if (mode === 'multiple' && Array.isArray(selected)) {\n      return selected.some((d) => d.toDateString() === date.toDateString());\n    }\n\n    if (\n      mode === 'range' &&\n      selected &&\n      typeof selected === 'object' &&\n      'from' in selected\n    ) {\n      const range = selected as DateRange;\n      if (!range.from) return false;\n      if (!range.to) return date.toDateString() === range.from.toDateString();\n      return date >= range.from && date <= range.to;\n    }\n\n    return false;\n  };\n\n  const isToday = (date: Date) => {\n    const today = new Date();\n    return date.toDateString() === today.toDateString();\n  };\n\n  const handleDateClick = (date: Date) => {\n    if (isDateDisabled(date)) return;\n\n    const modifiers = Object.keys(\n      getDayModifiers(date, date.getMonth(), date.getFullYear()),\n    ).filter(\n      (key) => getDayModifiers(date, date.getMonth(), date.getFullYear())[key],\n    );\n    onDayClick?.(date, modifiers);\n\n    if (mode === 'single') {\n      (onSelect as ((date: Date | undefined) => void) | undefined)?.(date);\n    } else if (mode === 'multiple') {\n      const multipleOnSelect = onSelect as\n        | ((dates: Date[] | undefined) => void)\n        | undefined;\n      const currentSelected = (selected as Date[]) || [];\n      const isAlreadySelected = currentSelected.some(\n        (d) => d.toDateString() === date.toDateString(),\n      );\n\n      if (isAlreadySelected) {\n        multipleOnSelect?.(\n          currentSelected.filter(\n            (d) => d.toDateString() !== date.toDateString(),\n          ),\n        );\n      } else {\n        multipleOnSelect?.([...currentSelected, date]);\n      }\n    } else if (mode === 'range') {\n      const rangeOnSelect = onSelect as\n        | ((range: DateRange | undefined) => void)\n        | undefined;\n      const currentRange = (selected as DateRange) || {};\n\n      if (!currentRange.from) {\n        rangeOnSelect?.({ from: date, to: undefined });\n      } else if (currentRange.from && currentRange.to) {\n        if (\n          date.getTime() === currentRange.from.getTime() ||\n          date.getTime() === currentRange.to.getTime()\n        ) {\n          rangeOnSelect?.({ from: undefined, to: undefined });\n        } else if (date >= currentRange.from && date <= currentRange.to) {\n          rangeOnSelect?.({ from: currentRange.from, to: date });\n        } else if (date < currentRange.from) {\n          rangeOnSelect?.({ from: date, to: currentRange.to });\n        } else {\n          rangeOnSelect?.({ from: currentRange.from, to: date });\n        }\n      } else if (currentRange.from && !currentRange.to) {\n        if (date < currentRange.from) {\n          rangeOnSelect?.({ from: date, to: currentRange.from });\n        } else if (date.getTime() === currentRange.from.getTime()) {\n          rangeOnSelect?.({ from: undefined, to: undefined });\n        } else {\n          rangeOnSelect?.({ from: currentRange.from, to: date });\n        }\n      }\n    }\n  };\n\n  const handleDateMouseEnter = (date: Date) => {\n    const modifiers = Object.keys(\n      getDayModifiers(date, date.getMonth(), date.getFullYear()),\n    ).filter(\n      (key) => getDayModifiers(date, date.getMonth(), date.getFullYear())[key],\n    );\n    setHoveredDate(date);\n    onDayMouseEnter?.(date, modifiers);\n  };\n\n  const handleDateMouseLeave = (date: Date) => {\n    const modifiers = Object.keys(\n      getDayModifiers(date, date.getMonth(), date.getFullYear()),\n    ).filter(\n      (key) => getDayModifiers(date, date.getMonth(), date.getFullYear())[key],\n    );\n    setHoveredDate(null);\n    onDayMouseLeave?.(date, modifiers);\n  };\n\n  const handleMonthSelect = (month: number, monthIndex: number = 0) => {\n    const newDates = [...currentDates];\n    newDates[monthIndex] = new Date(\n      newDates[monthIndex].getFullYear(),\n      month,\n      1,\n    );\n\n    for (let i = monthIndex + 1; i < numberOfMonths; i++) {\n      newDates[i] = new Date(newDates[i - 1]);\n      newDates[i].setMonth(newDates[i].getMonth() + 1);\n    }\n\n    setCurrentDates(newDates);\n    setShowMonthPicker(false);\n    onMonthChange?.(newDates[0]);\n  };\n\n  const handleYearSelect = (year: number, monthIndex: number = 0) => {\n    const newDates = [...currentDates];\n    newDates[monthIndex] = new Date(year, newDates[monthIndex].getMonth(), 1);\n\n    for (let i = monthIndex + 1; i < numberOfMonths; i++) {\n      newDates[i] = new Date(newDates[i - 1]);\n      newDates[i].setMonth(newDates[i].getMonth() + 1);\n    }\n\n    setCurrentDates(newDates);\n    setShowYearPicker(false);\n    onMonthChange?.(newDates[0]);\n  };\n\n  const navigateMonth = (direction: 'prev' | 'next') => {\n    const newDates = currentDates.map((date) => {\n      const newDate = new Date(date);\n      if (direction === 'prev') {\n        if (fromMonth && newDate <= fromMonth) return date;\n        newDate.setMonth(newDate.getMonth() - 1);\n      } else {\n        if (toMonth && newDate >= toMonth) return date;\n        newDate.setMonth(newDate.getMonth() + 1);\n      }\n      return newDate;\n    });\n    setCurrentDates(newDates);\n    onMonthChange?.(newDates[0]);\n  };\n\n  const generateYears = () => {\n    const years = [];\n    for (let year = fromYear; year <= toYear; year++) {\n      years.push(year);\n    }\n    return yearOrder === 'desc' ? years.reverse() : years;\n  };\n\n  const weekDayNames = getWeekDayNames();\n  const monthNames = getMonthNames();\n\n  const renderCaption = (monthIndex: number) => {\n    const currentDate = currentDates[monthIndex];\n    const currentMonth = currentDate.getMonth();\n    const currentYear = currentDate.getFullYear();\n\n    const monthName = formatters.formatCaption\n      ? formatters.formatCaption(currentDate)\n      : `${months[currentMonth]} ${currentYear}`;\n\n    if (captionLayout === 'label') {\n      return (\n        <div className='flex h-8 w-full items-center justify-center text-sm font-medium text-foreground'>\n          {monthName}\n        </div>\n      );\n    }\n\n    return (\n      <div className='flex items-center gap-1'>\n        {(captionLayout === 'dropdown' ||\n          captionLayout === 'dropdown-months') && (\n          <Button\n            variant='outline'\n            onClick={() => {\n              setShowMonthPicker(!showMonthPicker);\n              setShowYearPicker(false);\n            }}\n            className='h-8 px-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground'\n          >\n            {monthNames[currentMonth]}\n            <ChevronDownIcon className='ml-1 h-3.5 w-3.5' />\n          </Button>\n        )}\n\n        {captionLayout === 'dropdown-years' && (\n          <span className='text-sm font-medium text-foreground'>\n            {monthNames[currentMonth]}\n          </span>\n        )}\n\n        {(captionLayout === 'dropdown' ||\n          captionLayout === 'dropdown-years') && (\n          <Button\n            variant='outline'\n            onClick={() => {\n              setShowYearPicker(!showYearPicker);\n              setShowMonthPicker(false);\n            }}\n            className='h-8 px-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground'\n          >\n            {currentYear}\n            <ChevronDownIcon className='ml-1 h-3.5 w-3.5' />\n          </Button>\n        )}\n\n        {captionLayout === 'dropdown-months' && (\n          <span className='text-sm font-medium text-foreground'>\n            {currentYear}\n          </span>\n        )}\n      </div>\n    );\n  };\n\n  const CalendarDayButtonComponent = components.DayButton || CalendarDayButton;\n\n  return (\n    <div\n      className={cn(\n        'bg-background text-foreground group/calendar p-3 border border-border rounded-md shadow-xs',\n        animate && 'transition-all',\n        numberOfMonths > 1 && 'flex flex-col gap-4 sm:flex-row',\n        className,\n      )}\n      dir={dir}\n      data-calendar-container\n      {...restProps}\n    >\n      {Array.from({ length: numberOfMonths }, (_, monthIndex) => {\n        const currentDate = currentDates[monthIndex];\n        const currentMonth = currentDate.getMonth();\n        const currentYear = currentDate.getFullYear();\n        const calendarDays = generateCalendarDays(currentMonth, currentYear);\n\n        return (\n          <div key={monthIndex} className='relative'>\n            <div className='flex items-center justify-between mb-4'>\n              <Button\n                variant='ghost'\n                size='icon'\n                onClick={() => navigateMonth('prev')}\n                className={cn(\n                  'flex items-center justify-center h-8 w-8 hover:bg-accent hover:text-accent-foreground',\n                  numberOfMonths === 1 && 'flex',\n                  numberOfMonths > 1 && monthIndex === 0 && 'flex',\n                  numberOfMonths > 1 && monthIndex > 0 && 'hidden',\n                )}\n                aria-label={labels.labelPrevious?.() || 'Previous month'}\n                disabled={fromMonth && currentDate <= fromMonth}\n              >\n                <ChevronLeftIcon className='h-4 w-4' />\n              </Button>\n\n              {renderCaption(monthIndex)}\n\n              <Button\n                variant='ghost'\n                size='icon'\n                onClick={() => navigateMonth('next')}\n                className={cn(\n                  'flex items-center justify-center hover:bg-accent hover:text-accent-foreground',\n                  numberOfMonths === 1 && 'flex',\n                  numberOfMonths > 1 && monthIndex === 0 && 'flex sm:hidden',\n                  numberOfMonths > 1 &&\n                    monthIndex === numberOfMonths - 1 &&\n                    'hidden sm:flex',\n                  numberOfMonths > 1 &&\n                    monthIndex > 0 &&\n                    monthIndex < numberOfMonths - 1 &&\n                    'hidden',\n                )}\n                aria-label={labels.labelNext?.() || 'Next month'}\n                disabled={toMonth && currentDate >= toMonth}\n              >\n                <ChevronRightIcon className='h-4 w-4' />\n              </Button>\n            </div>\n\n            {showMonthPicker && (\n              <div className='absolute top-10 bottom-0 left-0 right-0 z-40 bg-popover border border-border rounded-lg shadow-lg'>\n                <div className='p-4 h-full overflow-y-auto flex justify-center'>\n                  <div className='grid grid-cols-3 gap-3 w-full max-w-sm'>\n                    {months.map((month, index) => (\n                      <Button\n                        key={month}\n                        variant='ghost'\n                        onClick={() => handleMonthSelect(index, monthIndex)}\n                        className={cn(\n                          'h-10 text-sm font-medium justify-center w-full min-w-0 hover:bg-accent hover:text-accent-foreground',\n                          index === currentMonth &&\n                            'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground',\n                        )}\n                      >\n                        {month.slice(0, 3)}\n                      </Button>\n                    ))}\n                  </div>\n                </div>\n              </div>\n            )}\n\n            {showYearPicker && (\n              <div className='absolute top-10 bottom-0 left-0 right-0 z-40 bg-popover border border-border rounded-lg shadow-lg'>\n                <div className='p-4 h-full overflow-y-auto'>\n                  <div className='grid grid-cols-4 gap-2'>\n                    {generateYears().map((year) => (\n                      <Button\n                        key={year}\n                        variant='ghost'\n                        onClick={() => handleYearSelect(year, monthIndex)}\n                        className={cn(\n                          'h-9 text-sm font-medium justify-center hover:bg-accent hover:text-accent-foreground w-full',\n                          year === currentYear &&\n                            'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground',\n                        )}\n                      >\n                        {year}\n                      </Button>\n                    ))}\n                  </div>\n                </div>\n              </div>\n            )}\n\n            <div\n              className={cn(\n                'grid mb-2',\n                showWeekNumber ? 'grid-cols-8' : 'grid-cols-7',\n              )}\n            >\n              {showWeekNumber && (\n                <div\n                  className='flex items-center justify-center text-sm font-normal text-muted-foreground'\n                  style={{ height: 'var(--cell-size, 2rem)' }}\n                >\n                  #\n                </div>\n              )}\n              {weekDayNames.map((day) => (\n                <div\n                  key={day}\n                  className={cn(\n                    'flex items-center justify-center text-xs font-normal text-muted-foreground',\n                    'h-(--cell-size,2rem) min-h-(--cell-size,2rem)',\n                  )}\n                >\n                  {formatters.formatWeekdayName\n                    ? formatters.formatWeekdayName(new Date())\n                    : day}\n                </div>\n              ))}\n            </div>\n\n            <div\n              className={cn(\n                'grid gap-1',\n                showWeekNumber ? 'grid-cols-8' : 'grid-cols-7',\n              )}\n            >\n              {calendarDays.map((dayObj, index) => {\n                const modifiers = getDayModifiers(\n                  dayObj.date,\n                  currentMonth,\n                  currentYear,\n                );\n                const modifiersArray = Object.keys(modifiers).filter(\n                  (key) => modifiers[key],\n                );\n                const dayClassNames = modifiersArray\n                  .map((modifier) => modifiersClassNames[modifier] || '')\n                  .join(' ');\n\n                const DayButtonComponent = CalendarDayButtonComponent;\n\n                return (\n                  <React.Fragment key={`${monthIndex}-${index}`}>\n                    {dayObj.weekNumber !== null && showWeekNumber && (\n                      <div\n                        className={cn(\n                          'flex items-center justify-center text-xs text-muted-foreground',\n                          'h-(--cell-size,2rem) w-(--cell-size,2rem)',\n                          'min-h-(--cell-size,2rem) min-w-(--cell-size,2rem)',\n                        )}\n                      >\n                        {formatters.formatWeekNumber\n                          ? formatters.formatWeekNumber(dayObj.weekNumber)\n                          : dayObj.weekNumber}\n                      </div>\n                    )}\n                    <DayButtonComponent\n                      day={{ date: dayObj.date }}\n                      modifiers={modifiers}\n                      onClick={() => handleDateClick(dayObj.date)}\n                      onMouseEnter={() => handleDateMouseEnter(dayObj.date)}\n                      onMouseLeave={() => handleDateMouseLeave(dayObj.date)}\n                      disabled={modifiers.disabled}\n                      className={cn(\n                        'text-xs font-normal flex flex-col items-center justify-center p-0.5 relative gap-0.5',\n                        'h-(--cell-size,2rem) w-(--cell-size,2rem)',\n                        'min-h-(--cell-size,2rem) min-w-(--cell-size,2rem)',\n                        'hover:bg-accent hover:text-accent-foreground',\n                        !dayObj.isCurrentMonth &&\n                          showOutsideDays &&\n                          'text-muted-foreground opacity-40',\n                        !dayObj.isCurrentMonth &&\n                          !showOutsideDays &&\n                          'invisible',\n                        modifiers.selected &&\n                          'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground',\n                        modifiers.today &&\n                          !modifiers.selected &&\n                          'bg-accent text-accent-foreground font-semibold',\n                        modifiers.range_start &&\n                          'bg-primary text-primary-foreground rounded-l-md rounded-r-none',\n                        modifiers.range_end &&\n                          'bg-primary text-primary-foreground rounded-r-md rounded-l-none',\n                        modifiers.range_middle &&\n                          'bg-accent text-accent-foreground rounded-none',\n                        modifiers.range_middle_preview &&\n                          'bg-muted text-muted-foreground rounded-none opacity-50',\n                        modifiers.range_end_preview &&\n                          'bg-primary/50 text-primary-foreground rounded-r-md rounded-l-none opacity-50',\n                        modifiers.disabled &&\n                          'text-muted-foreground opacity-50 cursor-not-allowed hover:bg-transparent',\n                        dayClassNames,\n                        classNames.day,\n                      )}\n                      data-date={dayObj.date.toISOString()}\n                      data-modifiers={modifiersArray.join(' ')}\n                    >\n                      {formatters.formatDay\n                        ? formatters.formatDay(dayObj.date)\n                        : dayObj.day}\n                    </DayButtonComponent>\n                  </React.Fragment>\n                );\n              })}\n            </div>\n\n            {footer && (\n              <div className='mt-4 text-center text-sm text-muted-foreground'>\n                {footer}\n              </div>\n            )}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nexport { Calendar, CalendarDayButton };\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/button.tsx",
      "content": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',\n        outline:\n          'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n        ghost: 'hover:bg-accent hover:text-accent-foreground',\n        link: 'text-primary underline-offset-4 hover:underline',\n        success: 'bg-green-600 text-white hover:bg-green-700',\n        warning: 'bg-yellow-500 text-black hover:bg-yellow-600',\n        info: 'bg-blue-500 text-white hover:bg-blue-600',\n        dark: 'bg-gray-800 text-white hover:bg-gray-700',\n        light: 'bg-gray-100 text-gray-800 hover:bg-gray-200',\n        gradient:\n          'bg-linear-to-r from-indigo-500 via-purple-500 to-pink-500 text-white hover:opacity-90',\n        glass:\n          'bg-white/10 backdrop-blur-md text-white border border-white/20 hover:bg-white/20',\n      },\n      size: {\n        default: 'h-9 px-4 py-2',\n        sm: 'h-8 rounded-md px-3 text-xs',\n        lg: 'h-10 rounded-md px-8',\n        icon: 'h-9 w-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nexport interface ButtonProps\n  extends\n    React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : 'button';\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    );\n  },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
