{"name":"date-range-picker","type":"registry:component","files":[{"path":"date-range-picker.tsx","type":"registry:component","target":"components/ui/date-range-picker/date-range-picker.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport {\n  eachDayOfInterval,\n  isBefore,\n  isAfter,\n  startOfDay,\n} from \"date-fns\"\nimport type { Locale } from \"date-fns\"\nimport {\n  Calendar as CalendarIcon,\n  X as CloseIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\"\nimport { Separator } from \"@/components/ui/separator\"\n\nimport {\n  formatDateLabel,\n  formatTriggerText,\n  isRangeComplete,\n} from \"./date-range-utils\"\nimport type { DateRange } from \"./date-range-utils\"\nimport {\n  DateRangeCalendar,\n  type DateRangeCalendarLabels,\n} from \"./date-range-calendar\"\nimport {\n  DateRangePresets,\n  getDateRangePresets,\n} from \"./date-range-presets\"\nimport type { DateRangePreset } from \"./date-range-presets\"\n\nexport type { DateRange, DateRangePreset }\nexport { getDateRangePresets }\n\nexport interface DateRangePickerLabels extends DateRangeCalendarLabels {\n  /** Accessible name for the trigger clear action. */\n  clearAction: string\n  /** Shown in the trigger while only the start date is picked. */\n  selectEndDate: string\n  /** Accessible name for the preset group. */\n  presetsGroup?: string | undefined\n  /** Announced when the start date has been picked. */\n  startSelected?: ((formattedStartDate: string) => string) | undefined\n  /** Announced when a complete range is picked or applied. */\n  rangeSelected?: ((formattedFrom: string, formattedTo: string) => string) | undefined\n  /** Announced when the value clears. */\n  cleared?: string | undefined\n}\n\nconst DEFAULT_LABELS: DateRangePickerLabels = {\n  previousMonth: \"Previous month\",\n  nextMonth: \"Next month\",\n  todayLabel: (formatted) => `Today, ${formatted}`,\n  clearAction: \"Clear date range\",\n  selectEndDate: \"Select end date\",\n  presetsGroup: \"Quick ranges\",\n  startSelected: (formatted) =>\n    `Start date selected, ${formatted}. Choose an end date.`,\n  rangeSelected: (from, to) => `Date range selected, ${from} through ${to}.`,\n  cleared: \"Date range cleared.\",\n}\n\nexport interface DateRangePickerProps\n  extends Omit<\n    React.ComponentProps<typeof Button>,\n    | \"onChange\"\n    | \"value\"\n    | \"defaultValue\"\n    | \"children\"\n    | \"disabled\"\n    | \"required\"\n    | \"type\"\n    | \"variant\"\n    | \"size\"\n    | \"appearance\"\n  > {\n  /** Controlled range. Omit to let the picker manage its own value. */\n  value?: DateRange | undefined\n  /** Initial range for uncontrolled use. */\n  defaultValue?: DateRange | undefined\n  /** Called with the committed range, or undefined once cleared. */\n  onValueChange?: ((range: DateRange | undefined) => void) | undefined\n  /** Controlled popover state. */\n  open?: boolean | undefined\n  /** Initial popover state for uncontrolled use. */\n  defaultOpen?: boolean | undefined\n  /** Called whenever the popover opens or closes. */\n  onOpenChange?: ((open: boolean) => void) | undefined\n  /** Earliest selectable date; navigation stops here too. */\n  minDate?: Date | undefined\n  /** Latest selectable date; navigation stops here too. */\n  maxDate?: Date | undefined\n  /** Extra availability rules beyond min and max. */\n  isDateDisabled?: ((date: Date) => boolean) | undefined\n  /** Visible calendar months. One month is used on narrow screens. */\n  numberOfMonths?: number\n  /** A date-fns locale used for formatting and week starts. */\n  locale?: Locale | undefined\n  /** Overrides the locale's first day of the week. */\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined\n  /** Shows dates from adjacent months. */\n  showOutsideDays?: boolean\n  /** Optional quick ranges shown beside the calendar. */\n  presets?: DateRangePreset[] | undefined\n  /** Requires Apply before the value commits. */\n  confirmMode?: boolean\n  /** Allows clearing the value from the trigger and footer. */\n  clearable?: boolean\n  /** Prevents all interaction. */\n  disabled?: boolean\n  /** Shows the value without allowing edits. */\n  readOnly?: boolean\n  /** Marks the value as required; hides the clear action. */\n  required?: boolean\n  /** Applies the invalid treatment to the trigger. */\n  invalid?: boolean\n  /** Trigger text while empty. */\n  placeholder?: string\n  /** Replaces the visible formatting for complete ranges. */\n  formatRange?: ((range: DateRange) => string) | undefined\n  /** Overrides built-in strings for localization. */\n  labels?: Partial<DateRangePickerLabels>\n}\n\n/**\n * Picks a date range from a popover of quick ranges and calendars. Values\n * stay incomplete until the second endpoint arrives, so applications never\n * receive half a range.\n */\nfunction DateRangePicker({\n  value,\n  defaultValue,\n  onValueChange,\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  minDate,\n  maxDate,\n  isDateDisabled,\n  numberOfMonths = 2,\n  locale,\n  weekStartsOn,\n  showOutsideDays = true,\n  presets,\n  confirmMode = false,\n  clearable = true,\n  disabled = false,\n  readOnly = false,\n  required = false,\n  invalid = false,\n  placeholder = \"Select date range\",\n  formatRange,\n  labels: labelsProp,\n  className,\n  ...triggerProps\n}: DateRangePickerProps) {\n  const labels: DateRangePickerLabels = React.useMemo(\n    () => ({ ...DEFAULT_LABELS, ...labelsProp }),\n    [labelsProp]\n  )\n\n  const [internalValue, setInternalValue] = React.useState<DateRange | undefined>(defaultValue)\n  const isControlledValue = value !== undefined\n  // An absent controlled value reads the same as a cleared value, which is\n  // correct either way: both mean no committed range.\n  const committed = (isControlledValue ? value : internalValue) ?? undefined\n\n  const [openUncontrolled, setOpenUncontrolled] = React.useState(defaultOpen)\n  const isOpen = open !== undefined ? open : openUncontrolled\n\n  // Draft records work in progress while the popover is open. When it is\n  // inactive the trigger falls back to the committed value.\n  const [draftActive, setDraftActive] = React.useState(false)\n  const [draftValue, setDraftValue] = React.useState<DateRange | undefined>(undefined)\n\n  const [monthSync, setMonthSync] = React.useState<{ date: Date; nonce: number } | undefined>(\n    undefined\n  )\n\n  const [announcement, setAnnouncement] = React.useState(\"\")\n\n  const displayed: DateRange | undefined = draftActive ? draftValue : committed\n\n  const effectiveMonths = useEffectiveMonths(numberOfMonths)\n\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (open === undefined) {\n        setOpenUncontrolled(next)\n      }\n      onOpenChange?.(next)\n      // Discarding the draft on both edges covers cancel, outside clicks,\n      // and Escape without dedicated paths.\n      setDraftActive(false)\n      setDraftValue(undefined)\n    },\n    [open, onOpenChange]\n  )\n\n  const commit = React.useCallback(\n    (range: DateRange | undefined) => {\n      if (!isControlledValue) {\n        setInternalValue(range)\n      }\n      onValueChange?.(range)\n    },\n    [isControlledValue, onValueChange]\n  )\n\n  const announce = React.useCallback((message: string | undefined) => {\n    if (message) {\n      setAnnouncement(message)\n    }\n  }, [])\n\n  const announceSelection = React.useCallback(\n    (range: DateRange | undefined) => {\n      if (!range?.from) {\n        return\n      }\n      if (!range.to) {\n        announce(labels.startSelected?.(formatDateLabel(range.from, locale)))\n        return\n      }\n      announce(\n        labels.rangeSelected?.(\n          formatDateLabel(range.from, locale),\n          formatDateLabel(range.to, locale)\n        )\n      )\n    },\n    [announce, labels, locale]\n  )\n\n  const isAvailable = React.useCallback(\n    (date: Date) => {\n      const day = startOfDay(date)\n      if (minDate && isBefore(day, startOfDay(minDate))) {\n        return false\n      }\n      if (maxDate && isAfter(day, startOfDay(maxDate))) {\n        return false\n      }\n      return isDateDisabled ? !isDateDisabled(day) : true\n    },\n    [minDate, maxDate, isDateDisabled]\n  )\n\n  const spansUnavailableDay = React.useCallback(\n    (range: DateRange): boolean => {\n      if (!range.from || !range.to) {\n        return false\n      }\n      const days = eachDayOfInterval({ start: startOfDay(range.from), end: startOfDay(range.to) })\n      return days.some((day) => !isAvailable(day))\n    },\n    [isAvailable]\n  )\n\n  const handleDaySelect = React.useCallback(\n    (day: Date) => {\n      const base: DateRange = displayed ?? { from: undefined }\n      let next: DateRange\n      if (base.from && base.to) {\n        // Starting another range never asks users to clear first.\n        next = { from: day }\n      } else if (base.from) {\n        const completed =\n          day.getTime() >= base.from.getTime() ? { from: base.from, to: day } : { from: day, to: base.from }\n        next = completed\n      } else {\n        next = { from: day }\n      }\n\n      if (next.to && spansUnavailableDay(next)) {\n        // A range would cross an unavailable date; restart from this day.\n        next = { from: day }\n      }\n\n      setDraftActive(true)\n      setDraftValue(next)\n      setMonthSync((current) => ({ date: next.from ?? day, nonce: (current?.nonce ?? 0) + 1 }))\n\n      if (isRangeComplete(next)) {\n        announceSelection(next)\n        if (!confirmMode) {\n          commit(next)\n          setOpen(false)\n        }\n      } else {\n        announce(labels.startSelected?.(formatDateLabel(day, locale)))\n      }\n    },\n    [\n      displayed,\n      spansUnavailableDay,\n      announce,\n      announceSelection,\n      labels,\n      locale,\n      confirmMode,\n      commit,\n      setOpen,\n    ]\n  )\n\n  const handlePresetSelect = React.useCallback(\n    (range: DateRange) => {\n      setDraftActive(true)\n      setDraftValue(range)\n      setMonthSync((current) => ({ date: range.from ?? new Date(), nonce: (current?.nonce ?? 0) + 1 }))\n      announceSelection(range)\n      if (!confirmMode) {\n        if (isRangeComplete(range)) {\n          commit(range)\n        }\n        setOpen(false)\n      }\n    },\n    [announceSelection, confirmMode, commit, setOpen]\n  )\n\n  const handleClear = React.useCallback(() => {\n    setDraftActive(true)\n    setDraftValue(undefined)\n    announce(labels.cleared)\n  }, [announce, labels])\n\n  const canClear = clearable && !required\n\n  const triggerText = formatTriggerText(displayed, {\n    locale,\n    placeholder,\n    selectEndDateLabel: labels.selectEndDate,\n    formatRange,\n  })\n\n  const showFooter = confirmMode && !readOnly\n\n  if (readOnly) {\n    return (\n      <div\n        data-slot=\"date-range-trigger\"\n        data-readonly\n        aria-disabled={true}\n        className={cn(triggerClassNames({ invalid }), \"cursor-default hover:bg-inherit\", className)}\n      >\n        <CalendarIcon aria-hidden className=\"size-4 shrink-0 text-[var(--hui-color-foreground-base-secondary)]\" />\n        <span className=\"truncate\">{triggerText}</span>\n      </div>\n    )\n  }\n\n  return (\n    <div data-slot=\"date-range-picker-root\" className=\"relative inline-flex w-full\">\n      <Popover open={isOpen} onOpenChange={setOpen}>\n        <PopoverTrigger\n          render={\n            <Button\n              data-slot=\"date-range-trigger\"\n              data-size=\"default\"\n              data-open={isOpen || undefined}\n              variant=\"link\"\n              className={cn(\n                triggerClassNames({ invalid }),\n                \"justify-start px-[var(--hui-space-3)] shadow-none\",\n                canClear && committed && \"pe-[var(--hui-space-9)]\",\n                className\n              )}\n              disabled={disabled}\n              aria-invalid={invalid || undefined}\n              {...triggerProps}\n            />\n          }\n        >\n          <CalendarIcon\n            aria-hidden\n            className=\"pointer-events-none size-4 shrink-0 text-[var(--hui-color-foreground-base-secondary)]\"\n          />\n          <span\n            className={cn(\n              \"truncate\",\n              !displayed &&\n                \"text-[var(--hui-color-foreground-base-secondary)] [font-weight:var(--hui-font-weight-regular)]\"\n            )}\n          >\n            {triggerText}\n          </span>\n        </PopoverTrigger>\n\n        {canClear && committed !== undefined && (\n          <Button\n            type=\"button\"\n            size=\"icon-sm\"\n            variant=\"link\"\n            aria-label={labels.clearAction}\n            disabled={disabled}\n            tabIndex={-1}\n            onClick={(event) => {\n              event.stopPropagation()\n              setDraftActive(false)\n              setDraftValue(undefined)\n              setInternalValue(undefined)\n              onValueChange?.(undefined)\n              announce(labels.cleared)\n            }}\n            className=\"absolute end-[var(--hui-space-2)] top-1/2 z-10 -translate-y-1/2 rounded-full opacity-72 transition-opacity hover:opacity-100\"\n          >\n            <CloseIcon />\n          </Button>\n        )}\n\n        <PopoverContent\n          align=\"start\"\n          sideOffset={6}\n          className=\"max-w-[calc(100vw_-_var(--hui-space-8))] p-[var(--hui-space-4)]\"\n        >\n          <div className=\"flex flex-col gap-y-[var(--hui-space-3)] min-[52rem]:flex-row min-[52rem]:gap-x-[var(--hui-space-3)]\">\n            {presets && presets.length > 0 && (\n              <>\n                <DateRangePresets\n                  presets={presets}\n                  value={displayed}\n                  onSelect={handlePresetSelect}\n                  ariaLabel={labels.presetsGroup}\n                  disabled={disabled}\n                  className=\"flex-row gap-x-[var(--hui-space-2)] overflow-x-auto pb-[var(--hui-space-1)] min-[52rem]:w-40 min-[52rem]:shrink-0 min-[52rem]:flex-col min-[52rem]:overflow-visible\"\n                />\n                <Separator\n                  orientation=\"vertical\"\n                  size=\"full\"\n                  variant=\"secondary\"\n                  className=\"hidden min-[52rem]:block\"\n                />\n                <Separator\n                  orientation=\"horizontal\"\n                  size=\"full\"\n                  variant=\"secondary\"\n                  className=\"min-[52rem]:hidden\"\n                />\n              </>\n            )}\n\n            <div className=\"flex-1\">\n              <DateRangeCalendar\n                selected={draftActive ? draftValue : committed}\n                onDaySelect={handleDaySelect}\n                minDate={minDate}\n                maxDate={maxDate}\n                isDateDisabled={isDateDisabled}\n                numberOfMonths={effectiveMonths}\n                locale={locale}\n                weekStartsOn={weekStartsOn}\n                showOutsideDays={showOutsideDays}\n                monthSync={monthSync}\n                labels={labels}\n              />\n            </div>\n          </div>\n\n          {showFooter && (\n            <>\n              <Separator size=\"full\" variant=\"secondary\" className=\"mt-[var(--hui-space-4)] mb-[var(--hui-space-3)]\" />\n              <div className=\"flex items-center justify-between\">\n                {canClear ? (\n                  <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={handleClear}>\n                    Clear\n                  </Button>\n                ) : (\n                  <span />\n                )}\n                <div className=\"flex items-center gap-[var(--hui-space-2)]\">\n                  <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={() => setOpen(false)}>\n                    Cancel\n                  </Button>\n                  <Button\n                    type=\"button\"\n                    size=\"sm\"\n                    disabled={!isRangeComplete(draftActive ? draftValue : committed)}\n                    onClick={() => {\n                      const pending = draftActive ? draftValue : committed\n                      if (!isRangeComplete(pending)) {\n                        return\n                      }\n                      commit(pending)\n                      setOpen(false)\n                    }}\n                  >\n                    Apply\n                  </Button>\n                </div>\n              </div>\n            </>\n          )}\n        </PopoverContent>\n      </Popover>\n\n      <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n        {announcement}\n      </p>\n    </div>\n  )\n}\n\ninterface TriggerStyleInput {\n  invalid: boolean\n}\n\nfunction triggerClassNames({ invalid }: TriggerStyleInput): string {\n  return cn(\n    \"inline-flex h-[var(--hui-space-10)] w-full min-w-0 items-center gap-[var(--hui-space-3)] rounded-[var(--hui-radius-2)] border-[0.5px] bg-[var(--hui-color-background-base-primary)] text-left text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-medium)] [letter-spacing:var(--hui-letter-spacing-small)] [transition:var(--hui-transition-interactive)]\",\n    invalid\n      ? \"border-[var(--hui-color-border-danger-emphasis)]\"\n      : \"border-[var(--hui-color-border-base-tertiary)]\"\n  )\n}\n\n/**\n * Keeps one calendar on phones and the requested count elsewhere. Media\n * queries alone cannot change how many months DayPicker renders.\n */\nfunction useEffectiveMonths(requested: number): number {\n  const query = \"(max-width: 47.99rem)\"\n  const subscribeNarrow = React.useCallback(\n    (onStoreChange: () => void) => {\n      const mediaQueryList = window.matchMedia(query)\n      mediaQueryList.addEventListener(\"change\", onStoreChange)\n      return () => {\n        mediaQueryList.removeEventListener(\"change\", onStoreChange)\n      }\n    },\n    []\n  )\n  const getServerSnapshot = React.useCallback(() => false, [])\n  const getSnapshot = React.useCallback(() => window.matchMedia(query).matches, [])\n\n  const narrow = React.useSyncExternalStore(subscribeNarrow, getSnapshot, getServerSnapshot)\n  if (!Number.isFinite(requested) || requested < 1) {\n    return 2\n  }\n  return narrow ? Math.min(requested, 1) : requested\n}\n\n// Re-exported for consumers composing their own surface around the calendar.\nexport { DateRangeCalendar }\nexport type { DateRangeCalendarProps } from \"./date-range-calendar\"\nexport { DateRangePicker }\n"},{"path":"date-range-calendar.tsx","type":"registry:component","target":"components/ui/date-range-picker/date-range-calendar.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { DayPicker, dateMatchModifiers } from \"react-day-picker\"\nimport type { ClassNames, DateRange as DayPickerDateRange, Matcher } from \"react-day-picker\"\nimport { format } from \"date-fns\"\nimport type { Locale } from \"date-fns\"\nimport {\n  ChevronLeft as ChevronLeftIcon,\n  ChevronRight as ChevronRightIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nimport { completeRange, type DateRange } from \"./date-range-utils\"\n\n/**\n * Shared day button styles. The button fills its grid cell so the clickable\n * area and the range fill cover the whole square.\n */\nconst DAY_BUTTON_CLASS =\n  \"inline-flex h-full w-full cursor-pointer select-none items-center justify-center whitespace-nowrap rounded-[var(--hui-radius-1)] p-0 text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-small)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)] outline-none motion-safe:[transition:background-color_var(--hui-duration-fast)_var(--hui-ease-out)] hover:bg-[var(--hui-color-background-base-primary-hover)] focus-visible:[outline:var(--hui-focus-ring)] focus-visible:[outline-offset:-1px]\"\n\n/*\n * Range states arrive as class names on the cell (RDP's `range_start`,\n * `range_end`, and `range_middle` keys), never as data attributes. Each key\n * targets its own button so the fill reads as one object: middle cells stay\n * square while endpoints round only their outer edge. Committed ranges use\n * the solid accent; the hover and keyboard preview keeps the same shape\n * with lighter fills.\n */\n/*\n * Range states arrive as class names on the cell (RDP's `range_start`,\n * `range_end`, and `range_middle` keys), never as data attributes. Each key\n * targets its own button so the fill reads as one object: middle cells stay\n * square while endpoints round only their outer edge. Committed ranges use\n * the solid accent; the hover and keyboard preview keeps the same shape\n * with lighter fills. Marker classes let the base cell exclude selected\n * days from today-ring and muted-outside treatments.\n */\nconst CELL_BASE_CLASSES =\n  \"h-(--hui-space-10) w-(--hui-space-10) p-0 text-center align-middle [&[data-today]:not([data-disabled]):not(.hui-range-endpoint):not(.hui-range-midpoint)>button]:shadow-[inset_0_0_0_1px_var(--hui-color-border-accent-primary)] [&[data-disabled]>button]:pointer-events-none [&[data-disabled]>button]:cursor-not-allowed [&[data-disabled]>button]:text-[var(--hui-color-foreground-base-tertiary)]! [&[data-outside]:not([data-disabled]):not(.hui-range-endpoint):not(.hui-range-midpoint)>button:not(:disabled)]:text-[var(--hui-color-foreground-base-secondary)]\"\n\nfunction buildRangeClassNames(previewing: boolean): Partial<ClassNames> {\n  const fill = previewing\n    ? \"[&>button]:bg-[var(--hui-color-background-accent-primary)]! [&>button]:text-[var(--hui-color-foreground-accent-primary-hover)]!\"\n    : \"[&>button]:bg-[var(--hui-color-background-accent-emphasis)]! [&>button]:text-[var(--hui-color-foreground-accent-emphasis)]! [&>button:hover]:bg-[var(--hui-color-background-accent-emphasis-hover)]!\"\n  const emphasis = cn(\n    \"hui-range-endpoint [&>button]:[font-weight:var(--hui-font-weight-medium)]\",\n    fill\n  )\n  return {\n    range_start: cn(\n      \"[&>button]:rounded-s-[var(--hui-radius-2)]! [&>button]:rounded-e-none!\",\n      emphasis\n    ),\n    range_end: cn(\n      \"[&>button]:rounded-e-[var(--hui-radius-2)]! [&>button]:rounded-s-none!\",\n      emphasis\n    ),\n    // A single-day range carries both keys; the opposite-side resets cancel\n    // out, leaving one square-cornered accented cell without tails.\n    range_middle:\n      \"hui-range-midpoint [&>button]:rounded-none! [&>button]:bg-[var(--hui-color-background-accent-primary)]! [&>button]:text-[var(--hui-color-foreground-base-secondary)]!\",\n  }\n}\n\nexport interface DateRangeCalendarLabels {\n  previousMonth: string\n  nextMonth: string\n  todayLabel: (formattedDate: string) => string\n}\n\nexport interface DateRangeCalendarProps {\n  /** The committed or temporary range rendered as selected. */\n  selected?: DateRange | undefined\n  /** Called for every selectable day the user activates. */\n  onDaySelect: (date: Date) => void\n  /** Earliest selectable date; navigation stops here too. */\n  minDate?: Date | undefined\n  /** Latest selectable date; navigation stops here too. */\n  maxDate?: Date | undefined\n  /** Extra availability rules beyond min and max. */\n  isDateDisabled?: ((date: Date) => boolean) | undefined\n  numberOfMonths?: number\n  locale?: Locale | undefined\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined\n  showOutsideDays?: boolean\n  labels: DateRangeCalendarLabels\n  /**\n   * When the value changes while the picker stays open, the parent bumps a\n   * nonce anchored to the month that should become visible, such as after\n   * picking a preset from another month.\n   */\n  monthSync?: { date: Date; nonce: number } | undefined\n}\n\nexport function DateRangeCalendar({\n  selected,\n  onDaySelect,\n  minDate,\n  maxDate,\n  isDateDisabled,\n  numberOfMonths = 2,\n  locale,\n  weekStartsOn,\n  showOutsideDays = true,\n  labels,\n  monthSync,\n}: DateRangeCalendarProps) {\n  // The calendar mounts once per popover session, so starting from the\n  // current selection is enough. No sync effects needed.\n  const initialMonth = React.useMemo(() => resolveInitialMonth(selected), []) // eslint-disable-line react-hooks/exhaustive-deps\n  const [month, setMonth] = React.useState<Date>(initialMonth)\n  const [previewEnd, setPreviewEnd] = React.useState<Date | undefined>(undefined)\n\n  const disabledMatchers = buildDisabledMatchers(minDate, maxDate, isDateDisabled)\n  const startNavigationMonth = minDate ? firstOfMonth(minDate) : undefined\n  const endNavigationMonth = maxDate ? firstOfMonth(maxDate) : undefined\n\n  // Adjusting state during render is the React-approved way to react to a\n  // parent-driven \"show this month\" signal without effect cascades.\n  const [syncedNonce, setSyncedNonce] = React.useState(monthSync?.nonce)\n  if (monthSync && monthSync.nonce !== syncedNonce) {\n    setSyncedNonce(monthSync.nonce)\n    setMonth(\n      clampToNavigation(\n        firstOfMonth(monthSync.date),\n        startNavigationMonth,\n        endNavigationMonth\n      )\n    )\n    if (previewEnd) {\n      setPreviewEnd(undefined)\n    }\n  }\n  const selectingEnd = Boolean(selected?.from && !selected?.to)\n  const displayed: DayPickerDateRange | undefined =\n    selectingEnd && selected?.from && previewEnd && !isSameCalendarDay(selected.from, previewEnd)\n      ? completeRange(selected.from, previewEnd)\n      : selected\n\n  const previewing = Boolean(\n    displayed && displayed.from && displayed.to && !isSameCalendarDay(displayed.from, displayed.to) && selectingEnd\n  )\n  const rangeClassNames = buildRangeClassNames(previewing)\n\n  const previousDisabled = startNavigationMonth ? month.getTime() <= startNavigationMonth.getTime() : false\n  const nextDisabled = endNavigationMonth\n    ? addMonths(firstOfMonth(month), numberOfMonths - 1).getTime() >= endNavigationMonth.getTime()\n    : false\n\n  // Hover and keyboard focus preview the proposed range while only the\n  // start date exists. Unavailable dates never become the previewed end.\n  const updatePreviewEnd = (date: Date, unavailable: boolean) => {\n    if (!selectingEnd || unavailable) {\n      return\n    }\n    setPreviewEnd(date)\n  }\n\n  const clearPreviewEnd = () => {\n    setPreviewEnd(undefined)\n  }\n\n  return (\n    <div className=\"flex flex-col gap-[var(--hui-space-3)]\">\n      <div className=\"flex items-center justify-between\">\n        <Button\n          type=\"button\"\n          variant=\"link\"\n          size=\"icon-sm\"\n          aria-label={labels.previousMonth}\n          disabled={previousDisabled}\n          onClick={() => setMonth(shiftMonth(month, -1, startNavigationMonth, endNavigationMonth))}\n        >\n          <ChevronLeftIcon />\n        </Button>\n        <Button\n          type=\"button\"\n          variant=\"link\"\n          size=\"icon-sm\"\n          aria-label={labels.nextMonth}\n          disabled={nextDisabled}\n          onClick={() => setMonth(shiftMonth(month, 1, startNavigationMonth, endNavigationMonth))}\n        >\n          <ChevronRightIcon />\n        </Button>\n      </div>\n\n      <DayPicker\n        mode=\"range\"\n        excludeDisabled\n        selected={displayed}\n        onSelect={(range, triggerDate) => {\n          void range\n          if (!triggerDate || matchersReject(disabledMatchers, triggerDate)) {\n            return\n          }\n          onDaySelect(triggerDate)\n        }}\n        onDayMouseEnter={(date, modifiers) => {\n          updatePreviewEnd(date, modifiers.disabled === true)\n        }}\n        onDayMouseLeave={() => {\n          clearPreviewEnd()\n        }}\n        onDayFocus={(date, modifiers) => {\n          updatePreviewEnd(date, modifiers.disabled === true)\n        }}\n        onDayBlur={() => {\n          clearPreviewEnd()\n        }}\n        month={month}\n        onMonthChange={(nextMonthValue) => {\n          setMonth(\n            clampToNavigation(nextMonthValue, startNavigationMonth, endNavigationMonth)\n          )\n        }}\n        numberOfMonths={numberOfMonths}\n        hideNavigation\n        showOutsideDays={showOutsideDays}\n        autoFocus\n        locale={locale}\n        weekStartsOn={weekStartsOn}\n        disabled={disabledMatchers}\n        classNames={{\n          months: \"flex items-start gap-x-[var(--hui-space-8)]\",\n          month: \"flex flex-col\",\n          month_caption:\n            \"flex h-(--hui-space-9) items-center justify-center text-center text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-medium)] [letter-spacing:var(--hui-letter-spacing-small)]\",\n          caption_label: \"\",\n          nav: \"hidden\",\n          button_previous: \"hidden\",\n          button_next: \"hidden\",\n          weekdays: \"\",\n          weekday:\n            \"w-(--hui-space-10) pb-(--hui-space-2) text-center text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-mini)] [font-weight:var(--hui-font-weight-regular)] [letter-spacing:var(--hui-letter-spacing-mini)]\",\n          week: \"min-w-max\",\n          weeks: \"border-collapse\",\n          month_grid: \"border-collapse\",\n          day: CELL_BASE_CLASSES,\n          day_button: DAY_BUTTON_CLASS,\n          today: \"\",\n          outside: \"\",\n          disabled: \"\",\n          range_middle: rangeClassNames.range_middle ?? \"\",\n          range_start: rangeClassNames.range_start ?? \"\",\n          range_end: rangeClassNames.range_end ?? \"\",\n          selected: \"\",\n          footer: \"sr-only\",\n        }}\n        labels={{\n          labelDayButton: (date, modifiers, options) =>\n            buildDayLabel(date, modifiers, options ?? {}, locale, labels.todayLabel),\n        }}\n      />\n    </div>\n  )\n}\n\nfunction buildDisabledMatchers(\n  minDate: Date | undefined,\n  maxDate: Date | undefined,\n  isDateDisabled: ((date: Date) => boolean) | undefined\n): Matcher[] {\n  const matchers: Matcher[] = []\n  if (minDate) {\n    matchers.push({ before: minDate })\n  }\n  if (maxDate) {\n    matchers.push({ after: maxDate })\n  }\n  if (isDateDisabled) {\n    matchers.push(isDateDisabled)\n  }\n  return matchers\n}\n\nfunction matchersReject(matchers: Matcher[], date: Date): boolean {\n  return matchers.length > 0 && dateMatchModifiers(date, matchers)\n}\n\nfunction resolveInitialMonth(selected: DateRange | undefined): Date {\n  const anchor = selected?.from ?? selected?.to ?? new Date()\n  return firstOfMonth(anchor)\n}\n\nfunction firstOfMonth(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\nfunction shiftMonth(\n  month: Date,\n  amount: number,\n  startLimit: Date | undefined,\n  endLimit: Date | undefined\n): Date {\n  let candidate = addMonths(month, amount)\n  if (startLimit && candidate < startLimit) {\n    candidate = startLimit\n  }\n  if (endLimit && candidate > endLimit) {\n    candidate = endLimit\n  }\n  return candidate\n}\n\nfunction clampToNavigation(\n  month: Date,\n  startMonth: Date | undefined,\n  endMonth: Date | undefined\n): Date {\n  return shiftMonth(month, 0, startMonth, endMonth)\n}\n\nfunction isSameCalendarDay(a: Date, b: Date): boolean {\n  return (\n    a.getFullYear() === b.getFullYear() &&\n    a.getMonth() === b.getMonth() &&\n    a.getDate() === b.getDate()\n  )\n}\n\nfunction buildDayLabel(\n  date: Date,\n  modifiers: Record<string, boolean | undefined>,\n  options: { locale?: Locale },\n  locale: Locale | undefined,\n  todayLabel: (formattedDate: string) => string\n): string {\n  let label = format(date, \"EEEE, MMMM d, yyyy\", { ...(options ?? {}), locale })\n  if (modifiers.range_start && modifiers.range_end) {\n    label += \", single day range\"\n  } else if (modifiers.range_start) {\n    label += \", start of range\"\n  } else if (modifiers.range_end) {\n    label += \", end of range\"\n  } else if (modifiers.range_middle) {\n    label += \", within range\"\n  }\n  if (modifiers.today) {\n    label = todayLabel(label)\n  }\n  return label\n}\n"},{"path":"date-range-presets.tsx","type":"registry:component","target":"components/ui/date-range-picker/date-range-presets.tsx","content":"\"use client\"\n\nimport {\n  endOfDay,\n  startOfDay,\n  startOfMonth,\n  subDays,\n  subMonths,\n  endOfMonth,\n} from \"date-fns\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nimport { type DateRange, isSameRange } from \"./date-range-utils\"\n\n/**\n * A preset carries a label and returns its range when chosen. Calculating\n * the range at selection time keeps relative presets such as \"Last 7 days\"\n * correct after the component has been mounted for a while.\n */\nexport interface DateRangePreset {\n  label: string\n  getValue: () => DateRange\n}\n\n/**\n * The quick ranges recommended for a first configuration. Applications map\n * this list to localize the labels or replace entries with their own\n * ranges; nothing here is baked into the picker itself.\n */\nexport function getDateRangePresets(): DateRangePreset[] {\n  return [\n    { label: \"Today\", getValue: todayRange },\n    { label: \"Yesterday\", getValue: yesterdayRange },\n    { label: \"Last 7 days\", getValue: () => lastNDaysRange(7) },\n    { label: \"Last 30 days\", getValue: () => lastNDaysRange(30) },\n    { label: \"This month\", getValue: thisMonthRange },\n    { label: \"Last month\", getValue: lastMonthRange },\n  ]\n}\n\nfunction todayRange(): DateRange {\n  const day = startOfDay(new Date())\n  return { from: day, to: day }\n}\n\nfunction yesterdayRange(): DateRange {\n  const day = subDays(startOfDay(new Date()), 1)\n  return { from: day, to: day }\n}\n\nfunction lastNDaysRange(days: number): DateRange {\n  const yesterday = subDays(startOfDay(new Date()), 1)\n  return { from: subDays(yesterday, days - 1), to: yesterday }\n}\n\nfunction thisMonthRange(): DateRange {\n  const now = new Date()\n  return { from: startOfMonth(now), to: endOfDay(now) }\n}\n\nfunction lastMonthRange(): DateRange {\n  const previous = subMonths(new Date(), 1)\n  return { from: startOfMonth(previous), to: endOfMonth(previous) }\n}\n\nexport interface DateRangePresetsProps {\n  presets: DateRangePreset[]\n  /** The range shown in the calendar; used for the selected state. */\n  value?: DateRange | undefined\n  onSelect: (range: DateRange) => void\n  /** Accessible name for the preset group. */\n  ariaLabel?: string | undefined\n  disabled?: boolean | undefined\n  className?: string | undefined\n}\n\n/**\n * The narrow column of quick ranges beside the calendar. Presets use quiet\n * text buttons so they never compete visually with the selected range.\n */\nexport function DateRangePresets({\n  presets,\n  value,\n  onSelect,\n  ariaLabel,\n  disabled,\n  className,\n}: DateRangePresetsProps) {\n  return (\n    <div\n      data-slot=\"date-range-presets\"\n      role=\"group\"\n      aria-label={ariaLabel}\n      className={cn(\"flex flex-col gap-[var(--hui-space-2)]\", className)}\n    >\n      {presets.map((preset) => {\n        const isSelected = presetMatches(preset, value)\n        return (\n          <Button\n            key={preset.label}\n            type=\"button\"\n            variant=\"link\"\n            size=\"sm\"\n            disabled={disabled}\n            aria-pressed={isSelected}\n            data-selected={isSelected || undefined}\n            className={cn(\n              \"h-[var(--hui-space-9)] w-full shrink-0 justify-start rounded-[var(--hui-radius-2)] px-[var(--hui-space-4)]\",\n              \"[font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-regular)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)]\",\n              \"hover:bg-[var(--hui-color-background-base-primary-hover)]! active:bg-[var(--hui-color-background-base-primary-hover)]!\",\n              \"data-[selected]:bg-[var(--hui-color-background-accent-primary)]! data-[selected]:text-[var(--hui-color-foreground-accent-primary-hover)]! data-[selected]:[font-weight:var(--hui-font-weight-medium)]!\"\n            )}\n            onClick={() => onSelect(preset.getValue())}\n          >\n            {preset.label}\n          </Button>\n        )\n      })}\n    </div>\n  )\n}\n\n/**\n * A preset counts as selected only when it produces exactly the current\n * complete range, compared by calendar day.\n */\nfunction presetMatches(preset: DateRangePreset, value: DateRange | undefined): boolean {\n  if (!value?.from || !value.to) {\n    return false\n  }\n  const target = preset.getValue()\n  return isSameRange(target, value)\n}\n"},{"path":"date-range-utils.ts","type":"registry:component","target":"components/ui/date-range-picker/date-range-utils.ts","content":"import { differenceInCalendarDays, format, isSameDay } from \"date-fns\"\nimport type { Locale } from \"date-fns\"\n\n/**\n * The value shape used by DateRangePicker. `to` may be missing while a\n * selection is still incomplete; a cleared value is undefined at the\n * component level.\n */\nexport interface DateRange {\n  from: Date | undefined\n  to?: Date | undefined\n}\n\nexport function isRangeComplete(range: DateRange | undefined): boolean {\n  return Boolean(range?.from && range?.to)\n}\n\n/**\n * Compares two ranges by calendar day so equal values constructed at\n * different times of day stay interchangeable.\n */\nexport function isSameRange(\n  a: DateRange | undefined,\n  b: DateRange | undefined\n): boolean {\n  if (!a && !b) {\n    return true\n  }\n  if (!a || !b) {\n    return false\n  }\n  const fromMatches =\n    (!a.from && !b.from) || (a.from !== undefined && b.from !== undefined && isSameDay(a.from, b.from))\n  const toMatches =\n    (!a.to && !b.to) || (a.to !== undefined && b.to !== undefined && isSameDay(a.to, b.to))\n  return fromMatches && toMatches\n}\n\n/**\n * Builds the completed range for two picked days. When the second day comes\n * before the first, the earlier day becomes the start instead of reporting\n * an error.\n */\nexport function completeRange(first: Date, second: Date): DateRange {\n  const [from, to] =\n    differenceInCalendarDays(second, first) < 0 ? [second, first] : [first, second]\n  return { from, to }\n}\n\nexport function formatDateLabel(date: Date, locale?: Locale): string {\n  return format(date, \"MMM d, yyyy\", { locale })\n}\n\nexport interface FormatTriggerOptions {\n  locale?: Locale\n  placeholder: string\n  selectEndDateLabel: string\n  formatRange?: ((range: DateRange) => string) | undefined\n}\n\n/**\n * Returns the text shown in the trigger for the current selection state:\n * the placeholder, the partial \"start - Select end date\" state, or the full\n * range formatted by `formatRange` or the default formatter.\n */\nexport function formatTriggerText(\n  range: DateRange | undefined,\n  options: FormatTriggerOptions\n): string {\n  if (!range?.from) {\n    return options.placeholder\n  }\n\n  const start = formatDateLabel(range.from, options.locale)\n\n  if (!range.to) {\n    return `${start} - ${options.selectEndDateLabel}`\n  }\n\n  if (options.formatRange) {\n    return options.formatRange(range)\n  }\n\n  return `${start} - ${formatDateLabel(range.to, options.locale)}`\n}\n"}],"dependencies":["date-fns","honestui","react-day-picker"],"registryDependencies":["https://www.honestui.com/r/button.json","https://www.honestui.com/r/popover.json","https://www.honestui.com/r/separator.json"]}