{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "monthrangepicker",
  "type": "registry:ui",
  "title": "Month Range Picker",
  "author": "@greenkdev",
  "description": "A month range picker component.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "src/components/ui/monthrangepicker.tsx",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { Button, buttonVariants } from \"./button\";\nimport { cn } from \"@/lib/utils\";\n\nconst addMonths = (input: Date, months: number) => {\n    const date = new Date(input);\n    date.setDate(1);\n    date.setMonth(date.getMonth() + months);\n    date.setDate(Math.min(input.getDate(), getDaysInMonth(date.getFullYear(), date.getMonth() + 1)));\n    return date;\n};\nconst getDaysInMonth = (year: number, month: number) => new Date(year, month, 0).getDate();\n\ntype Month = {\n    number: number;\n    name: string;\n    yearOffset: number;\n};\n\nconst MONTHS: Month[][] = [\n    [\n        { number: 0, name: \"Jan\", yearOffset: 0 },\n        { number: 1, name: \"Feb\", yearOffset: 0 },\n        { number: 2, name: \"Mar\", yearOffset: 0 },\n        { number: 3, name: \"Apr\", yearOffset: 0 },\n        { number: 0, name: \"Jan\", yearOffset: 1 },\n        { number: 1, name: \"Feb\", yearOffset: 1 },\n        { number: 2, name: \"Mar\", yearOffset: 1 },\n        { number: 3, name: \"Apr\", yearOffset: 1 },\n    ],\n    [\n        { number: 4, name: \"May\", yearOffset: 0 },\n        { number: 5, name: \"Jun\", yearOffset: 0 },\n        { number: 6, name: \"Jul\", yearOffset: 0 },\n        { number: 7, name: \"Aug\", yearOffset: 0 },\n        { number: 4, name: \"May\", yearOffset: 1 },\n        { number: 5, name: \"Jun\", yearOffset: 1 },\n        { number: 6, name: \"Jul\", yearOffset: 1 },\n        { number: 7, name: \"Aug\", yearOffset: 1 },\n    ],\n    [\n        { number: 8, name: \"Sep\", yearOffset: 0 },\n        { number: 9, name: \"Oct\", yearOffset: 0 },\n        { number: 10, name: \"Nov\", yearOffset: 0 },\n        { number: 11, name: \"Dec\", yearOffset: 0 },\n        { number: 8, name: \"Sep\", yearOffset: 1 },\n        { number: 9, name: \"Oct\", yearOffset: 1 },\n        { number: 10, name: \"Nov\", yearOffset: 1 },\n        { number: 11, name: \"Dec\", yearOffset: 1 },\n    ],\n];\n\ntype QuickSelector = {\n    label: string;\n    startMonth: Date;\n    endMonth: Date;\n    variant?: ButtonVariant;\n    onClick?: (selector: QuickSelector) => void;\n};\n\nconst QUICK_SELECTORS: QuickSelector[] = [\n    { label: \"This year\", startMonth: new Date(new Date().getFullYear(), 0), endMonth: new Date(new Date().getFullYear(), 11) },\n    { label: \"Last year\", startMonth: new Date(new Date().getFullYear() - 1, 0), endMonth: new Date(new Date().getFullYear() - 1, 11) },\n    { label: \"Last 6 months\", startMonth: new Date(addMonths(new Date(), -6)), endMonth: new Date() },\n    { label: \"Last 12 months\", startMonth: new Date(addMonths(new Date(), -12)), endMonth: new Date() },\n];\n\ntype MonthRangeCalProps = {\n    selectedMonthRange?: { start: Date; end: Date };\n    onStartMonthSelect?: (date: Date) => void;\n    onMonthRangeSelect?: ({ start, end }: { start: Date; end: Date }) => void;\n    onYearForward?: () => void;\n    onYearBackward?: () => void;\n    callbacks?: {\n        yearLabel?: (year: number) => string;\n        monthLabel?: (month: Month) => string;\n    };\n    variant?: {\n        calendar?: {\n            main?: ButtonVariant;\n            selected?: ButtonVariant;\n        };\n        chevrons?: ButtonVariant;\n    };\n    minDate?: Date;\n    maxDate?: Date;\n    quickSelectors?: QuickSelector[];\n    showQuickSelectors?: boolean;\n};\n\ntype ButtonVariant = \"default\" | \"outline\" | \"ghost\" | \"link\" | \"destructive\" | \"secondary\" | null | undefined;\n\nfunction MonthRangePicker({\n    onMonthRangeSelect,\n    onStartMonthSelect,\n    callbacks,\n    selectedMonthRange,\n    onYearBackward,\n    onYearForward,\n    variant,\n    minDate,\n    maxDate,\n    quickSelectors,\n    showQuickSelectors,\n    className,\n    ...props\n}: React.HTMLAttributes<HTMLDivElement> & MonthRangeCalProps) {\n    return (\n        <div className={cn(\"min-w-[400px]  p-3\", className)} {...props}>\n            <div className=\"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0\">\n                <div className=\"w-full\">\n                    <MonthRangeCal\n                        onMonthRangeSelect={onMonthRangeSelect}\n                        onStartMonthSelect={onStartMonthSelect}\n                        callbacks={callbacks}\n                        selectedMonthRange={selectedMonthRange}\n                        onYearBackward={onYearBackward}\n                        onYearForward={onYearForward}\n                        variant={variant}\n                        minDate={minDate}\n                        maxDate={maxDate}\n                        quickSelectors={quickSelectors}\n                        showQuickSelectors={showQuickSelectors}\n                    ></MonthRangeCal>\n                </div>\n            </div>\n        </div>\n    );\n}\n\nfunction MonthRangeCal({\n    selectedMonthRange,\n    onMonthRangeSelect,\n    onStartMonthSelect,\n    callbacks,\n    variant,\n    minDate,\n    maxDate,\n    quickSelectors = QUICK_SELECTORS,\n    showQuickSelectors = true,\n    onYearBackward,\n    onYearForward,\n}: MonthRangeCalProps) {\n    const [startYear, setStartYear] = React.useState<number>(selectedMonthRange?.start.getFullYear() ?? new Date().getFullYear());\n    const [startMonth, setStartMonth] = React.useState<number>(selectedMonthRange?.start?.getMonth() ?? new Date().getMonth());\n    const [endYear, setEndYear] = React.useState<number>(selectedMonthRange?.end?.getFullYear() ?? new Date().getFullYear() + 1);\n    const [endMonth, setEndMonth] = React.useState<number>(selectedMonthRange?.end?.getMonth() ?? new Date().getMonth());\n    const [rangePending, setRangePending] = React.useState<boolean>(false);\n    const [endLocked, setEndLocked] = React.useState<boolean>(true);\n    const [menuYear, setMenuYear] = React.useState<number>(startYear);\n\n    if (minDate && maxDate && minDate > maxDate) minDate = maxDate;\n\n    return (\n        <div className=\"flex gap-4\">\n            <div className=\"min-w-[400px] space-y-4\">\n                <div className=\"flex justify-evenly pt-1 relative items-center\">\n                    <div className=\"text-sm font-medium\">{callbacks?.yearLabel ? callbacks?.yearLabel(menuYear) : menuYear}</div>\n                    <div className=\"space-x-1 flex items-center\">\n                        <button\n                            onClick={() => {\n                                setMenuYear(menuYear - 1);\n                                if (onYearBackward) onYearBackward();\n                            }}\n                            className={cn(buttonVariants({ variant: variant?.chevrons ?? \"outline\" }), \"inline-flex items-center justify-center h-7 w-7 p-0 absolute left-1\")}\n                        >\n                            <ChevronLeft className=\"opacity-50 h-4 w-4\" />\n                        </button>\n                        <button\n                            onClick={() => {\n                                setMenuYear(menuYear + 1);\n                                if (onYearForward) onYearForward();\n                            }}\n                            className={cn(buttonVariants({ variant: variant?.chevrons ?? \"outline\" }), \"inline-flex items-center justify-center h-7 w-7 p-0 absolute right-1\")}\n                        >\n                            <ChevronRight className=\"opacity-50 h-4 w-4\" />\n                        </button>\n                    </div>\n                    <div className=\"text-sm font-medium\">{callbacks?.yearLabel ? callbacks?.yearLabel(menuYear + 1) : menuYear + 1}</div>\n                </div>\n                <table className=\"w-full border-collapse space-y-1\">\n                    <tbody>\n                        {MONTHS.map((monthRow, a) => {\n                            return (\n                                <tr key={\"row-\" + a} className=\"flex w-full mt-2\">\n                                    {monthRow.map((m, i) => {\n                                        return (\n                                            <td\n                                                key={m.number + \"-\" + m.yearOffset}\n                                                className={cn(\n                                                    cn(\n                                                        cn(\n                                                            cn(\n                                                                \"h-10 w-1/4 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20\",\n                                                                (menuYear + m.yearOffset > startYear || (menuYear + m.yearOffset == startYear && m.number > startMonth)) &&\n                                                                    (menuYear + m.yearOffset < endYear || (menuYear + m.yearOffset == endYear && m.number < endMonth)) &&\n                                                                    (rangePending || endLocked)\n                                                                    ? \"text-accent-foreground bg-accent\"\n                                                                    : \"\"\n                                                            ),\n                                                            menuYear + m.yearOffset == startYear && m.number == startMonth && (rangePending || endLocked)\n                                                                ? \"text-accent-foreground bg-accent rounded-l-md\"\n                                                                : \"\"\n                                                        ),\n                                                        menuYear + m.yearOffset == endYear &&\n                                                            m.number == endMonth &&\n                                                            (rangePending || endLocked) &&\n                                                            menuYear + m.yearOffset >= startYear &&\n                                                            m.number >= startMonth\n                                                            ? \"text-accent-foreground bg-accent rounded-r-md\"\n                                                            : \"\"\n                                                    ),\n                                                    i == 3 ? \"mr-2\" : i == 4 ? \"ml-2\" : \"\"\n                                                )}\n                                                onMouseEnter={() => {\n                                                    if (rangePending && !endLocked) {\n                                                        setEndYear(menuYear + m.yearOffset);\n                                                        setEndMonth(m.number);\n                                                    }\n                                                }}\n                                            >\n                                                <button\n                                                    onClick={() => {\n                                                        if (rangePending) {\n                                                            if (menuYear + m.yearOffset < startYear || (menuYear + m.yearOffset == startYear && m.number < startMonth)) {\n                                                                setRangePending(true);\n                                                                setEndLocked(false);\n                                                                setStartMonth(m.number);\n                                                                setStartYear(menuYear + m.yearOffset);\n                                                                setEndYear(menuYear + m.yearOffset);\n                                                                setEndMonth(m.number);\n                                                                if (onStartMonthSelect) onStartMonthSelect(new Date(menuYear + m.yearOffset, m.number));\n                                                            } else {\n                                                                setRangePending(false);\n                                                                setEndLocked(true);\n                                                                // Event fire data selected\n\n                                                                if (onMonthRangeSelect)\n                                                                    onMonthRangeSelect({ start: new Date(startYear, startMonth), end: new Date(menuYear + m.yearOffset, m.number) });\n                                                            }\n                                                        } else {\n                                                            setRangePending(true);\n                                                            setEndLocked(false);\n                                                            setStartMonth(m.number);\n                                                            setStartYear(menuYear + m.yearOffset);\n                                                            setEndYear(menuYear + m.yearOffset);\n                                                            setEndMonth(m.number);\n                                                            if (onStartMonthSelect) onStartMonthSelect(new Date(menuYear + m.yearOffset, m.number));\n                                                        }\n                                                    }}\n                                                    disabled={\n                                                        (maxDate\n                                                            ? menuYear + m.yearOffset > maxDate?.getFullYear() || (menuYear + m.yearOffset == maxDate?.getFullYear() && m.number > maxDate.getMonth())\n                                                            : false) ||\n                                                        (minDate\n                                                            ? menuYear + m.yearOffset < minDate?.getFullYear() || (menuYear + m.yearOffset == minDate?.getFullYear() && m.number < minDate.getMonth())\n                                                            : false)\n                                                    }\n                                                    className={cn(\n                                                        buttonVariants({\n                                                            variant:\n                                                                (startMonth == m.number && menuYear + m.yearOffset == startYear) ||\n                                                                (endMonth == m.number && menuYear + m.yearOffset == endYear && !rangePending)\n                                                                    ? variant?.calendar?.selected ?? \"default\"\n                                                                    : variant?.calendar?.main ?? \"ghost\",\n                                                        }),\n                                                        \"h-full w-full p-0 font-normal aria-selected:opacity-100\"\n                                                    )}\n                                                >\n                                                    {callbacks?.monthLabel ? callbacks.monthLabel(m) : m.name}\n                                                </button>\n                                            </td>\n                                        );\n                                    })}\n                                </tr>\n                            );\n                        })}\n                    </tbody>\n                </table>\n            </div>\n\n            {showQuickSelectors ? (\n                <div className=\" flex flex-col gap-1 justify-center\">\n                    {quickSelectors.map((s) => {\n                        return (\n                            <Button\n                                onClick={() => {\n                                    setStartYear(s.startMonth.getFullYear());\n                                    setStartMonth(s.startMonth.getMonth());\n                                    setEndYear(s.endMonth.getFullYear());\n                                    setEndMonth(s.endMonth.getMonth());\n                                    setRangePending(false);\n                                    setEndLocked(true);\n                                    if (onMonthRangeSelect) onMonthRangeSelect({ start: s.startMonth, end: s.endMonth });\n                                    if (s.onClick) s.onClick(s);\n                                }}\n                                key={s.label}\n                                variant={s.variant ?? \"outline\"}\n                            >\n                                {s.label}\n                            </Button>\n                        );\n                    })}\n                </div>\n            ) : null}\n        </div>\n    );\n}\n\nMonthRangePicker.displayName = \"MonthRangePicker\";\n\nexport { MonthRangePicker };\n",
      "type": "registry:ui"
    }
  ]
}