{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "range-calendar",
  "title": "Range Calendar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/range-calendar/RangeCalendar.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { DayPicker, type DateRange } from 'react-day-picker'\nimport { cn } from '@/lib/utils'\nimport { buttonVariants } from '@/components/ui/button'\n\n// `selected`/`onSelect` are re-declared explicitly: a bare\n// Omit<ComponentProps<DayPicker>, 'mode'> distributes over DayPicker's prop\n// union and drops `selected` (not common to every mode), so consumers couldn't\n// pass a range. Forcing mode=\"range\" makes the range shape exact.\nexport interface RangeCalendarProps\n  extends Omit<React.ComponentProps<typeof DayPicker>, 'mode' | 'selected' | 'onSelect'> {\n  selected?: DateRange\n  onSelect?: (range: DateRange | undefined) => void\n}\n\n/**\n * Date-range calendar — click two dates and the range fills in between. The\n * React mirror of the reka-ui-based Vue RangeCalendar: range start/end cells\n * get the primary fill, the middle gets the accent fill, with the same cell\n * sizes, today / outside / disabled states, and outline nav chevrons mapped\n * 1:1 from the Vue registry's Tailwind class strings. Forces `mode=\"range\"`.\n */\nfunction RangeCalendar({ className, classNames, showOutsideDays = true, selected, onSelect, ...props }: RangeCalendarProps) {\n  return (\n    <DayPicker\n      mode=\"range\"\n      selected={selected}\n      onSelect={onSelect}\n      data-uipkge=\"\"\n      data-slot=\"range-calendar\"\n      showOutsideDays={showOutsideDays}\n      className={cn('p-3', className)}\n      classNames={{\n        // react-day-picker v10 renders <Nav> as the FIRST child of .rdp-months\n        // (a sibling of the month, NOT inside month_caption). The nav is\n        // `absolute inset-x-0 top-0`, so .rdp-months MUST be `relative` or the\n        // chevrons escape to the nearest positioned ancestor and float away from\n        // the calendar. months relative + an h-9 caption overlays the nav on the\n        // caption row, prev/next pinned to the inset-x edges — matching Vue.\n        // No mt-4: rdp's .rdp-months wraps the nav + caption, so a top margin\n        // would push the header down 16px vs Vue (where mt-4 sits on the grid\n        // wrapper). Caption→grid gap is the Month's gap-4.\n        months: 'relative flex flex-col gap-y-4 sm:flex-row sm:gap-x-4 sm:gap-y-0',\n        // flex-1: the extra Month wrapper (Months > Month > grid) collapses to the\n        // grid's intrinsic width in the sm:flex-row track, leaving the grid narrow\n        // and the centered caption drifting left. flex-1 makes Month fill its track\n        // → full-width grid + centered label, matching Vue.\n        month: 'flex flex-1 flex-col gap-4',\n        // Label-height (no h-9) so the nav's top-0 aligns with the caption label,\n        // matching Vue (size-9 buttons overflow below the heading-sized header).\n        month_caption: 'flex items-center justify-center',\n        caption_label: 'text-sm font-medium',\n        // Vue's RangeCalendar nav is NOT the single Calendar's nav: it uses the\n        // smaller size-7 / opacity-50 buttons pinned 4px in (left-1 / right-1),\n        // not size-9 / opacity-70 at the inset-x-0 edge. inset-x-1 reproduces the\n        // left-1 / right-1 offset; the buttons match RangeCalendarPrev/NextButton.\n        nav: 'absolute inset-x-1 top-0 flex items-center justify-between',\n        button_previous: cn(\n          buttonVariants({ variant: 'outline' }),\n          'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',\n        ),\n        button_next: cn(\n          buttonVariants({ variant: 'outline' }),\n          'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',\n        ),\n        month_grid: 'w-full border-collapse space-y-1',\n        weekdays: 'flex',\n        weekday: 'text-muted-foreground flex-1 rounded-md text-xs font-normal',\n        week: 'mt-2 flex w-full',\n        day: cn(\n          'relative flex-1 p-0 text-center text-sm focus-within:relative focus-within:z-20',\n          '[&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md',\n          '[&:has([aria-selected].day-outside)]:bg-accent/50 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md',\n        ),\n        day_button: cn(\n          buttonVariants({ variant: 'ghost' }),\n          'h-8 w-8 cursor-pointer p-0 font-normal aria-selected:opacity-100',\n        ),\n        today: '[&:not([aria-selected])]:bg-accent [&:not([aria-selected])]:text-accent-foreground',\n        outside: 'day-outside text-muted-foreground aria-selected:text-muted-foreground',\n        disabled: 'text-muted-foreground opacity-50',\n        // Range start / end → primary fill (mirrors Vue's data-[selection-start] / -end)\n        range_start:\n          'day-range-start rounded-l-md [&>button]:bg-primary [&>button]:text-primary-foreground [&>button:hover]:bg-primary [&>button:hover]:text-primary-foreground [&>button:focus]:bg-primary [&>button:focus]:text-primary-foreground',\n        range_end:\n          'day-range-end rounded-r-md [&>button]:bg-primary [&>button]:text-primary-foreground [&>button:hover]:bg-primary [&>button:hover]:text-primary-foreground [&>button:focus]:bg-primary [&>button:focus]:text-primary-foreground',\n        // Range middle → accent fill\n        range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground [&>button]:hover:bg-accent',\n        hidden: 'invisible',\n        ...classNames,\n      }}\n      formatters={{\n        // Match Vue's narrow (single-letter) weekday labels; react-day-picker\n        // defaults to short 'cccccc' (two-letter \"Mo\"). Locale-aware narrow.\n        formatWeekdayName: (weekday, options) =>\n          weekday.toLocaleDateString(options?.locale?.code, { weekday: 'narrow' }),\n        ...props.formatters,\n      }}\n      components={{\n        Chevron: ({ orientation, className: chevronClassName, ...chevronProps }) =>\n          orientation === 'left' ? (\n            <ChevronLeft className={cn('size-4', chevronClassName)} aria-hidden=\"true\" {...chevronProps} />\n          ) : (\n            <ChevronRight className={cn('size-4', chevronClassName)} aria-hidden=\"true\" {...chevronProps} />\n          ),\n      }}\n      {...props}\n    />\n  )\n}\nRangeCalendar.displayName = 'RangeCalendar'\n\nexport { RangeCalendar }\n",
      "type": "registry:ui",
      "target": "~/components/ui/range-calendar/RangeCalendar.tsx"
    },
    {
      "path": "packages/registry-react/components/range-calendar/index.ts",
      "content": "export { RangeCalendar, type RangeCalendarProps } from './RangeCalendar'\n",
      "type": "registry:ui",
      "target": "~/components/ui/range-calendar/index.ts"
    }
  ],
  "dependencies": [
    "react-day-picker",
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/button.json"
  ],
  "description": "Calendar variant for from/to date selection — click two dates and the range fills in between. Same min/max and disabled-date support as the single-date Calendar.",
  "categories": [
    "date-time"
  ]
}