"use client" import * as React from "react" import { CalendarIcon, XIcon } from "lucide-react" import { Calendar, type CalendarDateRange, type CalendarProps } from "@/components/calendar/calendar" import { Button } from "@/components/ui/button" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { cn } from "@/lib/utils" import { parseDateKey } from "./date-utils" export type DateRangePickerValue = CalendarDateRange export type DateRangePickerLabels = CalendarProps["labels"] & { placeholder?: string apply?: React.ReactNode clear?: React.ReactNode start?: string end?: string } export type DateRangePickerPreset = { label: React.ReactNode value: DateRangePickerValue } export type DateRangePickerProps = Omit< CalendarProps, "mode" | "value" | "onValueChange" | "range" | "onRangeChange" | "labels" > & { value?: DateRangePickerValue onValueChange?: (value: DateRangePickerValue) => void labels?: DateRangePickerLabels placeholder?: string disabled?: boolean clearable?: boolean formatValue?: (value: string) => React.ReactNode closeOnSelect?: boolean closeOnPresetSelect?: boolean showFooter?: boolean presets?: DateRangePickerPreset[] triggerClassName?: string contentClassName?: string months?: number } function defaultFormatValue(value: string) { const date = parseDateKey(value) if (!date) return value return new Intl.DateTimeFormat("en-US", { dateStyle: "medium" }).format(date) } function getTextLabel(value: React.ReactNode, fallback: string) { return typeof value === "string" ? value : fallback } function DateRangePicker({ className, value, onValueChange, labels, placeholder, disabled = false, clearable = true, formatValue = defaultFormatValue, closeOnSelect = false, closeOnPresetSelect = false, showFooter = true, presets, triggerClassName, contentClassName, months, numberOfMonths = 2, pagedNavigation = true, ...calendarProps }: DateRangePickerProps) { const [open, setOpen] = React.useState(false) const [draftValue, setDraftValue] = React.useState(value ?? {}) const from = value?.from ?? "" const to = value?.to ?? "" const hasValue = Boolean(from || to) const activeValue = showFooter ? draftValue : value const activeFrom = activeValue?.from ?? "" const activeTo = activeValue?.to ?? "" const hasDraftValue = Boolean(activeFrom || activeTo) const resolvedNumberOfMonths = months ?? numberOfMonths const label = from && to ? `${formatValue(from)} - ${formatValue(to)}` : from ? `${formatValue(from)} - ...` : placeholder ?? labels?.placeholder ?? "Select date range" const draftLabel = activeFrom && activeTo ? `${formatValue(activeFrom)} - ${formatValue(activeTo)}` : activeFrom ? `${formatValue(activeFrom)} - ...` : labels?.placeholder ?? "Select date range" React.useEffect(() => { if (open && showFooter) { setDraftValue(value ?? {}) } }, [open, showFooter, value]) const handleRangeChange = (nextValue: DateRangePickerValue) => { if (showFooter) { setDraftValue(nextValue) return } onValueChange?.(nextValue) if (closeOnSelect && nextValue.from && nextValue.to) { setOpen(false) } } const applyDraftValue = () => { onValueChange?.(draftValue) setOpen(false) } const clearDraftValue = () => { const nextValue = {} if (showFooter) { setDraftValue(nextValue) return } onValueChange?.(nextValue) } const resetValue = () => { const nextValue = {} setDraftValue(nextValue) onValueChange?.(nextValue) } const clearValue = (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() resetValue() } const selectPreset = (presetValue: DateRangePickerValue) => { if (showFooter) { setDraftValue(presetValue) if (closeOnPresetSelect) { onValueChange?.(presetValue) setOpen(false) } return } onValueChange?.(presetValue) if ((closeOnSelect || closeOnPresetSelect) && presetValue.from && presetValue.to) { setOpen(false) } } return (
} > {hasValue ? ( {from ? formatValue(from) : "..."} - {to ? formatValue(to) : "..."} ) : ( {label} )} {clearable && hasValue ? ( { if (event.key === "Enter" || event.key === " ") { event.preventDefault() resetValue() } }} > ) : null} {presets?.length ? (
{presets.map((preset, index) => ( ))}
) : null} { const fromValue = range?.from ? formatValue(range.from) : null const toValue = range?.to ? formatValue(range.to) : null if (fromValue && toValue) return `${fromValue} -> ${toValue}` if (fromValue) return `${fromValue} -> ...` return "No range selected" }} numberOfMonths={resolvedNumberOfMonths} pagedNavigation={pagedNavigation} showClearShortcut={!showFooter && clearable} showSelectionSummary {...calendarProps} /> {showFooter && (
{draftLabel}
)}
) } export { DateRangePicker }