{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar",
  "type": "registry:ui",
  "title": "Calendar and RangeCalendar",
  "description": "Accessible React Aria Calendar and RangeCalendar surfaces with Dethink token-backed month navigation, date cells, range states, density, RTL, and theme styling.",
  "dependencies": [
    "@internationalized/date@^3.12.4",
    "react-aria-components@^1.21.1"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/calendar/calendar.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/calendar/calendar.tsx",
      "content": "import {\n  Button as AriaButton,\n  Calendar as AriaCalendar,\n  CalendarCell as AriaCalendarCell,\n  CalendarGrid,\n  CalendarGridBody,\n  CalendarGridHeader,\n  CalendarMonthPicker,\n  CalendarStateContext,\n  CalendarYearPicker,\n  CalendarHeaderCell,\n  I18nProvider,\n  RangeCalendar as AriaRangeCalendar,\n  RangeCalendarStateContext,\n  type CalendarGridProps,\n  type CalendarProps as AriaCalendarProps,\n  type RangeCalendarProps as AriaRangeCalendarProps,\n} from \"react-aria-components\";\nimport {\n  forwardRef,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n  type ForwardedRef,\n  type ReactElement,\n  type RefAttributes,\n} from \"react\";\nimport {\n  endOfMonth,\n  endOfYear,\n  startOfMonth,\n  startOfYear,\n  toCalendarDate,\n  type CalendarDate,\n  type DateValue,\n} from \"@internationalized/date\";\nimport { cn } from \"../../utils/cn\";\n\nexport type CalendarWeekStartsOn =\n  \"sun\" | \"mon\" | \"tue\" | \"wed\" | \"thu\" | \"fri\" | \"sat\";\n\nexport interface CalendarProps<T extends DateValue = DateValue> extends Omit<\n  AriaCalendarProps<T>,\n  | \"children\"\n  | \"className\"\n  | \"firstDayOfWeek\"\n  | \"isDisabled\"\n  | \"isInvalid\"\n  | \"onChange\"\n> {\n  className?: string;\n  disabled?: boolean;\n  invalid?: boolean;\n  locale?: string;\n  onValueChange?: AriaCalendarProps<T>[\"onChange\"];\n  weekdayStyle?: CalendarGridProps[\"weekdayStyle\"];\n  weekStartsOn?: CalendarWeekStartsOn;\n}\n\nexport interface RangeCalendarProps<\n  T extends DateValue = DateValue,\n> extends Omit<\n  AriaRangeCalendarProps<T>,\n  | \"children\"\n  | \"className\"\n  | \"firstDayOfWeek\"\n  | \"isDisabled\"\n  | \"isInvalid\"\n  | \"onChange\"\n> {\n  className?: string;\n  disabled?: boolean;\n  invalid?: boolean;\n  locale?: string;\n  onValueChange?: AriaRangeCalendarProps<T>[\"onChange\"];\n  weekdayStyle?: CalendarGridProps[\"weekdayStyle\"];\n  weekStartsOn?: CalendarWeekStartsOn;\n}\n\nexport interface DateCalendarGridProps {\n  dataSlotPrefix?: string;\n  range?: boolean;\n  weekdayStyle?: CalendarGridProps[\"weekdayStyle\"];\n}\n\ntype CalendarViewMode = \"day\" | \"month\" | \"year\";\n\nconst calendarRootBaseClasses =\n  \"group/calendar grid w-fit min-w-0 gap-[var(--dt-space-3)] rounded-md border border-border bg-background p-[var(--dt-space-3)] text-foreground shadow-sm data-[disabled]:opacity-60 data-[invalid]:border-destructive\";\n\nconst rangeCalendarRootBaseClasses =\n  \"group/range-calendar grid w-fit min-w-0 gap-[var(--dt-space-3)] rounded-md border border-border bg-background p-[var(--dt-space-3)] text-foreground shadow-sm data-[disabled]:opacity-60 data-[invalid]:border-destructive\";\n\nconst calendarHeaderClasses =\n  \"grid grid-cols-[2rem_minmax(0,1fr)_2rem] items-center gap-[var(--dt-space-1)]\";\n\nconst calendarHeadingGroupClasses =\n  \"flex min-w-0 items-center justify-center gap-0\";\n\nconst calendarHeadingButtonClasses =\n  \"inline-flex h-8 items-center justify-center rounded-md px-[var(--dt-space-1)] text-center text-sm font-medium text-foreground outline-none motion-safe:transition-[background-color,color,box-shadow] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring data-[active=true]:bg-muted\";\n\nconst calendarButtonClasses =\n  \"inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none motion-safe:transition-[background-color,color,translate,scale] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring motion-safe:active:translate-y-px disabled:pointer-events-none disabled:opacity-40\";\n\nconst calendarPickerGridClasses =\n  \"grid h-full w-full grid-cols-3 grid-rows-4 gap-[var(--dt-space-2)]\";\n\nconst calendarPanelClasses = \"grid min-h-64 w-64 items-stretch\";\n\nconst calendarPickerOptionClasses =\n  \"inline-flex h-full min-h-9 min-w-0 items-center justify-center rounded-md px-[var(--dt-space-2)] py-[var(--dt-space-1)] text-sm font-medium text-foreground outline-none motion-safe:transition-[background-color,color,box-shadow,translate,scale] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring motion-safe:active:translate-y-px disabled:pointer-events-none disabled:opacity-35 data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground\";\n\nconst calendarGridClasses =\n  \"w-full border-separate border-spacing-[var(--dt-space-1)] text-sm\";\n\nconst calendarHeaderCellClasses =\n  \"size-8 text-center text-xs font-medium text-muted-foreground\";\n\nconst calendarCellBaseClasses =\n  \"flex size-8 items-center justify-center rounded-md text-center text-sm leading-none tabular-nums outline-none motion-safe:transition-[background-color,color,box-shadow,translate,scale] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-35 data-[focused]:ring-2 data-[focused]:ring-ring data-[invalid]:text-destructive data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 motion-safe:data-[pressed]:scale-95 data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[today]:font-semibold data-[unavailable]:text-destructive data-[unavailable]:line-through\";\n\nconst rangeCalendarCellBaseClasses =\n  \"flex size-8 items-center justify-center rounded-md text-center text-sm leading-none tabular-nums outline-none motion-safe:transition-[background-color,color,box-shadow,translate,scale] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-35 data-[focused]:ring-2 data-[focused]:ring-ring data-[invalid]:text-destructive data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 motion-safe:data-[pressed]:scale-95 data-[selected]:bg-primary/15 data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-start]:bg-primary data-[selection-start]:text-primary-foreground data-[today]:font-semibold data-[unavailable]:text-destructive data-[unavailable]:line-through\";\n\ntype CalendarComponent = (<T extends DateValue = DateValue>(\n  props: CalendarProps<T> & RefAttributes<HTMLDivElement>,\n) => ReactElement | null) & { displayName?: string };\n\ntype RangeCalendarComponent = (<T extends DateValue = DateValue>(\n  props: RangeCalendarProps<T> & RefAttributes<HTMLDivElement>,\n) => ReactElement | null) & { displayName?: string };\n\nexport function calendarClassNames({\n  className,\n}: Pick<CalendarProps, \"className\"> = {}) {\n  return cn(calendarRootBaseClasses, className);\n}\n\nexport function rangeCalendarClassNames({\n  className,\n}: Pick<RangeCalendarProps, \"className\"> = {}) {\n  return cn(rangeCalendarRootBaseClasses, className);\n}\n\nexport function calendarCellClassNames({\n  className,\n}: {\n  className?: string;\n} = {}) {\n  return cn(calendarCellBaseClasses, className);\n}\n\nexport function rangeCalendarCellClassNames({\n  className,\n}: {\n  className?: string;\n} = {}) {\n  return cn(rangeCalendarCellBaseClasses, className);\n}\n\nfunction ChevronLeftIcon() {\n  return (\n    <svg aria-hidden=\"true\" className=\"size-4\" fill=\"none\" viewBox=\"0 0 16 16\">\n      <path\n        d=\"m9.5 4-4 4 4 4\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.75\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon() {\n  return (\n    <svg aria-hidden=\"true\" className=\"size-4\" fill=\"none\" viewBox=\"0 0 16 16\">\n      <path\n        d=\"m6.5 4 4 4-4 4\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.75\"\n      />\n    </svg>\n  );\n}\n\nfunction useCalendarState() {\n  const calendarState = useContext(CalendarStateContext);\n  const rangeCalendarState = useContext(RangeCalendarStateContext);\n\n  return calendarState ?? rangeCalendarState;\n}\n\nfunction isCalendarDateRangeOutsideBounds({\n  endDate,\n  startDate,\n  state,\n}: {\n  endDate: CalendarDate;\n  startDate: CalendarDate;\n  state: ReturnType<typeof useCalendarState>;\n}) {\n  if (!state) {\n    return true;\n  }\n\n  const minValue = state.minValue ? toCalendarDate(state.minValue) : null;\n  const maxValue = state.maxValue ? toCalendarDate(state.maxValue) : null;\n\n  return Boolean(\n    (minValue && endDate.compare(minValue) < 0) ||\n    (maxValue && startDate.compare(maxValue) > 0),\n  );\n}\n\nfunction CalendarNavigationButton({\n  dataSlotPrefix,\n  direction,\n  viewMode,\n}: {\n  dataSlotPrefix: string;\n  direction: \"next\" | \"previous\";\n  viewMode: CalendarViewMode;\n}) {\n  const state = useCalendarState();\n  const isPrevious = direction === \"previous\";\n\n  if (viewMode === \"day\") {\n    return (\n      <AriaButton\n        slot={isPrevious ? \"previous\" : \"next\"}\n        aria-label={isPrevious ? \"Previous month\" : \"Next month\"}\n        className={calendarButtonClasses}\n        data-slot={`${dataSlotPrefix}-${direction}`}\n      >\n        {isPrevious ? <ChevronLeftIcon /> : <ChevronRightIcon />}\n      </AriaButton>\n    );\n  }\n\n  const years = viewMode === \"year\" ? 12 : 1;\n  const targetDate = state?.focusedDate.add({\n    years: isPrevious ? -years : years,\n  });\n  const isTargetOutsideBounds = targetDate\n    ? isCalendarDateRangeOutsideBounds({\n        endDate:\n          viewMode === \"year\" ? endOfYear(targetDate) : endOfMonth(targetDate),\n        startDate:\n          viewMode === \"year\"\n            ? startOfYear(targetDate)\n            : startOfMonth(targetDate),\n        state,\n      })\n    : true;\n  const label =\n    viewMode === \"year\"\n      ? isPrevious\n        ? \"Previous 12 years\"\n        : \"Next 12 years\"\n      : isPrevious\n        ? \"Previous year\"\n        : \"Next year\";\n\n  return (\n    <button\n      aria-label={label}\n      className={calendarButtonClasses}\n      data-slot={`${dataSlotPrefix}-${direction}`}\n      disabled={!state || state.isDisabled || isTargetOutsideBounds}\n      type=\"button\"\n      onClick={() => {\n        if (targetDate) {\n          state?.setFocusedDate(targetDate);\n        }\n      }}\n    >\n      {isPrevious ? <ChevronLeftIcon /> : <ChevronRightIcon />}\n    </button>\n  );\n}\n\nfunction CalendarPickerHeader({\n  dataSlotPrefix,\n  setViewMode,\n  viewMode,\n}: {\n  dataSlotPrefix: string;\n  setViewMode: (viewMode: CalendarViewMode) => void;\n  viewMode: CalendarViewMode;\n}) {\n  return (\n    <div\n      data-slot={`${dataSlotPrefix}-header`}\n      className={calendarHeaderClasses}\n    >\n      <CalendarNavigationButton\n        dataSlotPrefix={dataSlotPrefix}\n        direction=\"previous\"\n        viewMode={viewMode}\n      />\n      <div\n        data-slot={`${dataSlotPrefix}-heading`}\n        className={calendarHeadingGroupClasses}\n      >\n        <CalendarMonthPicker format=\"long\">\n          {(monthPicker) => {\n            const month = monthPicker.items.find(\n              (item) => item.id === monthPicker.value,\n            );\n\n            return (\n              <button\n                aria-label={`Choose month, current ${month?.formatted ?? \"month\"}`}\n                className={calendarHeadingButtonClasses}\n                data-active={viewMode === \"month\" ? \"true\" : undefined}\n                data-slot={`${dataSlotPrefix}-month-trigger`}\n                type=\"button\"\n                onClick={() =>\n                  setViewMode(viewMode === \"month\" ? \"day\" : \"month\")\n                }\n              >\n                {month?.formatted}\n              </button>\n            );\n          }}\n        </CalendarMonthPicker>\n        <CalendarYearPicker visibleYears={12}>\n          {(yearPicker) => {\n            const year = yearPicker.items.find(\n              (item) => item.id === yearPicker.value,\n            );\n\n            return (\n              <button\n                aria-label={`Choose year, current ${year?.formatted ?? \"year\"}`}\n                className={calendarHeadingButtonClasses}\n                data-active={viewMode === \"year\" ? \"true\" : undefined}\n                data-slot={`${dataSlotPrefix}-year-trigger`}\n                type=\"button\"\n                onClick={() =>\n                  setViewMode(viewMode === \"year\" ? \"day\" : \"year\")\n                }\n              >\n                {year?.formatted}\n              </button>\n            );\n          }}\n        </CalendarYearPicker>\n      </div>\n      <CalendarNavigationButton\n        dataSlotPrefix={dataSlotPrefix}\n        direction=\"next\"\n        viewMode={viewMode}\n      />\n    </div>\n  );\n}\n\nfunction CalendarMonthSelection({\n  dataSlotPrefix,\n  onSelect,\n}: {\n  dataSlotPrefix: string;\n  onSelect: () => void;\n}) {\n  const state = useCalendarState();\n\n  return (\n    <CalendarMonthPicker format=\"short\">\n      {(monthPicker) => (\n        <div\n          aria-label={monthPicker[\"aria-label\"]}\n          className={calendarPickerGridClasses}\n          data-slot={`${dataSlotPrefix}-month-picker`}\n          role=\"group\"\n        >\n          {monthPicker.items.map((item) => {\n            const isSelected = item.id === monthPicker.value;\n            const isDisabled =\n              Boolean(state?.isDisabled) ||\n              isCalendarDateRangeOutsideBounds({\n                endDate: endOfMonth(item.date),\n                startDate: startOfMonth(item.date),\n                state,\n              });\n\n            return (\n              <button\n                aria-label={item.formatted}\n                className={calendarPickerOptionClasses}\n                data-selected={isSelected ? \"true\" : undefined}\n                data-slot={`${dataSlotPrefix}-month-option`}\n                disabled={isDisabled}\n                key={item.id}\n                type=\"button\"\n                onClick={() => {\n                  monthPicker.onChange(item.id);\n                  onSelect();\n                }}\n              >\n                {item.formatted}\n              </button>\n            );\n          })}\n        </div>\n      )}\n    </CalendarMonthPicker>\n  );\n}\n\nfunction CalendarYearSelection({\n  dataSlotPrefix,\n  onSelect,\n}: {\n  dataSlotPrefix: string;\n  onSelect: () => void;\n}) {\n  const state = useCalendarState();\n\n  return (\n    <CalendarYearPicker visibleYears={12}>\n      {(yearPicker) => (\n        <div\n          aria-label={yearPicker[\"aria-label\"]}\n          className={calendarPickerGridClasses}\n          data-slot={`${dataSlotPrefix}-year-picker`}\n          role=\"group\"\n        >\n          {yearPicker.items.map((item) => {\n            const isSelected = item.id === yearPicker.value;\n            const isDisabled =\n              Boolean(state?.isDisabled) ||\n              isCalendarDateRangeOutsideBounds({\n                endDate: endOfYear(item.date),\n                startDate: startOfYear(item.date),\n                state,\n              });\n\n            return (\n              <button\n                aria-label={item.formatted}\n                className={calendarPickerOptionClasses}\n                data-selected={isSelected ? \"true\" : undefined}\n                data-slot={`${dataSlotPrefix}-year-option`}\n                disabled={isDisabled}\n                key={item.id}\n                type=\"button\"\n                onClick={() => {\n                  yearPicker.onChange(item.id);\n                  onSelect();\n                }}\n              >\n                {item.formatted}\n              </button>\n            );\n          })}\n        </div>\n      )}\n    </CalendarYearPicker>\n  );\n}\n\nexport function DateCalendarGrid({\n  dataSlotPrefix = \"calendar\",\n  range = false,\n  weekdayStyle = \"short\",\n}: DateCalendarGridProps) {\n  const isRangeCalendar = range || dataSlotPrefix === \"range-calendar\";\n  const [viewMode, setViewMode] = useState<CalendarViewMode>(\"day\");\n  const state = useCalendarState();\n  const visibleMonth = state?.visibleRange.start.toString();\n  const panelRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const panel = panelRef.current;\n    if (\n      !panel ||\n      !panel.animate ||\n      (typeof window.matchMedia === \"function\" &&\n        window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches)\n    )\n      return;\n    // Animate the existing subtree so moving between months never remounts the\n    // focused calendar cell or resets React Aria's keyboard state.\n    const animation = panel.animate([{ opacity: 0.55 }, { opacity: 1 }], {\n      duration: 160,\n      easing: \"ease-out\",\n    });\n    return () => animation.cancel();\n  }, [visibleMonth, viewMode]);\n\n  return (\n    <>\n      <CalendarPickerHeader\n        dataSlotPrefix={dataSlotPrefix}\n        setViewMode={setViewMode}\n        viewMode={viewMode}\n      />\n      <div\n        ref={panelRef}\n        data-slot={`${dataSlotPrefix}-panel`}\n        className={calendarPanelClasses}\n      >\n        {viewMode === \"month\" ? (\n          <CalendarMonthSelection\n            dataSlotPrefix={dataSlotPrefix}\n            onSelect={() => setViewMode(\"day\")}\n          />\n        ) : null}\n        {viewMode === \"year\" ? (\n          <CalendarYearSelection\n            dataSlotPrefix={dataSlotPrefix}\n            onSelect={() => setViewMode(\"month\")}\n          />\n        ) : null}\n        {viewMode === \"day\" ? (\n          <CalendarGrid\n            data-slot={`${dataSlotPrefix}-grid`}\n            className={calendarGridClasses}\n            weekdayStyle={weekdayStyle}\n          >\n            <CalendarGridHeader>\n              {(day) => (\n                <CalendarHeaderCell\n                  data-slot={`${dataSlotPrefix}-header-cell`}\n                  className={calendarHeaderCellClasses}\n                >\n                  {day}\n                </CalendarHeaderCell>\n              )}\n            </CalendarGridHeader>\n            <CalendarGridBody>\n              {(date) => (\n                <AriaCalendarCell\n                  date={date}\n                  data-slot={`${dataSlotPrefix}-cell`}\n                  className={\n                    isRangeCalendar\n                      ? rangeCalendarCellBaseClasses\n                      : calendarCellBaseClasses\n                  }\n                />\n              )}\n            </CalendarGridBody>\n          </CalendarGrid>\n        ) : null}\n      </div>\n    </>\n  );\n}\n\nfunction CalendarRoot<T extends DateValue = DateValue>(\n  {\n    className,\n    disabled = false,\n    invalid = false,\n    locale,\n    onValueChange,\n    weekdayStyle = \"short\",\n    weekStartsOn,\n    ...props\n  }: CalendarProps<T>,\n  ref: ForwardedRef<HTMLDivElement>,\n) {\n  const calendar = (\n    <AriaCalendar<T>\n      {...props}\n      ref={ref}\n      firstDayOfWeek={weekStartsOn}\n      isDisabled={disabled}\n      isInvalid={invalid}\n      onChange={onValueChange}\n      data-slot=\"calendar\"\n      className={calendarClassNames({ className })}\n    >\n      <DateCalendarGrid dataSlotPrefix=\"calendar\" weekdayStyle={weekdayStyle} />\n    </AriaCalendar>\n  );\n\n  if (locale) {\n    return <I18nProvider locale={locale}>{calendar}</I18nProvider>;\n  }\n\n  return calendar;\n}\n\nfunction RangeCalendarRoot<T extends DateValue = DateValue>(\n  {\n    className,\n    disabled = false,\n    invalid = false,\n    locale,\n    onValueChange,\n    weekdayStyle = \"short\",\n    weekStartsOn,\n    ...props\n  }: RangeCalendarProps<T>,\n  ref: ForwardedRef<HTMLDivElement>,\n) {\n  const calendar = (\n    <AriaRangeCalendar<T>\n      {...props}\n      ref={ref}\n      firstDayOfWeek={weekStartsOn}\n      isDisabled={disabled}\n      isInvalid={invalid}\n      onChange={onValueChange}\n      data-slot=\"range-calendar\"\n      className={rangeCalendarClassNames({ className })}\n    >\n      <DateCalendarGrid\n        dataSlotPrefix=\"range-calendar\"\n        weekdayStyle={weekdayStyle}\n      />\n    </AriaRangeCalendar>\n  );\n\n  if (locale) {\n    return <I18nProvider locale={locale}>{calendar}</I18nProvider>;\n  }\n\n  return calendar;\n}\n\nexport const Calendar = forwardRef(CalendarRoot) as CalendarComponent;\nCalendar.displayName = \"Calendar\";\n\nexport const RangeCalendar = forwardRef(\n  RangeCalendarRoot,\n) as RangeCalendarComponent;\nRangeCalendar.displayName = \"RangeCalendar\";\n"
    },
    {
      "path": "packages/components/src/components/calendar/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/calendar/index.ts",
      "content": "export {\n  Calendar,\n  DateCalendarGrid,\n  RangeCalendar,\n  calendarCellClassNames,\n  calendarClassNames,\n  rangeCalendarCellClassNames,\n  rangeCalendarClassNames,\n  type CalendarProps,\n  type CalendarWeekStartsOn,\n  type DateCalendarGridProps,\n  type RangeCalendarProps,\n} from \"./calendar\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.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}\n"
    }
  ]
}
