{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar",
  "title": "Calendar",
  "description": "A calendar component.",
  "dependencies": [
    "react-day-picker",
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "@cubby-ui/button",
    "@cubby-ui/scroll-area",
    "@cubby-ui/text-morph",
    "@cubby-ui/use-controllable-state",
    "@cubby-ui/date-utils",
    "@cubby-ui/elevated"
  ],
  "files": [
    {
      "path": "registry/default/calendar/calendar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  dateMatchModifiers,\n  DayPicker,\n  useDayPicker,\n  type DateRange,\n  type DayEventHandler,\n  type DayPickerProps,\n  type DayProps,\n  type Labels,\n  type Matcher,\n  type MonthCaptionProps,\n  type MonthGridProps,\n} from \"react-day-picker\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n  ArrowDown01Icon,\n  ArrowLeft01Icon,\n  ArrowRight01Icon,\n} from \"@hugeicons/core-free-icons\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/registry/default/button/button\";\nimport { useControllableState } from \"@/registry/default/hooks/use-controllable-state\";\nimport { daysBetween, isSameDay } from \"@/registry/default/lib/date-utils\";\nimport { solidSurface } from \"@/registry/default/lib/elevated\";\nimport { ScrollArea } from \"@/registry/default/scroll-area/scroll-area\";\nimport { TextMorph } from \"@/registry/default/text-morph/text-morph\";\n\nimport {\n  CalendarPicker,\n  cellButtonClassName,\n  focusRing,\n  pressPaint,\n} from \"./calendar-picker\";\nimport \"./calendar.css\";\n\n/**\n * Screen-reader text for what the calendar adds on top of DayPicker, in\n * DayPicker's `label*` naming. Passed in `labels` beside DayPicker's own.\n */\nexport interface CalendarLabels {\n  /** The caption button; `caption` is the formatted month and year. */\n  labelCaptionTrigger: (caption: string) => string;\n  /** The month and year picker panel. */\n  labelMonthYearPicker: string;\n  /** The picker's year column. */\n  labelYears: string;\n}\n\nconst DEFAULT_LABELS: CalendarLabels = {\n  labelCaptionTrigger: (caption) => `${caption}, choose month and year`,\n  labelMonthYearPicker: \"Choose month and year\",\n  labelYears: \"Year\",\n};\n\n/** DayPicker's own labels plus the calendar's (`CalendarLabels`). */\nexport type CalendarLabelsProp = Partial<Labels> & Partial<CalendarLabels>;\n\nexport type CalendarProps = DayPickerProps & {\n  /**\n   * Draws the card the calendar sits on. Turn it off when the container\n   * already is one, as the pickers' popover is.\n   */\n  framed?: boolean;\n  labels?: CalendarLabelsProp;\n};\n\ninterface RangeLanding {\n  anchor: Date;\n  target: Date;\n}\n\n/**\n * One cell's slice of the landing wipe, so the wipe's head follows a single\n * ease-out cubic across the whole range.\n */\nfunction wipeTiming(\n  step: number,\n  span: number,\n): { delay: number; duration: number } {\n  const total = Math.min(\n    Math.max(140 + span * WIPE_MS_PER_DAY, WIPE_MIN_MS),\n    WIPE_MAX_MS,\n  );\n  const cells = span + 1;\n  // Time at which an ease-out cubic, 1 - (1 - t)^3, reaches position p.\n  const at = (position: number) => total * (1 - Math.cbrt(1 - position));\n  const delay = at(step / cells);\n  return { delay, duration: Math.max(at((step + 1) / cells) - delay, 1) };\n}\n\ninterface CalendarContextValue {\n  landing: RangeLanding | null;\n  pickerEnabled: boolean;\n  pickerOpen: boolean;\n  pickerId: string;\n  togglePicker: (displayIndex: number) => void;\n  labelCaptionTrigger: CalendarLabels[\"labelCaptionTrigger\"];\n}\n\nconst CalendarContext = React.createContext<CalendarContextValue | null>(null);\n\n/** Matches --calendar-enter-duration in calendar.css. */\nconst CAPTION_MORPH_MS = 280;\n\nconst WIPE_MIN_MS = 180;\nconst WIPE_MAX_MS = 300;\nconst WIPE_MS_PER_DAY = 12;\n\n/** How far the year list reaches when `startMonth`/`endMonth` aren't set. */\nconst YEARS_BEFORE = 100;\nconst YEARS_AFTER = 25;\n\n/* -------------------------------------------------------------------------------------------------\n * Date helpers\n * -------------------------------------------------------------------------------------------------*/\n\nfunction startOfMonth(date: Date): Date {\n  return new Date(date.getFullYear(), date.getMonth(), 1);\n}\n\nfunction addMonths(date: Date, amount: number): Date {\n  return new Date(date.getFullYear(), date.getMonth() + amount, 1);\n}\n\n/** Months since year 0, so two months compare with plain arithmetic. */\nfunction monthIndex(date: Date): number {\n  return date.getFullYear() * 12 + date.getMonth();\n}\n\n/**\n * Whether a day appears in a month's grid, including the neighbouring months'\n * days that fill its first and last weeks (and fixed weeks' extra rows).\n */\nfunction isInGrid(\n  day: Date,\n  month: Date,\n  { weekStartsOn, fixedWeeks }: { weekStartsOn: number; fixedWeeks: boolean },\n): boolean {\n  const first = startOfMonth(month);\n  const start = new Date(first);\n  start.setDate(1 - ((first.getDay() - weekStartsOn + 7) % 7));\n  const end = new Date(start);\n  if (fixedWeeks) {\n    end.setDate(start.getDate() + 41);\n  } else {\n    const last = new Date(first.getFullYear(), first.getMonth() + 1, 0);\n    end.setFullYear(last.getFullYear(), last.getMonth(), last.getDate());\n    end.setDate(end.getDate() + ((weekStartsOn + 6 - last.getDay()) % 7));\n  }\n  const time = new Date(day).setHours(0, 0, 0, 0);\n  return time >= start.getTime() && time <= end.getTime();\n}\n\nfunction firstSelectedDate(props: DayPickerProps): Date | undefined {\n  const { selected } = props as { selected?: Date | Date[] | DateRange };\n  if (!selected) return undefined;\n  if (selected instanceof Date) return selected;\n  if (Array.isArray(selected)) return selected[0];\n  return selected.from;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Classes\n * -------------------------------------------------------------------------------------------------*/\n\n// Corners concentric with the frame: 16px frame radius minus its 4px padding.\nconst navButtonClassName = cn(\n  buttonVariants({ variant: \"ghost\", size: \"icon_sm\" }),\n  \"text-muted-foreground hover:text-foreground rounded-xl rtl:[&_svg]:-scale-x-100\",\n  \"aria-disabled:pointer-events-none aria-disabled:opacity-40\",\n);\n\nconst DEFAULT_CLASS_NAMES: NonNullable<DayPickerProps[\"classNames\"]> = {\n  root: \"relative w-fit\",\n  // The muted header strip. Side by side, months share one on the months row;\n  // stacked, each month draws its own (classes repeated in full, since Tailwind\n  // only generates literal classes).\n  months: cn(\n    \"relative isolate flex flex-col gap-1 sm:flex-row sm:gap-x-4\",\n    \"sm:before:absolute sm:before:inset-x-0 sm:before:top-0 sm:before:-z-1 sm:before:h-[calc(var(--calendar-header)-0.25rem)] sm:before:rounded-xl sm:before:bg-muted\",\n  ),\n  // Fixed side columns keep the caption centred on a month without arrows.\n  month: cn(\n    \"grid grid-cols-[2.25rem_1fr_2.25rem] grid-rows-[auto_1fr] gap-y-1 sm:not-pointer-coarse:grid-cols-[2rem_1fr_2rem]\",\n    \"max-sm:relative max-sm:isolate\",\n    \"max-sm:before:absolute max-sm:before:inset-x-0 max-sm:before:top-0 max-sm:before:-z-1 max-sm:before:h-[calc(var(--calendar-header)-0.25rem)] max-sm:before:rounded-xl max-sm:before:bg-muted\",\n  ),\n  month_caption:\n    \"col-start-2 row-start-1 flex h-9 min-w-0 items-center justify-center sm:not-pointer-coarse:h-8\",\n  button_previous: \"col-start-1 row-start-1 self-center justify-self-start\",\n  button_next: \"col-start-3 row-start-1 self-center justify-self-end\",\n  caption_label: \"text-sm font-medium\",\n  dropdowns: \"flex items-center gap-2 text-sm font-medium\",\n  dropdown_root: \"relative inline-flex items-center\",\n  dropdown: \"absolute inset-0 cursor-pointer opacity-0\",\n  month_grid: \"w-full border-separate border-spacing-x-0 border-spacing-y-0.5\",\n  weekdays: \"\",\n  weekday:\n    \"text-muted-foreground h-8 w-10 p-0 text-xs font-medium sm:not-pointer-coarse:w-9\",\n  week_number_header: \"h-8 w-10 p-0 sm:not-pointer-coarse:w-9\",\n  week_number:\n    \"text-muted-foreground/70 size-10 p-0 text-xs tabular-nums sm:not-pointer-coarse:size-9\",\n  weeks: \"\",\n  week: \"\",\n  day: cn(\n    \"group/day relative size-10 p-0 text-center sm:not-pointer-coarse:size-9\",\n    \"data-disabled:pointer-events-none\",\n  ),\n  day_button: cn(\n    \"calendar-day-button\",\n    cellButtonClassName,\n    // Outside the displayed month\n    \"group-data-muted/day:text-muted-foreground\",\n    // Today\n    \"group-data-today/day:font-semibold group-data-today/day:after:opacity-100\",\n    // Selected (and the two ends of a range)\n    \"group-data-filled/day:text-primary-foreground group-data-filled/day:font-medium group-data-filled/day:after:bg-primary-foreground group-data-filled/day:[--cell-paint:var(--primary)] group-data-filled/day:hover:[--cell-paint:var(--primary-hover)]\",\n    // Disabled: struck through so it doesn't rely on colour alone\n    \"group-data-disabled/day:text-muted-foreground group-data-disabled/day:decoration-muted-foreground/60 group-data-disabled/day:line-through group-data-disabled/day:opacity-60\",\n  ),\n  hidden: \"invisible\",\n  // Month transitions, see calendar.css\n  weeks_before_enter: \"calendar-enter-from-start\",\n  weeks_before_exit: \"calendar-exit-to-start\",\n  weeks_after_enter: \"calendar-enter-from-end\",\n  weeks_after_exit: \"calendar-exit-to-end\",\n  // Never empty: DayPicker adds these with classList.add, which throws on an\n  // empty string.\n  caption_before_enter: \"calendar-caption-still\",\n  caption_before_exit: \"calendar-caption-hold\",\n  caption_after_enter: \"calendar-caption-still\",\n  caption_after_exit: \"calendar-caption-hold\",\n};\n\nfunction mergeClassNames(\n  overrides: DayPickerProps[\"classNames\"],\n): NonNullable<DayPickerProps[\"classNames\"]> {\n  if (!overrides) return DEFAULT_CLASS_NAMES;\n  const merged: Record<string, string> = { ...DEFAULT_CLASS_NAMES };\n  for (const [key, value] of Object.entries(overrides)) {\n    merged[key] = cn(merged[key], value);\n  }\n  return merged;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * DayPicker slots\n * -------------------------------------------------------------------------------------------------*/\n\n/**\n * Selection state goes on the cell as data attributes, so calendar.css can draw\n * the range band on the cell, unbroken beneath the buttons.\n */\nfunction CalendarDay({ day, modifiers, style, ...props }: DayProps) {\n  const context = React.useContext(CalendarContext);\n  const { formatters, classNames, dayPickerProps } = useDayPicker();\n  const { hidden, showOutsideDays } = dayPickerProps;\n\n  // DayPicker hides days past `startMonth`/`endMonth`. Where they only fill\n  // out an edge week, show them as unavailable so the month has no blank cells.\n  if (\n    modifiers.hidden &&\n    day.outside &&\n    showOutsideDays &&\n    !(hidden && dateMatchModifiers(day.date, hidden, day.dateLib))\n  ) {\n    return (\n      <td\n        {...props}\n        className={cn(props.className, \"visible\")}\n        style={style}\n        aria-hidden\n        data-hidden={undefined}\n        data-disabled=\"\"\n        data-slot=\"calendar-day\"\n        data-muted=\"\"\n      >\n        <span className={classNames.day_button}>\n          {formatters.formatDay(day.date, day.dateLib.options, day.dateLib)}\n        </span>\n      </td>\n    );\n  }\n  const inRange = Boolean(modifiers.range_middle);\n  const filled = Boolean(modifiers.selected) && !inRange;\n\n  const landing = context?.landing;\n  const banded =\n    inRange || Boolean(modifiers.range_start || modifiers.range_end);\n  const wipe =\n    landing && banded\n      ? wipeTiming(\n          Math.abs(daysBetween(landing.anchor, day.date)),\n          Math.abs(daysBetween(landing.anchor, landing.target)),\n        )\n      : null;\n\n  return (\n    <td\n      {...props}\n      style={\n        wipe\n          ? ({\n              ...style,\n              \"--calendar-wipe-delay\": `${wipe.delay}ms`,\n              \"--calendar-wipe-duration\": `${wipe.duration}ms`,\n            } as React.CSSProperties)\n          : style\n      }\n      data-sweep={wipe ? \"\" : undefined}\n      data-slot=\"calendar-day\"\n      data-landing-target={\n        (landing && isSameDay(day.date, landing.target)) || undefined\n      }\n      data-filled={filled || undefined}\n      data-in-range={inRange || undefined}\n      data-muted={(day.outside && !filled && !inRange) || undefined}\n      data-range-start={modifiers.range_start || undefined}\n      data-range-end={modifiers.range_end || undefined}\n      data-preview-start={modifiers.preview_start || undefined}\n      data-preview-middle={modifiers.preview_middle || undefined}\n      data-preview-end={modifiers.preview_end || undefined}\n      data-preview-target={modifiers.preview_target || undefined}\n    />\n  );\n}\n\n/**\n * Wraps DayPicker's table so the days card can stretch to the month beside it\n * while the table keeps its natural height.\n */\nfunction CalendarMonthGrid(props: MonthGridProps) {\n  const context = React.useContext(CalendarContext);\n  const covered = Boolean(context?.pickerOpen);\n  return (\n    <div\n      data-slot=\"calendar-grid\"\n      data-covered={covered || undefined}\n      inert={covered}\n      className=\"col-span-3 row-start-2 p-1\"\n    >\n      <table {...props} />\n    </div>\n  );\n}\n\nfunction CalendarMonthCaption({\n  calendarMonth,\n  displayIndex,\n  children,\n  ...props\n}: MonthCaptionProps) {\n  const context = React.useContext(CalendarContext);\n  const { dayPickerProps } = useDayPicker();\n\n  if (!context?.pickerEnabled) return <div {...props}>{children}</div>;\n\n  const caption = new Intl.DateTimeFormat(dayPickerProps.locale?.code, {\n    month: \"long\",\n    year: \"numeric\",\n  }).format(calendarMonth.date);\n\n  return (\n    <div {...props}>\n      <button\n        type=\"button\"\n        data-slot=\"calendar-caption-trigger\"\n        aria-label={context.labelCaptionTrigger(caption)}\n        aria-expanded={context.pickerOpen}\n        aria-controls={context.pickerOpen ? context.pickerId : undefined}\n        onClick={() => context.togglePicker(displayIndex)}\n        className={cn(\n          \"inline-flex h-full cursor-pointer items-center gap-1.5 rounded-xl ps-2.5 pe-2 text-sm\",\n          \"hover:bg-surface-hover aria-expanded:bg-surface-hover transition-[background-color,outline-color,outline-offset] duration-150 ease-out\",\n          focusRing,\n        )}\n      >\n        {/* One label, so TextMorph eases the width change itself; two\n            side-by-side labels shimmered. */}\n        <TextMorph\n          value={caption}\n          duration={CAPTION_MORPH_MS}\n          className=\"font-medium\"\n        />\n        <HugeiconsIcon\n          icon={ArrowDown01Icon}\n          strokeWidth={2}\n          className={cn(\n            \"text-muted-foreground size-3.5 transition-[rotate] duration-200 ease-out motion-reduce:transition-none\",\n            context.pickerOpen && \"rotate-180\",\n          )}\n        />\n      </button>\n      {/* DayPicker's caption label is a polite live region; kept so month\n          changes are still announced. */}\n      <span className=\"sr-only\">{children}</span>\n    </div>\n  );\n}\n\nfunction CalendarPreviousMonthButton({\n  className,\n  ...props\n}: React.ComponentProps<\"button\">) {\n  return (\n    <button\n      {...props}\n      data-slot=\"calendar-nav\"\n      className={cn(navButtonClassName, className)}\n    >\n      <HugeiconsIcon icon={ArrowLeft01Icon} strokeWidth={2} />\n    </button>\n  );\n}\n\nfunction CalendarNextMonthButton({\n  className,\n  ...props\n}: React.ComponentProps<\"button\">) {\n  return (\n    <button\n      {...props}\n      data-slot=\"calendar-nav\"\n      className={cn(navButtonClassName, className)}\n    >\n      <HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} />\n    </button>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Motion state\n * -------------------------------------------------------------------------------------------------*/\n\n/**\n * A range \"lands\" when a second click completes it. Tracked during render so\n * the first frame showing the full range already carries it; from an effect it\n * would paint once, then restart. Cleared after the wipe and cap settle, or\n * when the month changes so the incoming days don't replay it.\n */\nfunction useRangeLanding(range: DateRange | undefined | null, month: number) {\n  const rangeKey = range\n    ? `${range.from?.getTime() ?? \"\"}:${range.to?.getTime() ?? \"\"}`\n    : \"\";\n  const [previous, setPrevious] = React.useState({\n    key: rangeKey,\n    month,\n    from: range?.from,\n    to: range?.to,\n  });\n  const [landing, setLanding] = React.useState<RangeLanding | null>(null);\n  if (previous.key !== rangeKey || previous.month !== month) {\n    const wasPicking = Boolean(previous.from && !previous.to);\n    setPrevious({ key: rangeKey, month, from: range?.from, to: range?.to });\n    if (\n      previous.month === month &&\n      wasPicking &&\n      previous.from &&\n      range?.from &&\n      range.to &&\n      !isSameDay(range.from, range.to)\n    ) {\n      const anchor = previous.from;\n      setLanding({\n        anchor,\n        target: isSameDay(range.from, anchor) ? range.to : range.from,\n      });\n    } else {\n      setLanding(null);\n    }\n  }\n\n  React.useEffect(() => {\n    if (!landing) return;\n    const timeout = setTimeout(() => setLanding(null), 400);\n    return () => clearTimeout(timeout);\n  }, [landing]);\n\n  return landing;\n}\n\n/**\n * How the selection fill enters when it remounts for a new month: with the\n * incoming days (\"start\"/\"end\"), or \"instant\" when DayPicker skips its\n * animation. `null` means a fresh selection, which settles in.\n */\nfunction useFillEntrance(\n  month: number,\n  selected: number | undefined,\n  pickerKey: number,\n  dayFocused: boolean,\n) {\n  const [previous, setPrevious] = React.useState({\n    month,\n    selected,\n    pickerKey,\n  });\n  const [entrance, setEntrance] = React.useState<\n    \"start\" | \"end\" | \"instant\" | null\n  >(null);\n  if (\n    previous.month !== month ||\n    previous.selected !== selected ||\n    previous.pickerKey !== pickerKey\n  ) {\n    setPrevious({ month, selected, pickerKey });\n    if (previous.month === month) {\n      setEntrance(null);\n    } else if (previous.pickerKey !== pickerKey || dayFocused) {\n      setEntrance(\"instant\");\n    } else {\n      setEntrance(month > previous.month ? \"end\" : \"start\");\n    }\n  }\n  return entrance;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Calendar\n * -------------------------------------------------------------------------------------------------*/\n\n/**\n * A month grid for picking a date, several dates, or a range. Built on React\n * DayPicker, so every DayPicker prop passes through.\n *\n * On top of DayPicker it adds: a caption that opens a month and year picker,\n * a live preview while picking the end of a range, a selection that slides\n * between days, and animated month changes.\n */\nfunction Calendar({\n  className,\n  classNames,\n  components,\n  modifiers,\n  footer,\n  framed = true,\n  showOutsideDays,\n  fixedWeeks: fixedWeeksProp,\n  animate = true,\n  captionLayout,\n  month: monthProp,\n  defaultMonth,\n  onMonthChange,\n  onDayMouseEnter,\n  onDayFocus,\n  onDayBlur,\n  labels,\n  ...props\n}: CalendarProps) {\n  const {\n    labelCaptionTrigger = DEFAULT_LABELS.labelCaptionTrigger,\n    labelMonthYearPicker = DEFAULT_LABELS.labelMonthYearPicker,\n    labelYears = DEFAULT_LABELS.labelYears,\n    ...dayPickerLabels\n  } = labels ?? {};\n  const today = props.today ?? new Date();\n  const locale = props.locale?.code;\n  const numberOfMonths = props.numberOfMonths ?? 1;\n  const pickerEnabled = captionLayout === undefined;\n  const pickerId = React.useId();\n\n  const [month, setMonth] = useControllableState<Date>({\n    value: monthProp,\n    defaultValue: startOfMonth(\n      defaultMonth ?? firstSelectedDate(props) ?? today,\n    ),\n    onValueChange: onMonthChange,\n  });\n\n  // `pickerIndex` is the displayed month whose caption opened the picker, so a\n  // picked month lands back in the same position.\n  const [pickerOpen, setPickerOpen] = React.useState(false);\n  const [pickerIndex, setPickerIndex] = React.useState(0);\n  const [pickerYear, setPickerYear] = React.useState(() => month.getFullYear());\n  // Remounting DayPicker after a pick skips its slide animation, which would\n  // otherwise run underneath the picker fading away.\n  const [pickerKey, setPickerKey] = React.useState(0);\n  // DayPicker skips its month animation while a day has keyboard focus, and\n  // the selection fill follows suit.\n  const [dayFocused, setDayFocused] = React.useState(false);\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  const pendingFocusRef = React.useRef<\"picker\" | \"days\" | \"caption\" | null>(\n    null,\n  );\n\n  // Range preview: the hovered (or keyboard-focused) day while picking a\n  // range's end.\n  const [hovered, setHovered] = React.useState<Date>();\n  const range =\n    props.mode === \"range\" ? (props.selected as DateRange | undefined) : null;\n  const pickingEnd = Boolean(range?.from && !range.to);\n\n  let previewModifiers: Record<string, Matcher | Matcher[] | undefined> = {};\n  if (pickingEnd && range?.from && hovered && !isSameDay(hovered, range.from)) {\n    const [start, end] =\n      hovered < range.from ? [hovered, range.from] : [range.from, hovered];\n    previewModifiers = {\n      preview_start: start,\n      preview_end: end,\n      preview_middle: { after: start, before: end },\n      preview_target: hovered,\n    };\n  }\n\n  // One month keeps six weeks so its height (and a popover around it) holds\n  // still. Side by side, outside days are hidden, so fixed weeks would only\n  // add blank rows. The sliding fill renders only while its day is on screen,\n  // so it never slides in from a stale anchor.\n  const showOutside = showOutsideDays ?? numberOfMonths === 1;\n  const fixedWeeks = fixedWeeksProp ?? numberOfMonths === 1;\n  const selectedDay =\n    props.mode === \"single\" ? (props.selected as Date | undefined) : undefined;\n  const firstShown = monthIndex(month);\n  const landing = useRangeLanding(range, firstShown);\n  const showIndicator =\n    selectedDay !== undefined &&\n    ((monthIndex(selectedDay) >= firstShown &&\n      monthIndex(selectedDay) < firstShown + numberOfMonths) ||\n      (numberOfMonths === 1 &&\n        showOutside &&\n        isInGrid(selectedDay, month, {\n          weekStartsOn: props.ISOWeek\n            ? 1\n            : (props.weekStartsOn ?? props.locale?.options?.weekStartsOn ?? 0),\n          fixedWeeks,\n        })));\n\n  // The fill remounts per month so it doesn't slide from the old month's spot.\n  const fillEnter = useFillEntrance(\n    firstShown,\n    selectedDay?.getTime(),\n    pickerKey,\n    dayFocused,\n  );\n\n  const startBound = props.startMonth ? monthIndex(props.startMonth) : null;\n  const endBound = props.endMonth ? monthIndex(props.endMonth) : null;\n  const isMonthDisabled = (year: number, monthOfYear: number) => {\n    const index = year * 12 + monthOfYear;\n    return (\n      (startBound !== null && index < startBound) ||\n      (endBound !== null && index > endBound)\n    );\n  };\n  const minYear =\n    props.startMonth?.getFullYear() ?? today.getFullYear() - YEARS_BEFORE;\n  const maxYear =\n    props.endMonth?.getFullYear() ?? today.getFullYear() + YEARS_AFTER;\n\n  const togglePicker = (displayIndex: number) => {\n    if (pickerOpen) {\n      setPickerOpen(false);\n      return;\n    }\n    setPickerIndex(displayIndex);\n    setPickerYear(addMonths(month, displayIndex).getFullYear());\n    pendingFocusRef.current = \"picker\";\n    setPickerOpen(true);\n  };\n\n  const closePicker = () => {\n    pendingFocusRef.current = \"caption\";\n    setPickerOpen(false);\n  };\n\n  const selectMonth = (monthOfYear: number) => {\n    setMonth(new Date(pickerYear, monthOfYear - pickerIndex, 1));\n    setPickerKey((key) => key + 1);\n    pendingFocusRef.current = \"days\";\n    setPickerOpen(false);\n  };\n\n  // The covered days are inert, so without this focus would fall back to the\n  // body.\n  React.useEffect(() => {\n    const target = pendingFocusRef.current;\n    pendingFocusRef.current = null;\n    const root = rootRef.current;\n    if (!target || !root) return;\n    const selectors = {\n      picker:\n        '[data-slot=\"calendar-picker-months\"] button[tabindex=\"0\"]:not(:disabled)',\n      days: '[data-slot=\"calendar-day\"] button[tabindex=\"0\"]',\n      caption: '[data-slot=\"calendar-caption-trigger\"]',\n    };\n    const candidates = root.querySelectorAll<HTMLElement>(selectors[target]);\n    const element =\n      target === \"caption\"\n        ? (candidates[pickerIndex] ?? candidates[0])\n        : candidates[0];\n    element?.focus();\n  }, [pickerOpen, pickerKey, pickerIndex]);\n\n  const context: CalendarContextValue = {\n    landing,\n    pickerEnabled,\n    pickerOpen,\n    pickerId,\n    togglePicker,\n    labelCaptionTrigger,\n  };\n\n  const handleDayMouseEnter: DayEventHandler<React.MouseEvent> = (\n    date,\n    dayModifiers,\n    event,\n  ) => {\n    if (pickingEnd) setHovered(date);\n    onDayMouseEnter?.(date, dayModifiers, event);\n  };\n\n  const handleDayFocus: DayEventHandler<React.FocusEvent> = (\n    date,\n    dayModifiers,\n    event,\n  ) => {\n    setDayFocused(true);\n    if (pickingEnd) setHovered(date);\n    onDayFocus?.(date, dayModifiers, event);\n  };\n\n  const handleDayBlur: DayEventHandler<React.FocusEvent> = (\n    date,\n    dayModifiers,\n    event,\n  ) => {\n    setDayFocused(false);\n    onDayBlur?.(date, dayModifiers, event);\n  };\n\n  return (\n    <CalendarContext.Provider value={context}>\n      <div\n        ref={rootRef}\n        data-slot=\"calendar\"\n        data-mode={props.mode}\n        data-framed={framed || undefined}\n        data-picker-open={pickerOpen || undefined}\n        data-range-landing={\n          landing\n            ? landing.target > landing.anchor\n              ? \"forward\"\n              : \"backward\"\n            : undefined\n        }\n        className={cn(\n          \"relative w-fit [--calendar-header:2.5rem] sm:not-pointer-coarse:[--calendar-header:2.25rem]\",\n          framed && cn(\"rounded-2xl p-1\", solidSurface(3, 1)),\n          className,\n        )}\n        onMouseLeave={() => setHovered(undefined)}\n        onBlur={(event) => {\n          if (!event.currentTarget.contains(event.relatedTarget)) {\n            setHovered(undefined);\n          }\n        }}\n      >\n        {/* Own stack, so the selection fill (z -1) sits under the days\n            without dropping behind the card. */}\n        <div className=\"relative isolate\">\n          <DayPicker\n            key={pickerKey}\n            // Side by side, outside days would repeat the neighbouring\n            // month's days (and any range drawn across them).\n            showOutsideDays={showOutside}\n            fixedWeeks={fixedWeeks}\n            animate={animate}\n            navLayout=\"around\"\n            captionLayout={pickerEnabled ? \"label\" : captionLayout}\n            {...(props.mode === \"range\" ? { resetOnSelect: true } : null)}\n            {...props}\n            labels={dayPickerLabels}\n            month={month}\n            onMonthChange={setMonth}\n            modifiers={{ ...previewModifiers, ...modifiers }}\n            onDayMouseEnter={handleDayMouseEnter}\n            onDayFocus={handleDayFocus}\n            onDayBlur={handleDayBlur}\n            classNames={mergeClassNames(classNames)}\n            components={{\n              Day: CalendarDay,\n              MonthGrid: CalendarMonthGrid,\n              MonthCaption: CalendarMonthCaption,\n              PreviousMonthButton: CalendarPreviousMonthButton,\n              NextMonthButton: CalendarNextMonthButton,\n              ...components,\n            }}\n          />\n          {showIndicator && (\n            <span\n              key={firstShown}\n              aria-hidden\n              data-slot=\"calendar-selection\"\n              data-enter={fillEnter ?? undefined}\n              className=\"calendar-selection\"\n            />\n          )}\n          {pickerEnabled && (\n            <CalendarPicker\n              id={pickerId}\n              open={pickerOpen}\n              year={pickerYear}\n              onYearChange={setPickerYear}\n              minYear={minYear}\n              maxYear={maxYear}\n              activeMonth={addMonths(month, pickerIndex)}\n              today={today}\n              locale={locale}\n              isMonthDisabled={isMonthDisabled}\n              onSelectMonth={selectMonth}\n              onClose={closePicker}\n              label={labelMonthYearPicker}\n              yearsLabel={labelYears}\n            />\n          )}\n        </div>\n        {footer && (\n          <div\n            role=\"status\"\n            aria-live=\"polite\"\n            data-slot=\"calendar-footer\"\n            className=\"text-muted-foreground px-2 pt-2 pb-1 text-sm\"\n          >\n            {footer}\n          </div>\n        )}\n      </div>\n    </CalendarContext.Provider>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Presets\n * -------------------------------------------------------------------------------------------------*/\n\n/**\n * A rail of one-click shortcuts that sits beside a calendar (\"Today\", \"Last 7\n * days\"). Vertical from `sm` up, a scrolling row of chips below it. Classes\n * go on the outer scroll area; other props on the group inside.\n */\nfunction CalendarPresets({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <ScrollArea\n      fadeEdges=\"x\"\n      hideScrollbar\n      contentMinWidth=\"fit-content\"\n      // h-auto lets the rail stretch to the calendar (ScrollArea's full height\n      // resolves to nothing there). w-0/min-w-full keeps the chip row from\n      // widening its container.\n      className={cn(\n        \"h-auto shrink-0 max-sm:w-0 max-sm:min-w-full sm:w-36\",\n        className,\n      )}\n    >\n      <div\n        role=\"group\"\n        aria-label=\"Presets\"\n        data-slot=\"calendar-presets\"\n        className=\"flex gap-1 sm:flex-col\"\n        {...props}\n      />\n    </ScrollArea>\n  );\n}\n\nfunction CalendarPreset({\n  className,\n  active = false,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  /** Marks the preset matching the current selection. */\n  active?: boolean;\n}) {\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"calendar-preset\"\n      data-active={active || undefined}\n      aria-pressed={active}\n      className={cn(\n        \"text-muted-foreground flex h-8 shrink-0 cursor-pointer items-center rounded-lg px-2.5 text-start text-sm whitespace-nowrap\",\n        \"hover:text-foreground hover:[--cell-paint:var(--surface-hover)]\",\n        \"data-active:text-foreground data-active:font-medium data-active:[--cell-paint:var(--surface-hover)]\",\n        pressPaint,\n        focusRing,\n        // Hover changes colour instantly, like the days.\n        \"transition-[outline-color,outline-offset] duration-150 ease-out\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Calendar, CalendarPresets, CalendarPreset };\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/calendar/calendar.tsx"
    },
    {
      "path": "registry/default/calendar/calendar-picker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ScrollArea } from \"@/registry/default/scroll-area/scroll-area\";\n\n/* -------------------------------------------------------------------------------------------------\n * Shared cell styles, used by the calendar's days and the picker's cells\n * -------------------------------------------------------------------------------------------------*/\n\nexport const focusRing =\n  \"focus-visible:outline-ring/50 outline-0 outline-offset-0 outline-transparent outline-solid focus-visible:outline-2 focus-visible:outline-offset-2\";\n\n/**\n * A background in `::before` that shrinks on press while the label holds\n * still, as Button's does. It paints `--cell-paint`, or the press colour\n * when the cell has none, so a touch press shows without hover. Isolated so\n * the paint sits behind the label, not the surface around it.\n */\nexport const pressPaint =\n  \"relative isolate before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:bg-[var(--cell-paint,var(--cell-press,transparent))] before:transition-[scale,border-radius] before:duration-[100ms,150ms] before:ease-out active:[--cell-press:var(--surface-active)] active:before:scale-[0.96] motion-reduce:before:transition-none motion-reduce:active:before:scale-100\";\n\nexport const cellButtonClassName = cn(\n  // Layout\n  \"z-1 flex size-full items-center justify-center rounded-lg text-sm tabular-nums\",\n  // Focus\n  focusRing,\n  \"focus-visible:z-2\",\n  // Interaction: colours change instantly, since a fade would trail the\n  // pointer across the days. A mode can time the text colour (and the today\n  // dot with it) through --cell-text-duration and --cell-text-delay.\n  \"cursor-pointer transition-[outline-color,outline-offset,color] duration-[150ms,150ms,var(--cell-text-duration,0s)] delay-[0s,0s,var(--cell-text-delay,0s)] ease-out\",\n  \"after:transition-[background-color] after:duration-[var(--cell-text-duration,0s)] after:delay-[var(--cell-text-delay,0s)] after:ease-out\",\n  pressPaint,\n  \"hover:[--cell-paint:var(--surface-hover)]\",\n  // Today: a dot under the number, re-coloured when the day is filled\n  \"after:pointer-events-none after:absolute after:inset-x-0 after:bottom-1 after:mx-auto after:size-1 after:rounded-full after:bg-primary after:opacity-0\",\n);\n\n/* -------------------------------------------------------------------------------------------------\n * Keyboard\n * -------------------------------------------------------------------------------------------------*/\n\n/**\n * Arrow-key movement across a grid of buttons. Columns are counted from layout\n * with disabled cells included, so rows stay true when some are out of bounds.\n */\nfunction moveInGrid(event: React.KeyboardEvent<HTMLElement>): void {\n  const cells = Array.from(\n    event.currentTarget.querySelectorAll<HTMLButtonElement>(\"button\"),\n  );\n  const index = cells.indexOf(document.activeElement as HTMLButtonElement);\n  if (index === -1) return;\n\n  const top = cells[0].offsetTop;\n  const columns = Math.max(\n    1,\n    cells.filter((cell) => cell.offsetTop === top).length,\n  );\n  const step =\n    getComputedStyle(event.currentTarget).direction === \"rtl\" ? -1 : 1;\n  const strides: Record<string, number> = {\n    ArrowLeft: -step,\n    ArrowRight: step,\n    ArrowUp: -columns,\n    ArrowDown: columns,\n  };\n\n  let target: number | undefined;\n  if (event.key === \"Home\") {\n    target = cells.findIndex((cell) => !cell.disabled);\n  } else if (event.key === \"End\") {\n    target = cells.findLastIndex((cell) => !cell.disabled);\n  } else if (event.key in strides) {\n    const stride = strides[event.key];\n    for (\n      let next = index + stride;\n      next >= 0 && next < cells.length;\n      next += stride\n    ) {\n      if (!cells[next].disabled) {\n        target = next;\n        break;\n      }\n    }\n  } else {\n    return;\n  }\n\n  event.preventDefault();\n  if (target !== undefined && target >= 0) cells[target].focus();\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Month and year picker\n * -------------------------------------------------------------------------------------------------*/\n\ninterface CalendarPickerProps {\n  id: string;\n  open: boolean;\n  year: number;\n  onYearChange: (year: number) => void;\n  minYear: number;\n  maxYear: number;\n  /** The displayed month the picker was opened from. */\n  activeMonth: Date;\n  today: Date;\n  locale: string | undefined;\n  isMonthDisabled: (year: number, month: number) => boolean;\n  onSelectMonth: (month: number) => void;\n  onClose: () => void;\n  label: string;\n  yearsLabel: string;\n}\n\n/**\n * A scrolling year column beside the year's twelve months. Picking a year only\n * changes which months show; picking a month goes there and closes.\n */\nfunction CalendarPicker({\n  id,\n  open,\n  year,\n  onYearChange,\n  minYear,\n  maxYear,\n  activeMonth,\n  today,\n  locale,\n  isMonthDisabled,\n  onSelectMonth,\n  onClose,\n  label,\n  yearsLabel,\n}: CalendarPickerProps) {\n  const yearsRef = React.useRef<HTMLDivElement>(null);\n  const focusYearRef = React.useRef(false);\n  const monthsRef = React.useRef<HTMLDivElement>(null);\n  // The month focused when PageUp/PageDown changed the year, to refocus after.\n  const focusMonthRef = React.useRef<number | null>(null);\n\n  React.useLayoutEffect(() => {\n    if (!open) return;\n    const list = yearsRef.current;\n    const active = list?.querySelector<HTMLElement>(\"[data-active]\");\n    if (!list || !active) return;\n    list.scrollTop =\n      active.offsetTop - list.clientHeight / 2 + active.offsetHeight / 2;\n  }, [open]);\n\n  // Keyboard moves in the year column select as they go, so focus follows.\n  React.useEffect(() => {\n    if (!focusYearRef.current) return;\n    focusYearRef.current = false;\n    const active =\n      yearsRef.current?.querySelector<HTMLElement>(\"[data-active]\");\n    active?.focus();\n    active?.scrollIntoView({ block: \"nearest\" });\n  }, [year]);\n\n  // Keep focus on the same month in the new year, or on the first one that\n  // isn't out of bounds there.\n  React.useEffect(() => {\n    const index = focusMonthRef.current;\n    if (index === null) return;\n    focusMonthRef.current = null;\n    const cells =\n      monthsRef.current?.querySelectorAll<HTMLButtonElement>(\"button\");\n    if (!cells) return;\n    const target = cells[index]?.disabled\n      ? Array.from(cells).find((cell) => !cell.disabled)\n      : cells[index];\n    target?.focus();\n  }, [year]);\n\n  const stepYear = (next: number) => {\n    const clamped = Math.min(Math.max(next, minYear), maxYear);\n    if (clamped === year) return;\n    focusYearRef.current = true;\n    onYearChange(clamped);\n  };\n\n  const monthFormat = new Intl.DateTimeFormat(locale, { month: \"short\" });\n  const monthLongFormat = new Intl.DateTimeFormat(locale, {\n    month: \"long\",\n    year: \"numeric\",\n  });\n  const years = Array.from(\n    { length: maxYear - minYear + 1 },\n    (_, index) => minYear + index,\n  );\n  const months = Array.from({ length: 12 }, (_, monthOfYear) => monthOfYear);\n  const activeInYear = activeMonth.getFullYear() === year;\n  const tabStop =\n    activeInYear && !isMonthDisabled(year, activeMonth.getMonth())\n      ? activeMonth.getMonth()\n      : months.find((monthOfYear) => !isMonthDisabled(year, monthOfYear));\n\n  return (\n    <div\n      id={id}\n      role=\"group\"\n      aria-label={label}\n      data-slot=\"calendar-picker\"\n      data-state={open ? \"open\" : \"closed\"}\n      inert={!open}\n      className={cn(\n        \"absolute inset-x-0 top-(--calendar-header) bottom-0 z-3 flex overflow-hidden\",\n        \"rounded-xl bg-(--calendar-surface)\",\n      )}\n      onKeyDown={(event) => {\n        if (event.key !== \"Escape\") return;\n        event.preventDefault();\n        event.stopPropagation();\n        onClose();\n      }}\n    >\n      <ScrollArea\n        fadeEdges=\"y\"\n        hideScrollbar\n        viewportRef={(element) => {\n          yearsRef.current = element;\n        }}\n        className=\"border-border/60 h-auto w-17 shrink-0 border-e\"\n      >\n        <div\n          role=\"group\"\n          aria-label={yearsLabel}\n          data-slot=\"calendar-picker-years\"\n          className=\"flex flex-col gap-0.5 p-1\"\n          onKeyDown={(event) => {\n            const moves: Record<string, number> = {\n              ArrowUp: year - 1,\n              ArrowDown: year + 1,\n              PageUp: year - 10,\n              PageDown: year + 10,\n              Home: minYear,\n              End: maxYear,\n            };\n            if (!(event.key in moves)) return;\n            event.preventDefault();\n            stepYear(moves[event.key]);\n          }}\n        >\n          {years.map((option) => {\n            const active = option === year;\n            return (\n              <button\n                key={option}\n                type=\"button\"\n                data-active={active || undefined}\n                data-today={option === today.getFullYear() || undefined}\n                aria-current={active || undefined}\n                tabIndex={active ? 0 : -1}\n                onClick={() => onYearChange(option)}\n                className={cn(\n                  cellButtonClassName,\n                  \"text-muted-foreground h-8 w-full shrink-0\",\n                  \"data-today:after:opacity-100\",\n                  \"data-active:text-foreground data-active:font-semibold data-active:[--cell-paint:var(--surface-hover)]\",\n                )}\n              >\n                {option}\n              </button>\n            );\n          })}\n        </div>\n      </ScrollArea>\n      <div\n        role=\"group\"\n        aria-label={String(year)}\n        ref={monthsRef}\n        data-slot=\"calendar-picker-months\"\n        className=\"grid flex-1 auto-rows-fr grid-cols-3 gap-x-1 p-1\"\n        onKeyDown={(event) => {\n          if (event.key === \"PageUp\" || event.key === \"PageDown\") {\n            event.preventDefault();\n            const next = Math.min(\n              Math.max(year + (event.key === \"PageUp\" ? -1 : 1), minYear),\n              maxYear,\n            );\n            if (next === year) return;\n            focusMonthRef.current = Array.from(\n              event.currentTarget.querySelectorAll(\"button\"),\n            ).indexOf(document.activeElement as HTMLButtonElement);\n            onYearChange(next);\n            return;\n          }\n          moveInGrid(event);\n        }}\n      >\n        {months.map((monthOfYear) => {\n          const date = new Date(year, monthOfYear, 1);\n          const active = activeInYear && monthOfYear === activeMonth.getMonth();\n          const isCurrent =\n            year === today.getFullYear() && monthOfYear === today.getMonth();\n          return (\n            <button\n              key={monthOfYear}\n              type=\"button\"\n              data-active={active || undefined}\n              data-today={isCurrent || undefined}\n              aria-label={monthLongFormat.format(date)}\n              aria-current={active || undefined}\n              tabIndex={monthOfYear === tabStop ? 0 : -1}\n              disabled={isMonthDisabled(year, monthOfYear)}\n              onClick={() => onSelectMonth(monthOfYear)}\n              className={cn(\n                cellButtonClassName,\n                \"h-9 w-full self-center font-medium sm:not-pointer-coarse:h-8\",\n                \"data-today:after:opacity-100\",\n                \"data-active:text-primary-foreground data-active:after:bg-primary-foreground data-active:[--cell-paint:var(--primary)] data-active:hover:[--cell-paint:var(--primary-hover)]\",\n                \"disabled:pointer-events-none disabled:opacity-40\",\n              )}\n            >\n              {monthFormat.format(date)}\n            </button>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nexport { CalendarPicker };\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/calendar/calendar-picker.tsx"
    },
    {
      "path": "registry/default/calendar/calendar.css",
      "content": "/* -------------------------------------------------------------------------------------------------\n * Tokens\n *\n * Bands are mixed into the card colour rather than made translucent, so a fill\n * over a band covers it instead of stacking darker. Keep --calendar-surface in\n * step with the card's background in calendar.tsx.\n * -------------------------------------------------------------------------------------------------*/\n\n[data-slot=\"calendar\"] {\n  --calendar-surface: var(--surface-3);\n  --calendar-band: color-mix(\n    in oklab,\n    var(--color-primary) 18%,\n    var(--calendar-surface)\n  );\n  --calendar-band-preview: color-mix(\n    in oklab,\n    var(--color-primary) 7%,\n    var(--calendar-surface)\n  );\n  --calendar-radius: var(--radius-lg, 0.625rem);\n  --calendar-ease: cubic-bezier(0.19, 1, 0.22, 1);\n  --calendar-duration: 260ms;\n  --calendar-picker-duration: 200ms;\n  /* Zeroed under reduced motion, leaving plain crossfades. */\n  --calendar-shift: 1.25rem;\n  --calendar-blur: 2px;\n  --calendar-zoom: 0.06;\n  /* Which way \"later\" runs, for the half bands at a range's ends. */\n  --calendar-toward-end: to right;\n  --calendar-toward-start: to left;\n}\n\n.dark [data-slot=\"calendar\"] {\n  --calendar-band: color-mix(\n    in oklab,\n    var(--color-primary) 28%,\n    var(--calendar-surface)\n  );\n  --calendar-band-preview: color-mix(\n    in oklab,\n    var(--color-primary) 11%,\n    var(--calendar-surface)\n  );\n}\n\n[data-slot=\"calendar\"]:dir(rtl) {\n  --calendar-shift: -1.25rem;\n  --calendar-toward-end: to left;\n  --calendar-toward-start: to right;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  [data-slot=\"calendar\"],\n  [data-slot=\"calendar\"]:dir(rtl) {\n    --calendar-shift: 0px;\n    --calendar-blur: 0px;\n    --calendar-zoom: 0;\n  }\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Range band\n *\n * Drawn on the cells so it runs unbroken across a week, as a background image\n * so a landing range can wipe it in by animating its size. `td:first-of-type`\n * skips the week-number header.\n * -------------------------------------------------------------------------------------------------*/\n\n[data-slot=\"calendar-day\"] {\n  background-image: var(--calendar-paint, none);\n  background-repeat: no-repeat;\n}\n\n[data-slot=\"calendar-day\"][data-in-range] {\n  --calendar-paint: linear-gradient(var(--calendar-band), var(--calendar-band));\n  --calendar-paint-preview: linear-gradient(\n    var(--calendar-band-preview),\n    var(--calendar-band-preview)\n  );\n}\n\n[data-slot=\"calendar-day\"][data-range-start]:not([data-range-end]),\n[data-slot=\"calendar-day\"][data-preview-start] {\n  --calendar-paint: linear-gradient(\n    var(--calendar-toward-end),\n    transparent 50%,\n    var(--calendar-band) 50%\n  );\n  --calendar-paint-preview: linear-gradient(\n    var(--calendar-toward-end),\n    transparent 50%,\n    var(--calendar-band-preview) 50%\n  );\n}\n\n[data-slot=\"calendar-day\"][data-range-end]:not([data-range-start]),\n[data-slot=\"calendar-day\"][data-preview-end] {\n  --calendar-paint: linear-gradient(\n    var(--calendar-toward-start),\n    transparent 50%,\n    var(--calendar-band) 50%\n  );\n  --calendar-paint-preview: linear-gradient(\n    var(--calendar-toward-start),\n    transparent 50%,\n    var(--calendar-band-preview) 50%\n  );\n}\n\n/* While previewing, the shape is the same but only the light tint shows. */\n[data-slot=\"calendar-day\"]:is(\n  [data-preview-start],\n  [data-preview-middle],\n  [data-preview-end]\n) {\n  background-image: var(--calendar-paint-preview, none);\n}\n\n[data-slot=\"calendar-day\"][data-preview-middle] {\n  --calendar-paint-preview: linear-gradient(\n    var(--calendar-band-preview),\n    var(--calendar-band-preview)\n  );\n}\n\n[data-slot=\"calendar-day\"]:is([data-in-range], [data-preview-middle]):is(\n    td:first-of-type,\n    td[data-hidden] + td\n  ) {\n  border-start-start-radius: var(--calendar-radius);\n  border-end-start-radius: var(--calendar-radius);\n}\n\n[data-slot=\"calendar-day\"]:is([data-in-range], [data-preview-middle]):is(\n    td:last-of-type,\n    td:has(+ td[data-hidden])\n  ) {\n  border-start-end-radius: var(--calendar-radius);\n  border-end-end-radius: var(--calendar-radius);\n}\n\n/* An end with no visible inward neighbour has nothing to reach toward. */\n[data-slot=\"calendar-day\"]:is([data-range-start], [data-preview-start]):is(\n    td:last-of-type,\n    td:has(+ td[data-hidden])\n  ),\n[data-slot=\"calendar-day\"]:is([data-range-end], [data-preview-end]):is(\n    td:first-of-type,\n    td[data-hidden] + td\n  ) {\n  --calendar-paint: none;\n  --calendar-paint-preview: none;\n}\n\n/* The preview's far end. Its fill covers the half band, which would otherwise\n * split the day into two tones inside the ring. */\n[data-slot=\"calendar-day\"][data-preview-target] > button {\n  --cell-paint: var(--calendar-band-preview);\n}\n[data-slot=\"calendar-day\"][data-preview-target] > button::before {\n  box-shadow: inset 0 0 0 1.5px\n    color-mix(in oklab, var(--color-primary) 60%, transparent);\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Landing a range\n *\n * One wipe from the start day toward the clicked end. Each cell gets its own\n * delay and duration from calendar.tsx so the wipe's head follows a single\n * ease-out curve across cells.\n * -------------------------------------------------------------------------------------------------*/\n\n[data-slot=\"calendar\"][data-range-landing]\n  [data-slot=\"calendar-day\"][data-sweep] {\n  background-image:\n    var(--calendar-paint, none), var(--calendar-paint-preview, none);\n  background-position: var(--calendar-wipe-from, left) center;\n  animation: calendar-band-wipe var(--calendar-wipe-duration) linear\n    var(--calendar-wipe-delay) backwards;\n}\n\n[data-slot=\"calendar\"][data-range-landing=\"forward\"] {\n  --calendar-wipe-from: left;\n}\n\n[data-slot=\"calendar\"][data-range-landing=\"backward\"],\n[data-slot=\"calendar\"][data-range-landing=\"forward\"]:dir(rtl) {\n  --calendar-wipe-from: right;\n}\n\n[data-slot=\"calendar\"][data-range-landing=\"backward\"]:dir(rtl) {\n  --calendar-wipe-from: left;\n}\n\n[data-slot=\"calendar\"][data-range-landing]\n  [data-slot=\"calendar-day\"][data-landing-target]\n  > button {\n  animation: calendar-cap-land 320ms var(--calendar-ease);\n}\n\n@keyframes calendar-band-wipe {\n  from {\n    background-size:\n      0% 100%,\n      100% 100%;\n  }\n  to {\n    background-size:\n      100% 100%,\n      100% 100%;\n  }\n}\n\n@keyframes calendar-cap-land {\n  from {\n    scale: 0.82;\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  [data-slot=\"calendar\"][data-range-landing] [data-slot=\"calendar-day\"],\n  [data-slot=\"calendar\"][data-range-landing]\n    [data-slot=\"calendar-day\"]\n    > button {\n    animation: none;\n  }\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Neighbouring picks\n *\n * Selected days side by side in a week join into one shape: the corners\n * between them square off. In multiple mode that's any two picks; in range\n * mode, a two-day range's ends. Logical corners, so it holds in RTL. The\n * button is matched by class to keep `:has()` cheap.\n * -------------------------------------------------------------------------------------------------*/\n\n[data-slot=\"calendar\"][data-mode=\"multiple\"]\n  [data-slot=\"calendar-day\"][data-filled]:not([data-hidden]):has(\n    + [data-filled]:not([data-hidden])\n  )\n  > .calendar-day-button::before,\n[data-slot=\"calendar\"][data-mode=\"range\"]\n  [data-slot=\"calendar-day\"][data-range-start]:not([data-hidden]):has(\n    + [data-range-end]:not([data-hidden])\n  )\n  > .calendar-day-button::before {\n  border-start-end-radius: 0;\n  border-end-end-radius: 0;\n}\n\n[data-slot=\"calendar\"][data-mode=\"multiple\"]\n  [data-slot=\"calendar-day\"][data-filled]:not([data-hidden])\n  + [data-filled]:not([data-hidden])\n  > .calendar-day-button::before,\n[data-slot=\"calendar\"][data-mode=\"range\"]\n  [data-slot=\"calendar-day\"][data-range-start]:not([data-hidden])\n  + [data-range-end]:not([data-hidden])\n  > .calendar-day-button::before {\n  border-start-start-radius: 0;\n  border-end-start-radius: 0;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Sliding selection (single mode), in CSS anchor positioning.\n *\n * One shared fill is anchored to the selected day so it slides between days.\n * Outside days with several months shown, and the aria-hidden outgoing month,\n * keep their own fill since the same date can appear twice. Without anchor\n * positioning, each day draws its own fill.\n * -------------------------------------------------------------------------------------------------*/\n\n.calendar-selection {\n  display: none;\n}\n\n/* All anchor features or none: a partial implementation would leave the fill\n * unplaced or tied to another calendar. */\n@supports (anchor-name: --calendar-selection) and\n  (anchor-scope: --calendar-selection) and (inset: anchor(inside)) {\n  [data-slot=\"calendar\"] {\n    anchor-scope: --calendar-selection;\n  }\n\n  [data-slot=\"calendar\"][data-mode=\"single\"]\n    [data-slot=\"calendar-day\"][data-filled]:not(\n      [data-multiple-months] [data-outside],\n      [aria-hidden=\"true\"] *\n    )\n    > button {\n    anchor-name: --calendar-selection;\n    --cell-paint: transparent;\n  }\n\n  /* The fill takes a moment to slide over from the last day, so the new\n   * day's light text waits for it instead of sitting on the bare card.\n   * Leaving, the text darkens over the same span as the fill slides off. */\n  [data-slot=\"calendar\"][data-mode=\"single\"]\n    [data-slot=\"calendar-day\"]\n    > button {\n    --cell-text-duration: 120ms;\n  }\n  [data-slot=\"calendar\"][data-mode=\"single\"]\n    [data-slot=\"calendar-day\"][data-filled]\n    > button {\n    --cell-text-delay: 70ms;\n  }\n\n  .calendar-selection {\n    display: block;\n    position: absolute;\n    position-anchor: --calendar-selection;\n    inset: anchor(inside);\n    z-index: -1;\n    border-radius: var(--calendar-radius);\n    background: var(--color-primary);\n    pointer-events: none;\n    transition:\n      inset var(--calendar-duration) var(--calendar-ease),\n      opacity 150ms ease-out,\n      scale var(--calendar-duration) var(--calendar-ease);\n  }\n\n  /* On a click it settles in; arriving with a new month it takes the weeks'\n   * entrance instead (data-enter is the direction, or \"instant\"). */\n  .calendar-selection:not([data-enter]) {\n    @starting-style {\n      opacity: 0;\n      scale: 0.9;\n    }\n  }\n\n  .calendar-selection:is([data-enter=\"start\"], [data-enter=\"end\"]) {\n    animation: calendar-enter var(--calendar-enter-duration)\n      var(--calendar-ease) var(--calendar-enter-delay) both;\n  }\n\n  .calendar-selection[data-enter=\"end\"] {\n    --calendar-from: var(--calendar-shift);\n  }\n\n  .calendar-selection[data-enter=\"start\"] {\n    --calendar-from: calc(var(--calendar-shift) * -1);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .calendar-selection {\n      transition: opacity 150ms ease-out;\n    }\n\n    /* The fill fades in place instead of sliding, so the text keeps pace. */\n    [data-slot=\"calendar\"][data-mode=\"single\"]\n      [data-slot=\"calendar-day\"]\n      > button,\n    [data-slot=\"calendar\"][data-mode=\"single\"]\n      [data-slot=\"calendar-day\"][data-filled]\n      > button {\n      --cell-text-duration: 150ms;\n      --cell-text-delay: 0s;\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Month changes\n *\n * Outgoing and incoming weeks are sequenced, not crossfaded, so two\n * half-transparent sets of numbers never overlap. DayPicker only finishes the\n * transition when the outgoing caption's animation ends, so that caption runs\n * an invisible hold as long as the incoming weeks take.\n * -------------------------------------------------------------------------------------------------*/\n\n[data-slot=\"calendar\"] {\n  --calendar-exit-duration: 150ms;\n  --calendar-enter-delay: 40ms;\n  --calendar-enter-duration: 280ms;\n}\n\n/* Unclipped so the card's shadow and rim aren't cut off. */\n[data-slot=\"calendar\"] [data-animated-month]:not([aria-hidden=\"true\"]) {\n  overflow: visible !important;\n}\n\n/* Sizes the outgoing clone to the live month, so DayPicker's overflow: hidden\n * clips an extra week instead of letting it hang below a shrunk calendar. */\n[data-slot=\"calendar\"] [data-animated-month][aria-hidden=\"true\"] {\n  inset: 0;\n}\n\n/* Stacked (below sm), the clone's header strip would cover the live chevron. */\n[data-slot=\"calendar\"] [data-animated-month][aria-hidden=\"true\"]::before {\n  content: none;\n}\n\n/* So the clone's card can't cover the incoming weekday row. */\n[data-slot=\"calendar\"]\n  [data-animated-month][aria-hidden=\"true\"]\n  [data-slot=\"calendar-grid\"] {\n  background: none;\n  box-shadow: none;\n}\n\n.calendar-enter-from-start,\n.calendar-enter-from-end {\n  animation: calendar-enter var(--calendar-enter-duration) var(--calendar-ease)\n    var(--calendar-enter-delay) both;\n}\n\n.calendar-exit-to-start,\n.calendar-exit-to-end {\n  animation: calendar-exit var(--calendar-exit-duration) ease-out both;\n}\n\n.calendar-enter-from-start {\n  --calendar-from: calc(var(--calendar-shift) * -1);\n}\n\n.calendar-enter-from-end {\n  --calendar-from: var(--calendar-shift);\n}\n\n.calendar-exit-to-start {\n  --calendar-to: calc(var(--calendar-shift) * -1);\n}\n\n.calendar-exit-to-end {\n  --calendar-to: var(--calendar-shift);\n}\n\n.calendar-caption-hold {\n  visibility: hidden;\n  animation: calendar-hold\n    calc(var(--calendar-enter-delay) + var(--calendar-enter-duration)) linear\n    both;\n}\n\n@keyframes calendar-enter {\n  from {\n    opacity: 0;\n    translate: var(--calendar-from) 0;\n    filter: blur(var(--calendar-blur));\n  }\n}\n\n@keyframes calendar-exit {\n  to {\n    opacity: 0;\n    translate: var(--calendar-to) 0;\n    filter: blur(var(--calendar-blur));\n  }\n}\n\n@keyframes calendar-hold {\n  to {\n    opacity: 0;\n  }\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Month and year picker\n *\n * Transitions rather than animations, so a quick double click on the caption\n * reverses mid-flight instead of restarting.\n * -------------------------------------------------------------------------------------------------*/\n\n[data-slot=\"calendar-grid\"],\n[data-slot=\"calendar-nav\"] {\n  transition-property: opacity, scale, filter, visibility;\n  transition-duration: 150ms, var(--calendar-picker-duration), 150ms, 0s;\n  transition-timing-function: ease-out, var(--calendar-ease), ease-out, linear;\n}\n\n[data-slot=\"calendar-grid\"][data-covered] {\n  opacity: 0;\n  scale: calc(1 - var(--calendar-zoom) / 3);\n  filter: blur(var(--calendar-blur));\n}\n\n[data-slot=\"calendar\"][data-picker-open] [data-slot=\"calendar-nav\"] {\n  opacity: 0;\n  visibility: hidden;\n  transition-delay: 0s, 0s, 0s, 150ms;\n}\n\n[data-slot=\"calendar\"][data-picker-open] .calendar-selection {\n  opacity: 0;\n}\n\n[data-slot=\"calendar-picker\"] {\n  transition:\n    opacity 150ms ease-out,\n    scale var(--calendar-picker-duration) var(--calendar-ease),\n    filter 150ms ease-out,\n    visibility 0s linear 0s;\n}\n\n[data-slot=\"calendar-picker\"][data-state=\"closed\"] {\n  opacity: 0;\n  scale: calc(1 - var(--calendar-zoom) / 3);\n  filter: blur(var(--calendar-blur));\n  visibility: hidden;\n  pointer-events: none;\n  transition-delay: 0s, 0s, 0s, var(--calendar-picker-duration);\n}\n",
      "type": "registry:file",
      "target": "components/ui/cubby-ui/calendar/calendar.css"
    }
  ],
  "type": "registry:ui"
}