{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-range-picker",
  "type": "registry:ui",
  "title": "Date Range Picker",
  "description": "Date range picker calendar with dual-month navigation, a quick presets rail, range capsules and rolling label animations.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/date-range-picker.tsx",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type CSSProperties,\n  type ReactNode,\n} from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  type Variants,\n} from \"motion/react\";\nimport { Calendar, Check, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface DateRange {\n  start: Date;\n  end: Date;\n}\n\nexport interface DateRangePreset {\n  label: string;\n  range: (today: Date) => DateRange;\n}\n\nexport interface DateRangePickerProps {\n  value?: DateRange | null;\n  defaultValue?: DateRange | null;\n  onChange?: (range: DateRange) => void;\n  onApply?: (range: DateRange) => void;\n  onCancel?: () => void;\n  label?: string;\n  placeholder?: string;\n  presets?: DateRangePreset[];\n  minDate?: Date;\n  maxDate?: Date;\n  weekStartsOn?: 0 | 1;\n  locale?: string;\n  months?: \"auto\" | 1 | 2;\n  className?: string;\n}\n\ntype Bezier = [number, number, number, number];\n\nconst DAY = 864e5;\nconst startOfDay = (date: Date) =>\n  new Date(date.getFullYear(), date.getMonth(), date.getDate());\nconst monthStart = (date: Date) =>\n  new Date(date.getFullYear(), date.getMonth(), 1);\nconst monthEnd = (date: Date) =>\n  new Date(date.getFullYear(), date.getMonth() + 1, 0);\nconst addDays = (date: Date, amount: number) =>\n  new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);\nconst addMonths = (date: Date, amount: number) =>\n  new Date(date.getFullYear(), date.getMonth() + amount, 1);\nconst shiftMonths = (date: Date, amount: number) =>\n  new Date(\n    date.getFullYear(),\n    date.getMonth() + amount,\n    Math.min(\n      date.getDate(),\n      new Date(date.getFullYear(), date.getMonth() + amount + 1, 0).getDate(),\n    ),\n  );\nconst dayDiff = (a: Date, b: Date) =>\n  Math.round((startOfDay(a).getTime() - startOfDay(b).getTime()) / DAY);\nconst monthDiff = (a: Date, b: Date) =>\n  (a.getFullYear() - b.getFullYear()) * 12 + a.getMonth() - b.getMonth();\nconst sameDay = (a?: Date | null, b?: Date | null) =>\n  Boolean(a && b && dayDiff(a, b) === 0);\nconst keyOf = (date: Date) =>\n  `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}-${String(\n    date.getDate(),\n  ).padStart(2, \"0\")}`;\nconst fromKey = (key: string) => {\n  const [year, month, day] = key.split(\"-\").map(Number);\n  return new Date(year, month - 1, day);\n};\nconst ordered = (a: Date, b: Date): DateRange =>\n  dayDiff(a, b) <= 0 ? { start: a, end: b } : { start: b, end: a };\nconst viewFor = (range: DateRange | null, single: boolean, fallback: Date) => {\n  const end = monthStart(range?.end ?? fallback);\n  return single ? end : addMonths(end, -1);\n};\nconst sameRange = (a?: DateRange | null, b?: DateRange | null) =>\n  Boolean(a && b && sameDay(a.start, b.start) && sameDay(a.end, b.end));\nconst clampDate = (date: Date, min?: Date, max?: Date) =>\n  min && dayDiff(date, min) < 0\n    ? startOfDay(min)\n    : max && dayDiff(date, max) > 0\n      ? startOfDay(max)\n      : date;\n\nexport const defaultDateRangePresets: DateRangePreset[] = [\n  { label: \"Today\", range: (today) => ({ start: today, end: today }) },\n  {\n    label: \"Yesterday\",\n    range: (today) => ({\n      start: addDays(today, -1),\n      end: addDays(today, -1),\n    }),\n  },\n  {\n    label: \"Last 7 days\",\n    range: (today) => ({ start: addDays(today, -6), end: today }),\n  },\n  {\n    label: \"Last 30 days\",\n    range: (today) => ({ start: addDays(today, -29), end: today }),\n  },\n  {\n    label: \"This month\",\n    range: (today) => ({ start: monthStart(today), end: today }),\n  },\n  {\n    label: \"Last month\",\n    range: (today) => ({\n      start: addMonths(today, -1),\n      end: monthEnd(addMonths(today, -1)),\n    }),\n  },\n  {\n    label: \"This quarter\",\n    range: (today) => ({\n      start: new Date(\n        today.getFullYear(),\n        Math.floor(today.getMonth() / 3) * 3,\n        1,\n      ),\n      end: today,\n    }),\n  },\n  {\n    label: \"Year to date\",\n    range: (today) => ({\n      start: new Date(today.getFullYear(), 0, 1),\n      end: today,\n    }),\n  },\n];\n\nconst subscribeToday = (notify: () => void) => {\n  let timer = 0;\n  const schedule = () => {\n    const now = new Date();\n    timer = window.setTimeout(\n      () => {\n        notify();\n        schedule();\n      },\n      addDays(now, 1).getTime() - now.getTime() + 1000,\n    );\n  };\n  const onVisible = () => {\n    if (document.visibilityState === \"visible\") notify();\n  };\n  schedule();\n  document.addEventListener(\"visibilitychange\", onVisible);\n  return () => {\n    window.clearTimeout(timer);\n    document.removeEventListener(\"visibilitychange\", onVisible);\n  };\n};\n\nconst readToday = () => keyOf(new Date());\nconst serverToday = () => \"\";\n\nexport function useToday() {\n  const key = useSyncExternalStore(subscribeToday, readToday, serverToday);\n  return useMemo(() => (key ? fromKey(key) : undefined), [key]);\n}\n\nfunction useIsSmallContainer(rootRef: React.RefObject<HTMLDivElement | null>) {\n  const [isSmall, setIsSmall] = useState(true);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    if (!root) return;\n    const observer = new ResizeObserver(([entry]) => {\n      setIsSmall(entry.contentRect.width < 780);\n    });\n    observer.observe(root);\n    return () => observer.disconnect();\n  }, [rootRef]);\n\n  return isSmall;\n}\n\nconst noop = () => () => {};\nfunction useReducedFlag() {\n  const hydrated = useSyncExternalStore(\n    noop,\n    () => true,\n    () => false,\n  );\n  return !!useReducedMotion() && hydrated;\n}\n\nconst enterEase: Bezier = [0.16, 1, 0.3, 1];\nconst standardEase: Bezier = [0.7, 0, 0.84, 0];\n\nconst roll: Variants = {\n  enter: (direction: number) => ({\n    opacity: 0,\n    y: `${direction * 0.45}em`,\n    filter: \"blur(2px)\",\n  }),\n  center: {\n    opacity: 1,\n    y: \"0em\",\n    filter: \"blur(0px)\",\n    transition: { duration: 0.24, ease: enterEase },\n  },\n  exit: (direction: number) => ({\n    opacity: 0,\n    y: `${direction * -0.45}em`,\n    filter: \"blur(2px)\",\n    transition: { duration: 0.14, ease: standardEase },\n  }),\n};\n\nconst fade: Variants = {\n  enter: { opacity: 0 },\n  center: {\n    opacity: 1,\n    y: \"0em\",\n    filter: \"blur(0px)\",\n    transition: { duration: 0.12 },\n  },\n  exit: { opacity: 0, transition: { duration: 0.08 } },\n};\n\nconst slide: Variants = {\n  enter: (direction: number) => ({\n    opacity: 0,\n    x: direction * 28,\n  }),\n  center: {\n    opacity: 1,\n    x: 0,\n    transition: {\n      duration: 0.2,\n      ease: enterEase,\n    },\n  },\n  exit: (direction: number) => ({\n    opacity: 0,\n    x: direction * -28,\n    transition: {\n      duration: 0.14,\n      ease: standardEase,\n    },\n  }),\n};\n\nfunction Rolling({\n  text,\n  direction,\n  reduced,\n}: {\n  text: string;\n  direction: number;\n  reduced: boolean;\n}) {\n  const words = text.split(/\\s+/).filter(Boolean);\n  return (\n    <span\n      className=\"inline-flex flex-wrap items-baseline gap-[0.25em]\"\n      aria-hidden=\"true\"\n    >\n      {words.map((word, index) => (\n        <span\n          key={index}\n          className=\"inline-block overflow-hidden h-[1.25em] leading-[1.25em] relative align-baseline\"\n        >\n          <AnimatePresence initial={false} mode=\"popLayout\" custom={direction}>\n            <motion.span\n              key={word}\n              className=\"inline-block align-baseline\"\n              custom={direction}\n              variants={reduced ? fade : roll}\n              initial=\"enter\"\n              animate=\"center\"\n              exit=\"exit\"\n            >\n              {word}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n      ))}\n    </span>\n  );\n}\n\nfunction useDirection(range: DateRange | null) {\n  const time = range ? range.start.getTime() * 2 + range.end.getTime() : null;\n  const [previous, setPrevious] = useState(time);\n  const [direction, setDirection] = useState(1);\n  if (time !== previous) {\n    setPrevious(time);\n    setDirection(\n      time === null || previous === null || time >= previous ? 1 : -1,\n    );\n  }\n  return direction;\n}\n\ntype Segment = { left: number; width: number } | null;\n\ninterface MonthProps {\n  month: Date;\n  range: DateRange | null;\n  tabbable: string;\n  today?: Date;\n  minDate?: Date;\n  maxDate?: Date;\n  weekStartsOn: 0 | 1;\n  formatters: {\n    title: Intl.DateTimeFormat;\n    day: Intl.DateTimeFormat;\n    weekday: Intl.DateTimeFormat;\n    weekdayLong: Intl.DateTimeFormat;\n  };\n  onPick: (date: Date) => void;\n  onHover: (date: Date) => void;\n  onKey: (event: ReactKeyboardEvent<HTMLButtonElement>, date: Date) => void;\n  onFocusDay: (date: Date) => void;\n  focusRequest: string | null;\n  idBase: string;\n}\n\nfunction Month({\n  month,\n  range,\n  tabbable,\n  today,\n  minDate,\n  maxDate,\n  weekStartsOn,\n  formatters,\n  onPick,\n  onHover,\n  onKey,\n  onFocusDay,\n  focusRequest,\n  idBase,\n}: MonthProps) {\n  const gridRef = useRef<HTMLDivElement>(null);\n  const first = useMemo(\n    () => addDays(month, -((month.getDay() - weekStartsOn + 7) % 7)),\n    [month, weekStartsOn],\n  );\n  const rows = useMemo(\n    () =>\n      Array.from({ length: 6 }, (_, row) =>\n        Array.from({ length: 7 }, (_, col) => addDays(first, row * 7 + col)),\n      ),\n    [first],\n  );\n  const titleId = `${idBase}-${keyOf(month)}`;\n\n  useEffect(() => {\n    if (!focusRequest) return;\n    gridRef.current\n      ?.querySelector<HTMLButtonElement>(`[data-date=\"${focusRequest}\"]`)\n      ?.focus();\n  }, [focusRequest]);\n  const inMonth = (date: Date) => monthDiff(date, month) === 0;\n\n  const lo = range?.start;\n  const hi = range?.end;\n  const loKey = lo ? keyOf(lo) : \"\";\n  const hiKey = hi ? keyOf(hi) : \"\";\n\n  const segments = useMemo<Segment[]>(() => {\n    if (!loKey || !hiKey) return rows.map(() => null);\n    const start = fromKey(loKey);\n    const end = fromKey(hiKey);\n    const unit = 100 / 7;\n    const colOf = (date: Date) => dayDiff(date, first) % 7;\n\n    return rows.map((row) => {\n      const days = row.filter((date) => monthDiff(date, month) === 0);\n      if (!days.length) return null;\n      const inRangeDays = days.filter(\n        (date) => dayDiff(date, start) >= 0 && dayDiff(date, end) <= 0,\n      );\n      if (inRangeDays.length <= 1) return null;\n\n      const firstInRange = inRangeDays[0];\n      const lastInRange = inRangeDays[inRangeDays.length - 1];\n      const a = colOf(firstInRange);\n      const b = colOf(lastInRange);\n\n      return {\n        left: a * unit,\n        width: (b - a + 1) * unit,\n      };\n    });\n  }, [first, hiKey, loKey, month, rows]);\n\n  return (\n    <div className=\"flex flex-col select-none w-full @min-[480px]/picker:w-66.5 shrink-0\">\n      <p\n        id={titleId}\n        className=\"text-[15px] font-semibold text-zinc-900 dark:text-zinc-100 text-center mb-3.5 tracking-tight\"\n      >\n        {formatters.title.format(month)}\n      </p>\n      <div\n        ref={gridRef}\n        role=\"grid\"\n        aria-labelledby={titleId}\n        className=\"flex flex-col\"\n      >\n        <div role=\"row\" className=\"grid grid-cols-7 mb-2.5\">\n          {rows[0].map((date) => (\n            <span\n              key={date.getDay()}\n              role=\"columnheader\"\n              aria-label={formatters.weekdayLong.format(date)}\n              className=\"text-[11.5px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500 text-center py-0.5\"\n            >\n              {formatters.weekday.format(date).slice(0, 2)}\n            </span>\n          ))}\n        </div>\n        <div className=\"relative h-57\">\n          <span\n            className=\"absolute inset-0 pointer-events-none z-10\"\n            aria-hidden=\"true\"\n          >\n            {segments.map((segment, row) => {\n              if (!segment) return null;\n              return (\n                <span\n                  key={row}\n                  className=\"absolute h-9 rounded-full bg-zinc-150 dark:bg-zinc-800/90 border border-zinc-200/50 dark:border-white/4 pointer-events-none transition-all duration-150\"\n                  style={{\n                    left: `${segment.left}%`,\n                    width: `${segment.width}%`,\n                    top: `calc(${row} * 38px + 1px)`,\n                  }}\n                />\n              );\n            })}\n          </span>\n          <div className=\"grid grid-rows-6 h-full\">\n            {rows.map((row, rowIndex) => (\n              <div key={rowIndex} role=\"row\" className=\"grid grid-cols-7 h-9.5\">\n                {row.map((date) => {\n                  if (!inMonth(date)) {\n                    return (\n                      <span\n                        key={keyOf(date)}\n                        role=\"gridcell\"\n                        className=\"w-full h-full\"\n                      />\n                    );\n                  }\n                  const key = keyOf(date);\n                  const disabled = Boolean(\n                    (minDate && dayDiff(date, minDate) < 0) ||\n                    (maxDate && dayDiff(date, maxDate) > 0),\n                  );\n                  const inRange = Boolean(\n                    lo &&\n                    hi &&\n                    dayDiff(date, lo) >= 0 &&\n                    dayDiff(date, hi) <= 0,\n                  );\n                  const isEdge = sameDay(date, lo) || sameDay(date, hi);\n                  const isToday = sameDay(date, today);\n\n                  return (\n                    <span\n                      key={key}\n                      role=\"gridcell\"\n                      aria-selected={inRange}\n                      className=\"w-full h-9.5 flex items-center justify-center relative\"\n                    >\n                      <button\n                        type=\"button\"\n                        data-date={key}\n                        data-edge={isEdge || undefined}\n                        data-range={inRange || undefined}\n                        data-today={isToday || undefined}\n                        tabIndex={key === tabbable ? 0 : -1}\n                        disabled={disabled}\n                        aria-current={isToday ? \"date\" : undefined}\n                        aria-label={formatters.day.format(date)}\n                        onClick={() => onPick(date)}\n                        onPointerEnter={() => {\n                          if (!disabled) onHover(date);\n                        }}\n                        onFocus={() => onFocusDay(date)}\n                        onKeyDown={(event) => onKey(event, date)}\n                        className={cn(\n                          \"w-9 h-9 rounded-full flex flex-col items-center justify-center relative z-20 transition-all duration-150 cursor-pointer select-none\",\n                          isEdge\n                            ? \"bg-zinc-900 text-white dark:bg-white dark:text-zinc-950 font-bold shadow-[0_2px_10px_rgba(0,0,0,0.18)] dark:shadow-[0_2px_12px_rgba(255,255,255,0.22)] scale-[1.02]\"\n                            : inRange\n                              ? \"text-zinc-900 dark:text-zinc-100 font-semibold\"\n                              : \"text-zinc-600 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white hover:bg-zinc-100 dark:hover:bg-white/8 font-medium\",\n                          disabled &&\n                            \"opacity-25 cursor-not-allowed hover:bg-transparent\",\n                        )}\n                      >\n                        <span className=\"leading-none text-sm\">\n                          {date.getDate()}\n                        </span>\n                        {isToday && (\n                          <span\n                            className={cn(\n                              \"w-1 h-1 rounded-full mt-0.5\",\n                              isEdge\n                                ? \"bg-white dark:bg-zinc-950\"\n                                : \"bg-zinc-900 dark:bg-zinc-400\",\n                            )}\n                          />\n                        )}\n                      </button>\n                    </span>\n                  );\n                })}\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction MonthsView({\n  children,\n  direction,\n  reduced,\n}: {\n  children: ReactNode;\n  direction: number;\n  reduced: boolean;\n}) {\n  return (\n    <motion.div\n      custom={direction}\n      variants={reduced ? fade : slide}\n      initial=\"enter\"\n      animate=\"center\"\n      exit=\"exit\"\n      className=\"flex flex-col @min-[780px]/picker:flex-row gap-6 @min-[780px]/picker:gap-10 w-full\"\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport function DateRangePicker({\n  value,\n  defaultValue,\n  onChange,\n  onApply,\n  onCancel,\n  label = \"Date range\",\n  placeholder = \"Select dates\",\n  presets = defaultDateRangePresets,\n  minDate,\n  maxDate,\n  weekStartsOn = 0,\n  locale = \"en-US\",\n  months = \"auto\",\n  className,\n}: DateRangePickerProps) {\n  const reduced = useReducedFlag();\n  const rootRef = useRef<HTMLDivElement>(null);\n  const isSmallScreen = useIsSmallContainer(rootRef);\n  const detectedToday = useToday();\n  const hydrated = detectedToday !== undefined;\n  const today = useMemo(() => detectedToday ?? new Date(), [detectedToday]);\n  const uid = useId();\n\n  const initialRange = useMemo(() => {\n    if (value !== undefined) return value;\n    if (defaultValue !== undefined) return defaultValue;\n    return {\n      start: addDays(today, -6),\n      end: today,\n    };\n  }, [defaultValue, today, value]);\n\n  const [inner, setInner] = useState<DateRange | null>(initialRange);\n  const committed = value !== undefined ? value : inner;\n\n  const compact = months === 1 || (months === \"auto\" && isSmallScreen);\n\n  const [view, setView] = useState<Date>(() =>\n    viewFor(committed, compact, today),\n  );\n  const [previousCompact, setPreviousCompact] = useState(compact);\n\n  if (previousCompact !== compact) {\n    setPreviousCompact(compact);\n    setView(viewFor(committed, compact, today));\n  }\n  const [direction, setDirection] = useState(1);\n  const [draft, setDraft] = useState<DateRange | null>(committed);\n  const [anchor, setAnchor] = useState<Date | null>(null);\n  const [hover, setHover] = useState<Date | null>(null);\n  const [focusKey, setFocusKey] = useState(\"\");\n  const [focusRequest, setFocusRequest] = useState<string | null>(null);\n  const [isApplied, setIsApplied] = useState(false);\n  const appliedTimerRef = useRef<number | null>(null);\n\n  const formatters = useMemo(\n    () => ({\n      label: new Intl.DateTimeFormat(locale, {\n        month: \"short\",\n        day: \"numeric\",\n        year: \"numeric\",\n      }),\n      title: new Intl.DateTimeFormat(locale, {\n        month: \"long\",\n        year: \"numeric\",\n      }),\n      day: new Intl.DateTimeFormat(locale, {\n        weekday: \"long\",\n        month: \"long\",\n        day: \"numeric\",\n        year: \"numeric\",\n      }),\n      weekday: new Intl.DateTimeFormat(locale, { weekday: \"short\" }),\n      weekdayLong: new Intl.DateTimeFormat(locale, { weekday: \"long\" }),\n    }),\n    [locale],\n  );\n\n  const format = useCallback(\n    (range: DateRange | null) => {\n      if (!range) return placeholder;\n      if (sameDay(range.start, range.end)) {\n        return formatters.label.format(range.start);\n      }\n      return formatters.label.formatRange(range.start, range.end);\n    },\n    [formatters, placeholder],\n  );\n\n  const count = compact ? 1 : 2;\n  const visible = useMemo(\n    () => Array.from({ length: count }, (_, index) => addMonths(view, index)),\n    [count, view],\n  );\n\n  const shown = anchor ? ordered(anchor, hover ?? anchor) : draft;\n  const activePreset =\n    hydrated && !anchor\n      ? presets.findIndex((preset) => sameRange(preset.range(today), draft))\n      : -1;\n  const days = shown ? dayDiff(shown.end, shown.start) + 1 : 0;\n  const shownDirection = useDirection(shown);\n\n  const goTo = useCallback((next: Date) => {\n    setView((prev) => {\n      const delta = monthDiff(next, prev);\n      if (!delta) return prev;\n      setDirection(Math.sign(delta));\n      return next;\n    });\n  }, []);\n\n  const reveal = useCallback(\n    (range: DateRange) => {\n      const first = view;\n      const last = addMonths(view, count - 1);\n      if (monthDiff(range.start, first) >= 0 && monthDiff(range.end, last) <= 0)\n        return;\n      goTo(viewFor(range, compact, range.end));\n    },\n    [compact, count, goTo, view],\n  );\n\n  const pick = (date: Date) => {\n    setFocusKey(keyOf(date));\n    if (!anchor) {\n      setAnchor(date);\n      setHover(date);\n      return;\n    }\n    const next = ordered(anchor, date);\n    setDraft(next);\n    setAnchor(null);\n    setHover(null);\n    if (value === undefined) setInner(next);\n    onChange?.(next);\n  };\n\n  const choosePreset = (preset: DateRangePreset) => {\n    const range = preset.range(today);\n    setDraft(range);\n    setAnchor(null);\n    setHover(null);\n    setFocusKey(keyOf(range.start));\n    reveal(range);\n    if (value === undefined) setInner(range);\n    onChange?.(range);\n  };\n\n  const handleApply = () => {\n    const next = anchor ? { start: anchor, end: anchor } : draft;\n    if (!next) return;\n    if (value === undefined) setInner(next);\n    setIsApplied(true);\n    if (appliedTimerRef.current) window.clearTimeout(appliedTimerRef.current);\n    appliedTimerRef.current = window.setTimeout(() => {\n      setIsApplied(false);\n    }, 1500);\n    onChange?.(next);\n    onApply?.(next);\n  };\n\n  const handleCancel = () => {\n    setDraft(committed);\n    setAnchor(null);\n    setHover(null);\n    onCancel?.();\n  };\n\n  const onDayKey = (\n    event: ReactKeyboardEvent<HTMLButtonElement>,\n    date: Date,\n  ) => {\n    const weekday = (date.getDay() - weekStartsOn + 7) % 7;\n    const moves: Record<string, () => Date> = {\n      ArrowLeft: () => addDays(date, -1),\n      ArrowRight: () => addDays(date, 1),\n      ArrowUp: () => addDays(date, -7),\n      ArrowDown: () => addDays(date, 7),\n      Home: () => addDays(date, -weekday),\n      End: () => addDays(date, 6 - weekday),\n      PageUp: () => shiftMonths(date, event.shiftKey ? -12 : -1),\n      PageDown: () => shiftMonths(date, event.shiftKey ? 12 : 1),\n    };\n    const move = moves[event.key];\n    if (!move) return;\n    event.preventDefault();\n    const next = clampDate(move(), minDate, maxDate);\n    setFocusKey(keyOf(next));\n    setFocusRequest(keyOf(next));\n    if (anchor) setHover(next);\n    const first = view;\n    const last = addMonths(view, count - 1);\n    if (monthDiff(next, first) < 0) goTo(monthStart(next));\n    else if (monthDiff(next, last) > 0)\n      goTo(addMonths(monthStart(next), -(count - 1)));\n  };\n\n  const tabbable = useMemo(() => {\n    const onScreen = (date: Date) =>\n      visible.some((month) => monthDiff(date, month) === 0);\n    if (focusKey && onScreen(fromKey(focusKey))) return focusKey;\n    if (shown && onScreen(shown.start)) return keyOf(shown.start);\n    return keyOf(visible[0]);\n  }, [focusKey, shown, visible]);\n\n  const shownText = !hydrated ? \"\" : shown ? format(shown) : placeholder;\n  const countText =\n    hydrated && shown ? `${days} ${days === 1 ? \"day\" : \"days\"}` : \"\";\n  const status = !hydrated\n    ? \"\"\n    : anchor\n      ? `Start ${formatters.label.format(anchor)}. Choose an end date.`\n      : shown\n        ? `${shownText}, ${countText}`\n        : \"\";\n\n  return (\n    <div\n      ref={rootRef}\n      role=\"region\"\n      aria-label={label}\n      style={{ \"--cell\": \"38px\" } as CSSProperties}\n      className={cn(\n        \"@container/picker relative inline-flex w-full max-w-[880px] flex-col bg-white/95 dark:bg-[#141416]/95 border border-zinc-200/90 dark:border-white/8 rounded-3xl p-[clamp(16px,4%,28px)] shadow-[0_20px_50px_-12px_rgba(0,0,0,0.08),0_1px_3px_rgba(0,0,0,0.05)] dark:shadow-[0_24px_60px_-12px_rgba(0,0,0,0.8),0_0_0_1px_rgba(255,255,255,0.04)] backdrop-blur-xl text-zinc-900 dark:text-zinc-100 overflow-hidden select-none\",\n        months === 1 && \"max-w-[544px]\",\n        className,\n      )}\n    >\n      <div className=\"absolute inset-x-0 top-0 h-px bg-linear-to-r from-transparent via-zinc-400/25 dark:via-white/20 to-transparent pointer-events-none rounded-t-3xl\" />\n\n      <div className=\"flex flex-col @min-[480px]/picker:flex-row gap-6 @min-[480px]/picker:gap-8 items-stretch\">\n        <div\n          role=\"group\"\n          aria-label=\"Date presets\"\n          className=\"flex flex-row @min-[480px]/picker:flex-col gap-1 overflow-x-auto @min-[480px]/picker:overflow-visible pb-2 @min-[480px]/picker:pb-0 @min-[480px]/picker:pr-6 @min-[480px]/picker:border-r border-zinc-200/80 dark:border-white/6 shrink-0 scrollbar-none w-full @min-[480px]/picker:w-44\"\n        >\n          {presets.map((preset, index) => {\n            const isActive = index === activePreset;\n            return (\n              <button\n                key={preset.label}\n                type=\"button\"\n                data-preset={index}\n                aria-pressed={isActive}\n                onClick={() => choosePreset(preset)}\n                className={cn(\n                  \"relative px-4 py-2.5 rounded-xl text-[13.5px] font-medium text-left transition-colors whitespace-nowrap cursor-pointer z-10 select-none\",\n                  isActive\n                    ? \"text-zinc-950 dark:text-white font-semibold\"\n                    : \"text-zinc-500 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 hover:bg-zinc-100/70 dark:hover:bg-white/4\",\n                )}\n              >\n                {isActive && (\n                  <motion.span\n                    layoutId={`${uid}-preset-pill`}\n                    className=\"absolute inset-0 rounded-xl bg-zinc-200/80 dark:bg-zinc-800/90 shadow-sm dark:shadow-inner border border-zinc-300/40 dark:border-white/6 -z-10\"\n                    transition={{ type: \"spring\", stiffness: 420, damping: 32 }}\n                  />\n                )}\n                <span>{preset.label}</span>\n              </button>\n            );\n          })}\n        </div>\n\n        <div\n          className=\"relative min-w-0 flex-1\"\n          onPointerLeave={() => {\n            if (anchor) setHover(null);\n          }}\n        >\n          <div className=\"absolute top-0 left-0 z-30\">\n            <motion.button\n              type=\"button\"\n              whileHover={{ scale: 1.05 }}\n              whileTap={{ scale: 0.9 }}\n              transition={{ type: \"spring\", stiffness: 450, damping: 25 }}\n              aria-label=\"Previous month\"\n              onClick={() => goTo(addMonths(view, -1))}\n              disabled={Boolean(minDate && monthDiff(view, minDate) <= 0)}\n              className=\"w-8 h-8 rounded-full flex items-center justify-center text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white border border-zinc-200/80 dark:border-white/8 hover:bg-zinc-100 dark:hover:bg-white/6 hover:border-zinc-300 dark:hover:border-white/15 transition-all disabled:opacity-30 disabled:pointer-events-none cursor-pointer shadow-xs\"\n            >\n              <ChevronLeft size={16} strokeWidth={2.2} aria-hidden=\"true\" />\n            </motion.button>\n          </div>\n          <div className=\"absolute top-0 right-0 z-30\">\n            <motion.button\n              type=\"button\"\n              whileHover={{ scale: 1.05 }}\n              whileTap={{ scale: 0.9 }}\n              transition={{ type: \"spring\", stiffness: 450, damping: 25 }}\n              aria-label=\"Next month\"\n              onClick={() => goTo(addMonths(view, 1))}\n              disabled={Boolean(\n                maxDate && monthDiff(addMonths(view, count - 1), maxDate) >= 0,\n              )}\n              className=\"w-8 h-8 rounded-full flex items-center justify-center text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white border border-zinc-200/80 dark:border-white/8 hover:bg-zinc-100 dark:hover:bg-white/6 hover:border-zinc-300 dark:hover:border-white/15 transition-all disabled:opacity-30 disabled:pointer-events-none cursor-pointer shadow-xs\"\n            >\n              <ChevronRight size={16} strokeWidth={2.2} aria-hidden=\"true\" />\n            </motion.button>\n          </div>\n\n          <div className=\"relative overflow-hidden w-full min-h-73.5\">\n            {hydrated && (\n              <AnimatePresence initial={false} mode=\"wait\" custom={direction}>\n                <MonthsView\n                  key={`${keyOf(view)}-${count}`}\n                  direction={direction}\n                  reduced={reduced}\n                >\n                  {visible.map((month) => (\n                    <Month\n                      key={keyOf(month)}\n                      month={month}\n                      range={shown}\n                      tabbable={tabbable}\n                      today={today}\n                      minDate={minDate}\n                      maxDate={maxDate}\n                      weekStartsOn={weekStartsOn}\n                      formatters={formatters}\n                      idBase={uid}\n                      onPick={pick}\n                      onHover={(date) => {\n                        if (anchor) setHover(date);\n                      }}\n                      onKey={onDayKey}\n                      onFocusDay={(date) => {\n                        setFocusKey(keyOf(date));\n                        setFocusRequest(null);\n                      }}\n                      focusRequest={focusRequest}\n                    />\n                  ))}\n                </MonthsView>\n              </AnimatePresence>\n            )}\n          </div>\n        </div>\n      </div>\n\n      <div className=\"mt-6 pt-5 border-t border-zinc-200/80 dark:border-white/8 flex flex-wrap items-center justify-between gap-4\">\n        <div className=\"flex items-center gap-3\">\n          <div className=\"w-8 h-8 rounded-lg bg-zinc-100 dark:bg-white/6 border border-zinc-200/60 dark:border-white/8 flex items-center justify-center text-zinc-600 dark:text-zinc-400 shrink-0 shadow-xs\">\n            <Calendar size={15} strokeWidth={2} />\n          </div>\n          <div className=\"flex flex-col\">\n            <span className=\"text-[14.5px] font-semibold text-zinc-950 dark:text-white tracking-tight\">\n              <Rolling\n                text={shownText}\n                direction={shownDirection}\n                reduced={reduced}\n              />\n            </span>\n            <div className=\"flex items-center gap-1.5 mt-0.5\">\n              {countText && (\n                <span className=\"inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-medium bg-zinc-100 dark:bg-white/[0.07] text-zinc-600 dark:text-zinc-300 border border-zinc-200/60 dark:border-white/6\">\n                  <Rolling\n                    text={\n                      anchor && sameDay(anchor, hover)\n                        ? \"Pick an end date\"\n                        : countText\n                    }\n                    direction={shownDirection}\n                    reduced={reduced}\n                  />\n                </span>\n              )}\n            </div>\n          </div>\n        </div>\n\n        <div className=\"flex items-center gap-2.5 ml-auto\">\n          <motion.button\n            type=\"button\"\n            whileHover={{ scale: 1.03 }}\n            whileTap={{ scale: 0.95 }}\n            transition={{ type: \"spring\", stiffness: 450, damping: 25 }}\n            onClick={handleCancel}\n            className=\"px-4 py-2 text-[13.5px] font-medium text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white transition-colors cursor-pointer rounded-full hover:bg-zinc-100 dark:hover:bg-white/6\"\n          >\n            Cancel\n          </motion.button>\n          <motion.button\n            type=\"button\"\n            whileHover={{ scale: !shown ? 1 : 1.03 }}\n            whileTap={{ scale: !shown ? 1 : 0.95 }}\n            transition={{ type: \"spring\", stiffness: 450, damping: 25 }}\n            onClick={handleApply}\n            disabled={!shown}\n            className={cn(\n              \"relative flex items-center justify-center min-w-21 px-5 py-2 text-[13.5px] font-semibold rounded-full cursor-pointer transition-all duration-200 select-none shadow-sm\",\n              isApplied\n                ? \"bg-emerald-600 text-white shadow-emerald-500/25 dark:bg-emerald-500 dark:text-white\"\n                : \"bg-zinc-950 text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-100 dark:shadow-[0_2px_12px_rgba(255,255,255,0.2)]\",\n              !shown && \"opacity-40 pointer-events-none\",\n            )}\n          >\n            <AnimatePresence mode=\"wait\" initial={false}>\n              {isApplied ? (\n                <motion.span\n                  key=\"applied\"\n                  initial={{ opacity: 0, scale: 0.7 }}\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={{ opacity: 0, scale: 0.7 }}\n                  transition={{ duration: 0.15 }}\n                  className=\"inline-flex items-center gap-1.5\"\n                >\n                  <Check size={14} strokeWidth={2.5} />\n                  <span>Applied</span>\n                </motion.span>\n              ) : (\n                <motion.span\n                  key=\"apply\"\n                  initial={{ opacity: 0, scale: 0.7 }}\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={{ opacity: 0, scale: 0.7 }}\n                  transition={{ duration: 0.15 }}\n                >\n                  Apply\n                </motion.span>\n              )}\n            </AnimatePresence>\n          </motion.button>\n        </div>\n      </div>\n\n      <p className=\"sr-only\" aria-live=\"polite\">\n        {status}\n      </p>\n    </div>\n  );\n}\n\nexport default DateRangePicker;\n",
      "type": "registry:ui",
      "target": "@ui/date-range-picker.tsx"
    }
  ]
}
