{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-range-picker",
  "type": "registry:ui",
  "title": "Date range picker",
  "description": "A range picker that grows from its trigger into two months with presets and a stretching range highlight.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/motion-tokens.json",
    "https://irisui.dev/r/use-today.json",
    "https://irisui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/registry/components/date-range-picker/date-range-picker.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport type {\n  KeyboardEvent as ReactKeyboardEvent,\n  CSSProperties,\n  FocusEvent as ReactFocusEvent,\n  ReactNode,\n} from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useIsPresent,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n} from \"motion/react\";\nimport type { Transition, Variants } from \"motion/react\";\nimport { CalendarDays, ChevronDown, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nimport { useToday } from \"../lib/use-today\";\nimport { cn } from \"../lib/utils\";\n\n/** An inclusive range of whole days. */\nexport interface DateRange {\n  start: Date;\n  end: Date;\n}\n\n/** A shortcut in the preset rail. `range` receives the viewer's local today. */\nexport interface DateRangePreset {\n  label: string;\n  range: (today: Date) => DateRange;\n}\n\n/**\n * A range picker whose trigger grows into the panel it opens. The panel shows two months beside a preset rail, or one\n * month with a scrolling preset row when space is tight. The range highlight stretches across each week as you hover\n * or move with the keyboard, the ends glide between days, months slide in the direction you travel, and on Apply the\n * formatted label flies back into the trigger as the surface shrinks around it.\n * Use it for reports, filters, and bookings where a start and end date are chosen together.\n */\nexport interface DateRangePickerProps {\n  /** Controlled value. Pass `null` for no selection. */\n  value?: DateRange | null;\n  /** Uncontrolled starting value. */\n  defaultValue?: DateRange | null;\n  /** Called with the applied range. */\n  onChange?: (range: DateRange) => void;\n  /** Accessible name of the trigger and the dialog. Defaults to \"Date range\". */\n  label?: string;\n  placeholder?: string;\n  presets?: DateRangePreset[];\n  minDate?: Date;\n  maxDate?: Date;\n  /** 0 is Sunday, 1 is Monday. Defaults to 0. */\n  weekStartsOn?: 0 | 1;\n  locale?: string;\n  /** Force one or two months. `auto` picks two when the boundary is wide enough. */\n  months?: \"auto\" | 1 | 2;\n  /** The element the panel should stay inside. Defaults to the viewport. */\n  boundary?: () => HTMLElement | null;\n  className?: string;\n}\n\ntype Size = { w: number; h: number };\ntype Bezier = [number, number, number, number];\n\nconst DAY = 864e5;\nconst startOfDay = (date: Date) => new Date(date.getFullYear(), date.getMonth(), date.getDate());\nconst monthStart = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1);\nconst monthEnd = (date: Date) => new Date(date.getFullYear(), date.getMonth() + 1, 0);\nconst addDays = (date: Date, amount: number) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);\nconst addMonths = (date: Date, amount: number) => 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(date.getDate(), new Date(date.getFullYear(), date.getMonth() + amount + 1, 0).getDate()),\n  );\nconst dayDiff = (a: Date, b: Date) => Math.round((startOfDay(a).getTime() - startOfDay(b).getTime()) / DAY);\nconst monthDiff = (a: Date, b: Date) => (a.getFullYear() - b.getFullYear()) * 12 + a.getMonth() - b.getMonth();\nconst sameDay = (a?: Date | null, b?: Date | null) => Boolean(a && b && dayDiff(a, b) === 0);\nconst keyOf = (date: Date) =>\n  `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}-${String(date.getDate()).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 => (dayDiff(a, b) <= 0 ? { start: a, end: b } : { start: b, end: a });\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 ? startOfDay(min) : max && dayDiff(date, max) > 0 ? startOfDay(max) : date;\n\nexport const defaultDateRangePresets: DateRangePreset[] = [\n  { label: \"Today\", range: (today) => ({ start: today, end: today }) },\n  { label: \"Yesterday\", range: (today) => ({ start: addDays(today, -1), end: addDays(today, -1) }) },\n  { label: \"Last 7 days\", range: (today) => ({ start: addDays(today, -6), end: today }) },\n  { label: \"Last 30 days\", range: (today) => ({ start: addDays(today, -29), end: today }) },\n  { label: \"This month\", range: (today) => ({ start: monthStart(today), end: today }) },\n  { label: \"Last month\", range: (today) => ({ start: addMonths(today, -1), end: monthEnd(addMonths(today, -1)) }) },\n  {\n    label: \"This quarter\",\n    range: (today) => ({ start: new Date(today.getFullYear(), Math.floor(today.getMonth() / 3) * 3, 1), end: today }),\n  },\n  { label: \"Year to date\", range: (today) => ({ start: new Date(today.getFullYear(), 0, 1), end: today }) },\n];\n\n/** The viewer's local date (lib/use-today.ts, shared with calendar). Re-exported so existing imports from date-range-picker keep working. */\nexport { useToday } from \"../lib/use-today\";\n\nconst noop = () => () => {};\nfunction useReducedFlag() {\n  const hydrated = useSyncExternalStore(\n    noop,\n    () => true,\n    () => false,\n  );\n  return !!useReducedMotion() && hydrated;\n}\n\nconst { blur } = motionTokens;\nconst enterEase = [...motionTokens.ease.enter] as Bezier;\nconst standardEase = [...motionTokens.ease.standard] as Bezier;\n/** Duration springs restated as stiffness and damping, so every retarget keeps the velocity already in flight. */\nconst physical = (visualDuration: number, bounce: number): Transition => {\n  const root = (2 * Math.PI) / (visualDuration * 1.2);\n  return { type: \"spring\", stiffness: root * root, damping: 2 * (1 - bounce) * root, mass: 1 };\n};\nconst GROW = physical(0.48, 0),\n  SHRINK = physical(0.38, 0),\n  STRETCH = physical(0.3, 0),\n  GLIDE = physical(0.3, 0),\n  SLIDE = physical(0.4, 0);\nconst TRIGGER_RADIUS = 18,\n  PANEL_RADIUS = 26,\n  WIDE_CELL = 36,\n  WIDE_MIN = 712,\n  EDGE = 8;\n\nconst roll: Variants = {\n  enter: (direction: number) => ({ opacity: 0, y: `${direction * 0.45}em`, filter: `blur(${blur.subtle}px)` }),\n  center: { opacity: 1, y: \"0em\", filter: \"blur(0px)\", transition: { duration: 0.26, ease: enterEase } },\n  exit: (direction: number) => ({\n    opacity: 0,\n    y: `${direction * -0.45}em`,\n    filter: `blur(${blur.subtle}px)`,\n    transition: { duration: 0.14, ease: standardEase },\n  }),\n};\nconst fade: Variants = {\n  enter: { opacity: 0 },\n  center: { opacity: 1, y: \"0em\", filter: \"blur(0px)\", transition: { duration: 0.12 } },\n  exit: { opacity: 0, transition: { duration: 0.08 } },\n};\n/** Panel content arrives just behind the growing surface and leaves ahead of it. */\nconst faceIn: Variants = {\n  hidden: { opacity: 0, y: -4, filter: `blur(${blur.soft}px)` },\n  shown: { opacity: 1, y: 0, filter: \"blur(0px)\", transition: { duration: 0.26, ease: enterEase, delay: 0.07 } },\n  gone: { opacity: 0, y: -2, filter: `blur(${blur.soft}px)`, transition: { duration: 0.12, ease: standardEase } },\n};\nconst faceFade: Variants = {\n  hidden: { opacity: 0 },\n  shown: { opacity: 1, transition: { duration: 0.12 } },\n  gone: { opacity: 0, transition: { duration: 0.08 } },\n};\nconst slide: Variants = {\n  enter: (direction: number) => ({ opacity: 0, x: direction * 48 }),\n  center: { opacity: 1, x: 0, transition: { x: SLIDE, opacity: { duration: 0.22, ease: enterEase } } },\n  exit: (direction: number) => ({\n    opacity: 0,\n    x: direction * -36,\n    transition: { x: SLIDE, opacity: { duration: 0.14, ease: standardEase } },\n  }),\n};\n\n/* The trigger sits in flow above the surface while closed; when open, the surface and its panel rise over it. */\nconst triggerClass =\n  \"peer/trigger relative z-1 inline-flex h-control-md w-max items-center gap-2.5 border-0 bg-transparent py-0 pr-3 pl-3.5 font-medium whitespace-nowrap text-ink [-webkit-tap-highlight-color:transparent] disabled:cursor-default inert:pointer-events-none\";\nconst valueClass = \"inline-flex tabular-nums data-empty:font-normal data-empty:text-ink-2\";\n/* One material from trigger to panel. Size, corners, and offset come from springs; the border is an overlay (::after) so it never changes a measurement. */\nconst surfaceClass =\n  \"absolute top-0 left-0 overflow-clip bg-surface shadow-resting [transition:box-shadow_var(--duration-considered)_var(--ease-standard),background-color_var(--duration-fast)_var(--ease-standard)] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-line after:transition-[border-color] after:duration-fast after:ease-standard after:content-[''] contrast-more:after:border-line-strong motion-reduce:duration-0\";\nconst surfaceHover =\n  \"peer-enabled/trigger:peer-hover/trigger:bg-[color-mix(in_oklab,var(--ink)_3%,var(--surface))] peer-enabled/trigger:peer-hover/trigger:after:border-line-strong\";\nconst presetClass =\n  \"relative flex h-8 flex-none items-center rounded-button border-0 bg-transparent px-3 text-left whitespace-nowrap text-ink-2 [-webkit-tap-highlight-color:transparent] [transition:color_var(--duration-fast)_var(--ease-standard),background-color_var(--duration-fast)_var(--ease-standard)] hover:text-ink focus-visible:bg-[color-mix(in_oklab,var(--ink)_5%,transparent)] focus-visible:text-ink not-aria-pressed:hover:bg-[color-mix(in_oklab,var(--ink)_4%,transparent)] aria-pressed:font-medium aria-pressed:text-ink group-data-compact/panel:px-[11px] motion-reduce:duration-0\";\nconst navButtonClass =\n  \"absolute top-0 z-2 grid size-8 place-items-center rounded-button border-0 bg-transparent p-0 text-ink-2 [-webkit-tap-highlight-color:transparent] [transition:background-color_var(--duration-fast)_var(--ease-standard),color_var(--duration-fast)_var(--ease-standard),transform_var(--duration-instant)_var(--ease-standard)] focus-visible:bg-[color-mix(in_oklab,var(--ink)_5%,transparent)] focus-visible:text-ink enabled:hover:bg-[color-mix(in_oklab,var(--ink)_6.5%,transparent)] enabled:hover:text-ink enabled:active:[transform:scale(.92)] disabled:cursor-default disabled:opacity-35 motion-reduce:duration-0\";\nconst dayClass =\n  \"group/day relative z-1 grid size-full place-items-center rounded-[50%] border-0 bg-transparent p-0 text-ink tabular-nums [-webkit-tap-highlight-color:transparent] transition-[color] duration-fast ease-standard data-edge:font-medium data-edge:text-on-accent data-edge:delay-[40ms] disabled:cursor-default disabled:text-ink-3 disabled:opacity-50 data-today:after:absolute data-today:after:bottom-[5px] data-today:after:left-1/2 data-today:after:-ml-0.5 data-today:after:size-1 data-today:after:rounded-[50%] data-today:after:bg-accent data-today:after:content-[''] data-today:data-edge:after:bg-on-accent motion-reduce:duration-0\";\nconst dayFaceClass =\n  \"relative grid size-[calc(100%-6px)] place-items-center rounded-[50%] [transition:background-color_var(--duration-fast)_var(--ease-standard),transform_var(--duration-instant)_var(--ease-standard)] group-[:active:enabled]/day:[transform:scale(.9)] group-[:focus-visible:not([data-edge])]/day:bg-[color-mix(in_oklab,var(--ink)_7%,transparent)] group-[:hover:enabled:not([data-edge])]/day:bg-[color-mix(in_oklab,var(--ink)_7%,transparent)] motion-reduce:duration-0\";\nconst actionClass =\n  \"inline-flex h-control-sm items-center justify-center rounded-button border-0 px-3.5 font-medium whitespace-nowrap [-webkit-tap-highlight-color:transparent] [transition:background-color_var(--duration-fast)_var(--ease-standard),color_var(--duration-fast)_var(--ease-standard),transform_var(--duration-instant)_var(--ease-standard),opacity_var(--duration-fast)_var(--ease-standard)] motion-reduce:duration-0\";\nconst weekGrid = \"grid grid-cols-[repeat(7,var(--cell))]\";\n\n/** Words roll one by one: a later date rises from below, an earlier one drops from above; words that stay the same hold still. */\nfunction Rolling({ text, direction, reduced }: { text: string; direction: number; reduced: boolean }) {\n  const words = text.split(/\\s+/).filter(Boolean);\n  return (\n    <span className=\"inline-flex gap-[.28em] whitespace-nowrap\" aria-hidden=\"true\">\n      {words.map((word, index) => (\n        <span key={index} className=\"relative inline-block\">\n          <AnimatePresence initial={false} mode=\"popLayout\" custom={direction}>\n            <motion.span\n              key={word}\n              className=\"inline-block\"\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\n/** Remembers which way a range moved, so labels roll in time's direction. */\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(time === null || previous === null || time >= previous ? 1 : -1);\n  }\n  return direction;\n}\n\ntype Segment = { left: number; width: number } | null;\nconst cellIn = (date: Date, first: Date) => {\n  const index = dayDiff(date, first);\n  return { row: Math.floor(index / 7), col: index % 7 };\n};\n\n/** One week's slice of the highlight. It grows out of the edge the range arrives from, so hovering reads as stretching. */\nfunction Bar({ row, segment, reduced }: { row: number; segment: Segment; reduced: boolean }) {\n  const left = useMotionValue(segment?.left ?? 0),\n    width = useMotionValue(segment?.width ?? 0),\n    opacity = useMotionValue(segment ? 1 : 0);\n  const leftPct = useTransform(left, (value) => `${value}%`),\n    widthPct = useTransform(width, (value) => `${value}%`);\n  const wasEmpty = useRef(!segment);\n  useLayoutEffect(() => {\n    if (!segment) {\n      wasEmpty.current = true;\n      animate(opacity, 0, { duration: reduced ? 0 : 0.14 });\n      return;\n    }\n    if (wasEmpty.current || reduced) {\n      left.jump(segment.left);\n      width.jump(segment.width);\n    } else {\n      animate(left, segment.left, STRETCH);\n      animate(width, segment.width, STRETCH);\n    }\n    wasEmpty.current = false;\n    animate(opacity, 1, { duration: reduced ? 0 : 0.16 });\n  }, [left, opacity, reduced, segment, width]);\n  return (\n    <motion.span\n      className=\"absolute h-[calc(var(--cell)-6px)] rounded-full bg-accent-tint\"\n      style={{ left: leftPct, width: widthPct, opacity, top: `calc(${row} * var(--cell) + 3px)` }}\n    />\n  );\n}\n\n/** A solid end of the range. It glides from day to day and only fades when the end leaves this month. */\nfunction Thumb({ cell, reduced }: { cell: { row: number; col: number } | null; reduced: boolean }) {\n  const x = useMotionValue(cell?.col ?? 0),\n    y = useMotionValue(cell?.row ?? 0),\n    opacity = useMotionValue(cell ? 1 : 0),\n    scale = useMotionValue(cell ? 1 : 0.8);\n  const transform = useTransform(() => `translate(${x.get() * 100}%, ${y.get() * 100}%) scale(${scale.get()})`);\n  const hidden = useRef(!cell);\n  useLayoutEffect(() => {\n    if (!cell) {\n      hidden.current = true;\n      animate(opacity, 0, { duration: reduced ? 0 : 0.12 });\n      animate(scale, 0.8, { duration: reduced ? 0 : 0.12 });\n      return;\n    }\n    if (hidden.current || reduced) {\n      x.jump(cell.col);\n      y.jump(cell.row);\n    } else {\n      animate(x, cell.col, GLIDE);\n      animate(y, cell.row, GLIDE);\n    }\n    hidden.current = false;\n    animate(opacity, 1, { duration: reduced ? 0 : 0.16 });\n    animate(scale, 1, reduced ? { duration: 0 } : GLIDE);\n  }, [cell, opacity, reduced, scale, x, y]);\n  return (\n    <motion.span\n      className=\"absolute top-0 left-0 size-(--cell) before:absolute before:inset-[3px] before:rounded-[50%] before:bg-accent before:content-['']\"\n      style={{ transform, opacity }}\n    />\n  );\n}\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  reduced: boolean;\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  idBase: string;\n}\n\nfunction Month({\n  month,\n  range,\n  tabbable,\n  today,\n  minDate,\n  maxDate,\n  weekStartsOn,\n  reduced,\n  formatters,\n  onPick,\n  onHover,\n  onKey,\n  onFocusDay,\n  idBase,\n}: MonthProps) {\n  const first = useMemo(() => addDays(month, -((month.getDay() - weekStartsOn + 7) % 7)), [month, weekStartsOn]);\n  const rows = useMemo(\n    () => Array.from({ length: 6 }, (_, row) => Array.from({ length: 7 }, (_, col) => addDays(first, row * 7 + col))),\n    [first],\n  );\n  const titleId = `${idBase}-${keyOf(month)}`;\n  const inMonth = (date: Date) => monthDiff(date, month) === 0;\n\n  const lo = range?.start,\n    hi = range?.end;\n  const loKey = lo ? keyOf(lo) : \"\",\n    hiKey = hi ? keyOf(hi) : \"\";\n  /** Each week's slice of the range, in percent of the row. Keyed by the range's days so bars only move when it does. */\n  const segments = useMemo<Segment[]>(() => {\n    if (!loKey || !hiKey) return rows.map(() => null);\n    const start = fromKey(loKey),\n      end = fromKey(hiKey);\n    const unit = 100 / 7;\n    const colOf = (date: Date) => dayDiff(date, first) % 7;\n    return rows.map((row) => {\n      const days = row.filter((date) => monthDiff(date, month) === 0);\n      if (!days.length) return null;\n      const rowFirst = days[0]!,\n        rowLast = days[days.length - 1]!;\n      const from = dayDiff(start, rowFirst) > 0 ? start : rowFirst,\n        to = dayDiff(end, rowLast) < 0 ? end : rowLast;\n      if (dayDiff(from, to) <= 0) {\n        const a = colOf(from),\n          b = colOf(to);\n        return { left: a * unit, width: (b - a + 1) * unit };\n      }\n      // The week sits wholly before or after the range: collapse at the edge the range would grow in from.\n      if (dayDiff(rowLast, start) < 0) return { left: (colOf(rowLast) + 1) * unit, width: 0 };\n      return { left: colOf(rowFirst) * unit, width: 0 };\n    });\n  }, [first, hiKey, loKey, month, rows]);\n  const startCell = useMemo(\n    () => (loKey && monthDiff(fromKey(loKey), month) === 0 ? cellIn(fromKey(loKey), first) : null),\n    [loKey, month, first],\n  );\n  const endCell = useMemo(\n    () => (hiKey && hiKey !== loKey && monthDiff(fromKey(hiKey), month) === 0 ? cellIn(fromKey(hiKey), first) : null),\n    [hiKey, loKey, month, first],\n  );\n\n  return (\n    <div className=\"w-[calc(var(--cell)*7)]\">\n      <p id={titleId} className=\"m-0 h-8 text-center leading-8 font-medium whitespace-nowrap text-ink\">\n        {formatters.title.format(month)}\n      </p>\n      <div role=\"grid\" aria-labelledby={titleId} className=\"grid\">\n        <div role=\"row\" className={cn(weekGrid, \"h-7 items-center text-center text-(length:--text-xs) text-ink-3\")}>\n          {rows[0]!.map((date) => (\n            <span key={date.getDay()} role=\"columnheader\" aria-label={formatters.weekdayLong.format(date)}>\n              {formatters.weekday.format(date).slice(0, 2)}\n            </span>\n          ))}\n        </div>\n        <div className=\"relative\">\n          {/* The highlight lives under the days: one bar per week plus a solid end at each side of the range. */}\n          <span className=\"pointer-events-none absolute inset-0\" aria-hidden=\"true\">\n            {segments.map((segment, row) => (\n              <Bar key={row} row={row} segment={segment} reduced={reduced} />\n            ))}\n            <Thumb cell={startCell} reduced={reduced} />\n            <Thumb cell={endCell} reduced={reduced} />\n          </span>\n          {rows.map((row, index) => (\n            <div key={index} role=\"row\" className={cn(weekGrid, \"h-(--cell)\")}>\n              {row.map((date) => {\n                // oxlint-disable-next-line jsx-a11y/control-has-associated-label -- an empty cell that keeps the grid's columns; it holds no day to name\n                if (!inMonth(date)) return <span key={keyOf(date)} role=\"gridcell\" className=\"relative block\" />;\n                const key = keyOf(date);\n                const disabled = Boolean(\n                  (minDate && dayDiff(date, minDate) < 0) || (maxDate && dayDiff(date, maxDate) > 0),\n                );\n                const inRange = Boolean(lo && hi && dayDiff(date, lo) >= 0 && dayDiff(date, hi) <= 0);\n                const edge = sameDay(date, lo) || sameDay(date, hi);\n                const isToday = sameDay(date, today);\n                return (\n                  <span key={key} role=\"gridcell\" aria-selected={inRange} className=\"relative block\">\n                    <button\n                      type=\"button\"\n                      className={dayClass}\n                      data-date={key}\n                      data-edge={edge || 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                    >\n                      <span className={dayFaceClass}>{date.getDate()}</span>\n                    </button>\n                  </span>\n                );\n              })}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n\n/** The visible months travel together; a leaving set turns inert so focus and queries only find the current one. */\nfunction Months({ children, direction, reduced }: { children: ReactNode; direction: number; reduced: boolean }) {\n  const present = useIsPresent();\n  return (\n    <motion.div\n      className=\"flex gap-6\"\n      data-current={present || undefined}\n      inert={!present || undefined}\n      custom={direction}\n      variants={reduced ? fade : slide}\n      initial=\"enter\"\n      animate=\"center\"\n      exit=\"exit\"\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport function DateRangePicker({\n  value,\n  defaultValue = null,\n  onChange,\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  boundary,\n  className,\n}: DateRangePickerProps) {\n  const reduced = useReducedFlag();\n  const today = useToday();\n  const uid = useId();\n  const [inner, setInner] = useState<DateRange | null>(defaultValue);\n  const committed = value !== undefined ? value : inner;\n\n  const [open, setOpen] = useState(false);\n  const [compact, setCompact] = useState(false);\n  const [panelWidth, setPanelWidth] = useState(0);\n  const [view, setView] = useState<Date>(() => monthStart(committed?.end ?? new Date(2000, 0, 1)));\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\n  const formatters = useMemo(\n    () => ({\n      label: new Intl.DateTimeFormat(locale, { month: \"short\", day: \"numeric\", year: \"numeric\" }),\n      title: new Intl.DateTimeFormat(locale, { month: \"long\", year: \"numeric\" }),\n      day: new Intl.DateTimeFormat(locale, { weekday: \"long\", month: \"long\", day: \"numeric\", year: \"numeric\" }),\n      weekday: new Intl.DateTimeFormat(locale, { weekday: \"short\" }),\n      weekdayLong: new Intl.DateTimeFormat(locale, { weekday: \"long\" }),\n    }),\n    [locale],\n  );\n  const format = useCallback(\n    (range: DateRange | null) =>\n      !range\n        ? placeholder\n        : sameDay(range.start, range.end)\n          ? formatters.label.format(range.start)\n          : formatters.label.formatRange(range.start, range.end),\n    [formatters, placeholder],\n  );\n\n  const count = compact ? 1 : 2;\n  const visible = useMemo(() => Array.from({ length: count }, (_, index) => addMonths(view, index)), [count, view]);\n  const shown = anchor ? ordered(anchor, hover ?? anchor) : draft;\n  const activePreset = today && !anchor ? presets.findIndex((preset) => sameRange(preset.range(today), draft)) : -1;\n  const days = shown ? dayDiff(shown.end, shown.start) + 1 : 0;\n  const committedDirection = useDirection(committed);\n  const shownDirection = useDirection(shown);\n\n  const rootRef = useRef<HTMLDivElement>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const panelRef = useRef<HTMLDivElement>(null);\n  const railRef = useRef<HTMLDivElement>(null);\n  const pendingFocus = useRef<\"trigger\" | \"day\" | null>(null);\n\n  /* ---------- One surface: the trigger's box springs to the panel's box and back ---------- */\n  // Before the first measurement the surface simply fills the root and the root wraps the trigger, so server markup already looks right.\n  const width = useMotionValue<number | string>(\"100%\"),\n    height = useMotionValue<number | string>(\"100%\"),\n    radius = useMotionValue(TRIGGER_RADIUS),\n    x = useMotionValue(0),\n    rootWidth = useMotionValue<number | string>(\"auto\");\n  const sizes = useRef<{ trigger: Size; panel: Size | null }>({ trigger: { w: 0, h: 0 }, panel: null });\n  const live = useRef({ open: false, reduced: false, ready: false });\n  const queued = useRef(false);\n  const boundaryRef = useRef(boundary);\n  useLayoutEffect(() => {\n    boundaryRef.current = boundary;\n  }, [boundary]);\n\n  const bounds = useCallback(() => {\n    const element = boundaryRef.current?.();\n    const viewport = { left: 0, right: document.documentElement.clientWidth };\n    if (!element) return viewport;\n    const rect = element.getBoundingClientRect();\n    return { left: Math.max(viewport.left, rect.left), right: Math.min(viewport.right, rect.right) };\n  }, []);\n\n  const update = useCallback(() => {\n    queued.current = false;\n    const { trigger, panel } = sizes.current,\n      state = live.current;\n    if (!trigger.w) return;\n    const openPanel = state.open && panel ? panel : null;\n    const target = openPanel ?? trigger;\n    let offset = 0;\n    if (openPanel && rootRef.current) {\n      const rect = rootRef.current.getBoundingClientRect(),\n        box = bounds();\n      offset = Math.min(0, box.right - EDGE - (rect.left + openPanel.w));\n      offset = Math.max(offset, box.left + EDGE - rect.left);\n    }\n    const r = openPanel ? PANEL_RADIUS : TRIGGER_RADIUS;\n    if (!state.ready || state.reduced) {\n      width.jump(target.w);\n      height.jump(target.h);\n      radius.jump(r);\n      x.jump(offset);\n      rootWidth.jump(trigger.w);\n      state.ready = true;\n      return;\n    }\n    const spring = openPanel ? GROW : state.open ? GROW : SHRINK;\n    animate(width, target.w, spring);\n    animate(height, target.h, spring);\n    animate(radius, r, spring);\n    animate(x, offset, spring);\n    animate(rootWidth, trigger.w, motionTokens.spring.morph);\n  }, [bounds, height, radius, rootWidth, width, x]);\n\n  const schedule = useCallback(() => {\n    if (queued.current) return;\n    queued.current = true;\n    queueMicrotask(update);\n  }, [update]);\n\n  useLayoutEffect(() => {\n    live.current.open = open;\n    live.current.reduced = reduced;\n    if (!open) sizes.current.panel = null;\n    schedule();\n  }, [open, reduced, schedule]);\n\n  useLayoutEffect(() => {\n    const node = triggerRef.current;\n    if (!node) return;\n    const read = () => {\n      sizes.current.trigger = { w: node.offsetWidth, h: node.offsetHeight };\n      schedule();\n    };\n    read();\n    if (typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(read);\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, [schedule]);\n\n  useLayoutEffect(() => {\n    const node = panelRef.current;\n    if (!open || !node) return;\n    const read = () => {\n      sizes.current.panel = { w: node.offsetWidth, h: node.offsetHeight };\n      schedule();\n    };\n    read();\n    if (typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(read);\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, [open, schedule]);\n\n  // On a short screen (a phone, a trigger low on the page) the panel can open below the fold, with Apply out of reach.\n  // Once it has its size, scroll just enough to bring its bottom into view, never past its top. After closing, the\n  // trigger comes back into view if that scroll left it off screen.\n  useEffect(() => {\n    if (!open) return;\n    const frame = requestAnimationFrame(() => {\n      const node = panelRef.current;\n      if (!node) return;\n      const rect = node.getBoundingClientRect();\n      const overflow = rect.bottom - (window.innerHeight - EDGE);\n      if (overflow <= 0) return;\n      window.scrollBy({\n        top: Math.min(overflow, Math.max(0, rect.top - EDGE)),\n        behavior: reduced ? \"instant\" : \"smooth\",\n      });\n    });\n    return () => cancelAnimationFrame(frame);\n  }, [open, reduced]);\n  useEffect(() => {\n    if (open) return;\n    const trigger = triggerRef.current;\n    if (!trigger) return;\n    const rect = trigger.getBoundingClientRect();\n    if (rect.bottom < 0 || rect.top > window.innerHeight)\n      trigger.scrollIntoView({ block: \"center\", behavior: reduced ? \"instant\" : \"smooth\" });\n  }, [open, reduced]);\n\n  /* ---------- Layout: two months when there is room, one compact month otherwise ---------- */\n  const measureLayout = useCallback(() => {\n    const box = bounds();\n    const available = Math.max(0, box.right - box.left - EDGE * 2);\n    const single = months === 1 || (months === \"auto\" && available < WIDE_MIN);\n    return { single, width: Math.min(352, available) };\n  }, [bounds, months]);\n\n  useEffect(() => {\n    if (!open) return;\n    const onResize = () => {\n      const next = measureLayout();\n      setCompact(next.single);\n      setPanelWidth(next.width);\n      schedule();\n    };\n    window.addEventListener(\"resize\", onResize);\n    return () => window.removeEventListener(\"resize\", onResize);\n  }, [measureLayout, open, schedule]);\n\n  const viewFor = (range: DateRange | null, single: boolean, fallback: Date) => {\n    const end = monthStart(range?.end ?? fallback);\n    return single ? end : addMonths(end, -1);\n  };\n\n  const openPanel = () => {\n    if (!today) return;\n    const layout = measureLayout();\n    setCompact(layout.single);\n    setPanelWidth(layout.width);\n    setDraft(committed);\n    setAnchor(null);\n    setHover(null);\n    setView(viewFor(committed, layout.single, today));\n    setDirection(0);\n    setFocusKey(keyOf(committed?.start ?? today));\n    pendingFocus.current = \"day\";\n    setOpen(true);\n  };\n\n  const close = useCallback((focus: boolean) => {\n    if (focus) pendingFocus.current = \"trigger\";\n    setDirection(0);\n    setOpen(false);\n    setAnchor(null);\n    setHover(null);\n  }, []);\n\n  const apply = () => {\n    const next = anchor ? { start: anchor, end: anchor } : draft;\n    if (!next) return;\n    if (value === undefined) setInner(next);\n    onChange?.(next);\n    close(true);\n  };\n\n  /* ---------- Focus management ---------- */\n  useLayoutEffect(() => {\n    const target = pendingFocus.current;\n    if (!target) return;\n    if (target === \"trigger\" && !open) {\n      pendingFocus.current = null;\n      triggerRef.current?.focus({ preventScroll: true });\n      return;\n    }\n    if (target === \"day\" && open) {\n      const node = panelRef.current?.querySelector<HTMLButtonElement>(`[data-current] [data-date=\"${focusKey}\"]`);\n      if (node) {\n        pendingFocus.current = null;\n        node.focus({ preventScroll: true });\n      }\n    }\n  }, [focusKey, open, view, compact]);\n\n  useEffect(() => {\n    if (!open) return;\n    const down = (event: PointerEvent) => {\n      if (!rootRef.current?.contains(event.target as Node)) close(false);\n    };\n    document.addEventListener(\"pointerdown\", down);\n    return () => document.removeEventListener(\"pointerdown\", down);\n  }, [close, open]);\n\n  const onRootKey = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n    if (event.key === \"Escape\" && open) {\n      event.preventDefault();\n      event.stopPropagation();\n      close(true);\n    }\n  };\n  const onRootBlur = (event: ReactFocusEvent<HTMLDivElement>) => {\n    const next = event.relatedTarget as Node | null;\n    if (open && next && !event.currentTarget.contains(next)) close(false);\n  };\n\n  /* ---------- Month navigation and picking ---------- */\n  const goTo = (next: Date) => {\n    const delta = monthDiff(next, view);\n    if (!delta) return;\n    setDirection(Math.sign(delta));\n    setView(next);\n  };\n\n  /** Keeps a range on screen: nothing moves when it already fits, otherwise its last month lands on the right. */\n  const reveal = (range: DateRange) => {\n    const first = view,\n      last = addMonths(view, count - 1);\n    if (monthDiff(range.start, first) >= 0 && monthDiff(range.end, last) <= 0) return;\n    goTo(viewFor(range, compact, range.end));\n  };\n\n  const pick = (date: Date) => {\n    setFocusKey(keyOf(date));\n    if (!anchor) {\n      setAnchor(date);\n      setHover(date);\n      return;\n    }\n    setDraft(ordered(anchor, date));\n    setAnchor(null);\n    setHover(null);\n  };\n\n  const choosePreset = (preset: DateRangePreset) => {\n    if (!today) return;\n    const range = preset.range(today);\n    setDraft(range);\n    setAnchor(null);\n    setHover(null);\n    setFocusKey(keyOf(range.start));\n    reveal(range);\n  };\n\n  const onDayKey = (event: ReactKeyboardEvent<HTMLButtonElement>, date: Date) => {\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    if (anchor) setHover(next);\n    pendingFocus.current = \"day\";\n    const first = view,\n      last = addMonths(view, count - 1);\n    if (monthDiff(next, first) < 0) goTo(monthStart(next));\n    else if (monthDiff(next, last) > 0) goTo(addMonths(monthStart(next), -(count - 1)));\n  };\n\n  const onRailKey = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n    const keys = compact ? [\"ArrowLeft\", \"ArrowRight\"] : [\"ArrowUp\", \"ArrowDown\"];\n    if (!keys.includes(event.key)) return;\n    const buttons = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>(\"[data-preset]\"));\n    const at = buttons.indexOf(document.activeElement as HTMLButtonElement);\n    if (at < 0) return;\n    event.preventDefault();\n    buttons[(at + (event.key === keys[1] ? 1 : -1) + buttons.length) % buttons.length]?.focus();\n  };\n\n  /** The day that owns the tab stop: the focused day when it is on screen, else the first visible day of the range, else the 1st. */\n  const tabbable = useMemo(() => {\n    const onScreen = (date: Date) => 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  /* ---------- Preset highlight glides between presets ---------- */\n  const gx = useMotionValue(0),\n    gy = useMotionValue(0),\n    gw = useMotionValue(0),\n    gh = useMotionValue(0),\n    go = useMotionValue(0);\n  useLayoutEffect(() => {\n    const rail = railRef.current;\n    const node = activePreset < 0 ? null : rail?.querySelector<HTMLElement>(`[data-preset=\"${activePreset}\"]`);\n    if (!rail || !node) {\n      animate(go, 0, { duration: reduced ? 0 : 0.14 });\n      return;\n    }\n    const place = [node.offsetLeft, node.offsetTop, node.offsetWidth, node.offsetHeight] as const;\n    if (go.get() < 0.05 || reduced) {\n      gx.jump(place[0]);\n      gy.jump(place[1]);\n      gw.jump(place[2]);\n      gh.jump(place[3]);\n    } else {\n      animate(gx, place[0], GLIDE);\n      animate(gy, place[1], GLIDE);\n      animate(gw, place[2], GLIDE);\n      animate(gh, place[3], GLIDE);\n    }\n    animate(go, 1, { duration: reduced ? 0 : 0.16 });\n    if (\n      compact &&\n      (node.offsetLeft < rail.scrollLeft || node.offsetLeft + node.offsetWidth > rail.scrollLeft + rail.clientWidth)\n    ) {\n      rail.scrollTo({ left: node.offsetLeft - 12, behavior: reduced ? \"auto\" : \"smooth\" });\n    }\n  }, [activePreset, compact, go, gh, gw, gx, gy, open, reduced]);\n\n  /* ---------- Copy ---------- */\n  const committedText = format(committed);\n  const shownText = shown ? format(shown) : \"No dates\";\n  const countText = shown ? `${days} ${days === 1 ? \"day\" : \"days\"}` : \"\";\n  const status = !open\n    ? \"\"\n    : anchor\n      ? `Start ${formatters.label.format(anchor)}. Choose an end date.`\n      : shown\n        ? `${shownText}, ${countText}`\n        : \"\";\n  const layoutId = reduced ? undefined : `${uid}-value`;\n\n  const cellSize = compact && panelWidth ? Math.max(32, Math.min(42, Math.floor((panelWidth - 24) / 7))) : WIDE_CELL;\n\n  const quiet = open\n    ? reduced\n      ? { opacity: 0 }\n      : { opacity: 0, filter: `blur(${blur.subtle}px)` }\n    : { opacity: 1, filter: \"blur(0px)\" };\n  const quietTransition = open\n    ? { duration: 0.12, ease: standardEase }\n    : { duration: 0.22, ease: enterEase, delay: reduced ? 0 : 0.1 };\n\n  // The root holds the trigger's place in the page. The surface floats above it and grows into the panel, so opening never pushes content.\n  return (\n    <motion.div\n      ref={rootRef}\n      className={cn(\n        \"relative z-0 inline-block h-control-md align-top font-body text-sm leading-body tracking-body text-ink [transition:z-index_0s_linear_.5s] data-open:z-30 data-open:delay-0\",\n        className,\n      )}\n      style={{ width: rootWidth }}\n      data-open={open || undefined}\n      onKeyDown={onRootKey}\n      onBlur={onRootBlur}\n    >\n      {/* The trigger stays in place under the panel. Its icons fade, while the value itself flies to the panel footer and back. */}\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        className={triggerClass}\n        aria-haspopup=\"dialog\"\n        aria-expanded={open}\n        aria-controls={open ? `${uid}-panel` : undefined}\n        aria-label={`${label}: ${committedText}`}\n        disabled={!today}\n        inert={open || undefined}\n        onClick={openPanel}\n      >\n        <motion.span\n          className=\"grid flex-none place-items-center text-ink-2\"\n          initial={false}\n          animate={quiet}\n          transition={quietTransition}\n        >\n          <CalendarDays size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n        </motion.span>\n        {open || !layoutId ? (\n          <motion.span\n            key=\"resting\"\n            className={valueClass}\n            data-empty={!committed || undefined}\n            initial={false}\n            animate={open ? { opacity: 0 } : { opacity: 1 }}\n            transition={quietTransition}\n          >\n            <Rolling text={committedText} direction={committedDirection} reduced={reduced} />\n          </motion.span>\n        ) : (\n          <motion.span\n            key=\"shared\"\n            layoutId={layoutId}\n            layout=\"position\"\n            className={valueClass}\n            data-empty={!committed || undefined}\n            transition={SHRINK}\n          >\n            <Rolling text={committedText} direction={committedDirection} reduced={reduced} />\n          </motion.span>\n        )}\n        <motion.span\n          className=\"grid flex-none place-items-center text-ink-3\"\n          initial={false}\n          animate={quiet}\n          transition={quietTransition}\n        >\n          <ChevronDown size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n        </motion.span>\n      </button>\n\n      <motion.div\n        className={cn(surfaceClass, open ? \"z-2 shadow-floating\" : cn(\"z-0\", surfaceHover))}\n        style={{ width, height, borderRadius: radius, x }}\n      >\n        <AnimatePresence>\n          {open && today && (\n            <motion.div\n              key=\"panel\"\n              ref={panelRef}\n              id={`${uid}-panel`}\n              className=\"group/panel absolute top-0 left-0 w-max p-2.5 [--cell:36px] data-compact:p-2\"\n              role=\"dialog\"\n              aria-label={label}\n              data-compact={compact || undefined}\n              style={\n                { \"--cell\": `${cellSize}px`, width: compact && panelWidth ? panelWidth : undefined } as CSSProperties\n              }\n              exit={{ opacity: 1, transition: { duration: 0.14 } }}\n            >\n              <motion.div\n                className=\"flex gap-2.5 group-data-compact/panel:flex-col group-data-compact/panel:gap-2\"\n                variants={reduced ? faceFade : faceIn}\n                initial=\"hidden\"\n                animate=\"shown\"\n                exit=\"gone\"\n              >\n                {/* oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the labelled preset group owns roving arrow-key focus between its buttons; a fieldset would change the flex layout */}\n                <div\n                  ref={railRef}\n                  className=\"relative flex w-37 flex-none flex-col gap-0.5 border-r border-line-soft pr-2.5 group-data-compact/panel:-mx-2 group-data-compact/panel:w-auto group-data-compact/panel:flex-row group-data-compact/panel:overflow-x-auto group-data-compact/panel:overscroll-x-contain group-data-compact/panel:border-r-0 group-data-compact/panel:border-b group-data-compact/panel:px-2 group-data-compact/panel:pt-0 group-data-compact/panel:pb-2 group-data-compact/panel:[scrollbar-width:none] group-data-compact/panel:[&::-webkit-scrollbar]:hidden\"\n                  role=\"group\"\n                  aria-label=\"Presets\"\n                  onKeyDown={onRailKey}\n                >\n                  <motion.span\n                    className=\"pointer-events-none absolute top-0 left-0 rounded-button bg-[color-mix(in_oklab,var(--ink)_6.5%,transparent)]\"\n                    style={{ x: gx, y: gy, width: gw, height: gh, opacity: go }}\n                    aria-hidden=\"true\"\n                  />\n                  {presets.map((preset, index) => (\n                    <button\n                      key={preset.label}\n                      type=\"button\"\n                      className={presetClass}\n                      data-preset={index}\n                      aria-pressed={index === activePreset}\n                      onClick={() => choosePreset(preset)}\n                    >\n                      {preset.label}\n                    </button>\n                  ))}\n                </div>\n\n                <div\n                  className=\"relative px-0.5 group-data-compact/panel:self-center\"\n                  onPointerLeave={() => {\n                    if (anchor) setHover(null);\n                  }}\n                >\n                  <button\n                    type=\"button\"\n                    className={cn(navButtonClass, \"left-0.5\")}\n                    data-side=\"prev\"\n                    aria-label=\"Previous month\"\n                    onClick={() => goTo(addMonths(view, -1))}\n                    disabled={Boolean(minDate && monthDiff(view, minDate) <= 0)}\n                  >\n                    <ChevronLeft size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n                  </button>\n                  <button\n                    type=\"button\"\n                    className={cn(navButtonClass, \"right-0.5\")}\n                    data-side=\"next\"\n                    aria-label=\"Next month\"\n                    onClick={() => goTo(addMonths(view, 1))}\n                    disabled={Boolean(maxDate && monthDiff(addMonths(view, count - 1), maxDate) >= 0)}\n                  >\n                    <ChevronRight size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n                  </button>\n                  {/* The viewport keeps a fixed six-week height and stacks the leaving and arriving months in one cell, so the panel never resizes mid-slide. */}\n                  {/* Months slide horizontally inside a clipped window that keeps a little room for the blur. */}\n                  <div className=\"relative grid h-[calc(60px+var(--cell)*6)] overflow-clip [overflow-clip-margin:2px] *:[grid-area:1/1]\">\n                    <AnimatePresence initial={false} custom={direction}>\n                      <Months key={`${keyOf(view)}-${count}`} direction={direction} reduced={reduced}>\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                            reduced={reduced}\n                            formatters={formatters}\n                            idBase={uid}\n                            onPick={pick}\n                            onHover={(date) => {\n                              if (anchor) setHover(date);\n                            }}\n                            onKey={onDayKey}\n                            onFocusDay={(date) => setFocusKey(keyOf(date))}\n                          />\n                        ))}\n                      </Months>\n                    </AnimatePresence>\n                  </div>\n                </div>\n              </motion.div>\n\n              {/* Footer: the formatted range and the two decisions. */}\n              <div className=\"mt-2.5 flex w-0 min-w-full items-center justify-between gap-3 border-t border-line-soft pt-2.5 pr-0.5 pl-3 group-data-compact/panel:mt-1.5 group-data-compact/panel:flex-wrap group-data-compact/panel:pl-[11px]\">\n                <div className=\"grid min-w-0 gap-px group-data-compact/panel:flex-[1_1_8rem]\">\n                  <motion.span\n                    layoutId={layoutId}\n                    layout={layoutId ? \"position\" : undefined}\n                    className=\"inline-flex w-max font-medium tabular-nums\"\n                    transition={GROW}\n                    variants={layoutId ? undefined : faceFade}\n                    initial={layoutId ? undefined : \"hidden\"}\n                    animate={layoutId ? undefined : \"shown\"}\n                    exit={layoutId ? undefined : \"gone\"}\n                  >\n                    <Rolling text={shownText} direction={shownDirection} reduced={reduced} />\n                  </motion.span>\n                  <motion.span\n                    className=\"inline-flex min-h-[1.4em] text-(length:--text-xs) text-ink-3 tabular-nums\"\n                    variants={reduced ? faceFade : faceIn}\n                    initial=\"hidden\"\n                    animate=\"shown\"\n                    exit=\"gone\"\n                  >\n                    {countText && (\n                      <Rolling\n                        text={anchor && sameDay(anchor, hover) ? \"Pick an end date\" : countText}\n                        direction={shownDirection}\n                        reduced={reduced}\n                      />\n                    )}\n                  </motion.span>\n                </div>\n                <motion.div\n                  className=\"flex flex-none items-center gap-1.5 group-data-compact/panel:ml-auto\"\n                  variants={reduced ? faceFade : faceIn}\n                  initial=\"hidden\"\n                  animate=\"shown\"\n                  exit=\"gone\"\n                >\n                  <button\n                    type=\"button\"\n                    className={cn(\n                      actionClass,\n                      \"bg-transparent text-ink-2 hover:bg-hover hover:text-ink focus-visible:bg-hover focus-visible:text-ink active:[transform:scale(.97)]\",\n                    )}\n                    onClick={() => close(true)}\n                  >\n                    Cancel\n                  </button>\n                  <button\n                    type=\"button\"\n                    className={cn(\n                      actionClass,\n                      \"min-w-19 bg-accent text-on-accent enabled:hover:bg-accent-ink enabled:active:[transform:scale(.97)] disabled:cursor-default disabled:opacity-45\",\n                    )}\n                    onClick={apply}\n                    disabled={!shown}\n                  >\n                    Apply\n                  </button>\n                </motion.div>\n              </div>\n              <p\n                className=\"absolute -m-px size-px overflow-hidden border-0 p-0 whitespace-nowrap [clip-path:inset(50%)]\"\n                aria-live=\"polite\"\n              >\n                {status}\n              </p>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n    </motion.div>\n  );\n}\n\nexport default DateRangePicker;\n",
      "type": "registry:component",
      "target": "@components/iris/date-range-picker/date-range-picker.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/date-range-picker",
  "meta": {
    "kind": "component",
    "group": "Inputs",
    "export": "DateRangePicker",
    "tags": [
      "inputs",
      "new"
    ],
    "docs": "https://irisui.dev/components/date-range-picker"
  }
}
