Cadenza
EN

DateRangePicker

两端可键入的日期区间输入 + 双月日历——焦点在哪端,日历就填哪端

日期区间的完整控件,DatePicker 的双端版。 两个输入框各管一端,都可以直接键入;日历填的是焦点所在的那一端,还差 一端时焦点自动跳过去,所以连点两下就凑齐一个区间(见活动端)。 值的键名是 react-day-picker 的 from / to,原样传给日历;部件名用它 UI 侧的词(range_start / range_end):StartInput 编辑 fromEndInput 编辑 to

顺序反了就自动交换,两端都不会被清空。

使用

import { DateRangePicker } from '@gedatou/cadenza-ui'
<DateRangePicker aria-label="日期范围" startPlaceholder="开始" endPlaceholder="结束" />

不写 children 就有完整默认组合。根是 role="group"aria-label 落在组上; 两个输入框自带英文无障碍名(Start date / End date),组合时可覆盖。id 转发给起点输入框,FieldLabel htmlFor 直连它。

组成

写了 children 就替换输入侧;弹层默认在场,组合了 DateRangePickerPopup 才让位。标记部件必须是根的直接子节点或裹在 Fragment 里。

DateRangePicker
├─ InputGroup
│  ├─ DateRangePickerStartInput
│  ├─ (分隔图标)
│  ├─ DateRangePickerEndInput
│  ├─ DateRangePickerClear
│  └─ DateRangePickerTrigger
└─ DateRangePickerPopup
   ├─ Calendar(默认,numberOfMonths=2)
   └─ DateRangePickerFooter(可选,组合即进入确认模式)

受控

值是 { from?: Date, to?: Date } | null两端都是可选的——先点哪个输入框 就先填哪一端(见活动端),所以只有 to 的半程与只有 from 的 半程同样合法。半程也会走 onValueChange;两端都空不会出现,那是 null, 受控空值。details.reason / details.cancel() 与 DatePicker 同一套协议。

清空任一输入框离开只清掉那一端,另一端留着。

活动端

焦点在哪个输入框,日历的下一次点击就填哪一端。 点终点框再点日历,日期 落进终点、起点留空;还差一端时焦点自动跳到空着的那个框,所以连点两下就能凑齐 一个区间,中途不用回头。凑齐之后光标停在最后那天落进的那个框里。Tab 切换 输入框同样会重新瞄准。

顺序反了就自动交换,绝不清空任何一端。 瞄准哪一端只在另一端还空着时起作用; 两天都在了,就由先后决定谁是起点。先点 20 号再点 10 号,得到 10–20 号;在起点框 选一个晚于终点的日期,原来的终点变成新起点——不会有日期被悄悄丢掉。光标跟着 你选的那天走,所以它跑到哪个框,光标就在哪个框。

键入走同一套规则,只是会动到另一端的值要等文本成为一个完整日期才生效。 逐字符解析会让 2026-08-2 先被读成 2 号,照规则处理就会在打字中途改写另一个 输入框;判据是把解析结果按 format 格式化回去是否等于原文——2026-08-1 对不上 (格式化成 2026-08-01),补完 2026-08-10 才对得上。所以在终点框打一个早于 起点的日期,落下最后一位的瞬间两端就排好了,不必离开输入框。

只填自己那一端的键入不受这条约束,照常边打边提交。自定义 inputToValue 用的是 它自己的写法,对不上这个判据,保持保守——留草稿,离开输入框时再结算。

年月选择

两个月各有一套年月下拉,默认在场——区间常常横跨几个月,从哪一侧跳都行, 和活动端是同一个直觉:手放在哪边就管哪边。

右侧那套是带偏移的:在它里面选 3 月,得到的是 2 月–3 月而不是 3 月–4 月 (你指定的是那一格显示什么,不是整个面板从哪开始)。两个月始终相邻,这一条 由 react-day-picker 保证。

年份范围、locale 联动、换回纯标题的写法与 DatePicker 的年月选择完全同款, 包括「收窄要走弹层的函数 children」这一点:

<DateRangePickerPopup>
  {calendar => (
    <Calendar {...calendar} endMonth={new Date(2030, 11)} startMonth={new Date(2020, 0)} />
  )}
</DateRangePickerPopup>

确认模式

往弹层里组合 DateRangePickerFooter,点选就从「即时提交」变成「暂存」 (输入框实时预览、面板留着给你检查),DateRangePickerClose(或 Enter) 才提交并关闭——reason 是 close-pressDateRangePickerCancel、Esc、 点击外部都丢弃暂存。Footer 是纯布局壳,动作部件与文案由使用方组合,契约与 DatePicker 的确认模式完全同款。

手势本身两个模式一样(都走活动端),差别只在什么时候提交:即时 模式凑齐两端就提交并关闭,确认模式下点选一直是暂存,由确定按钮宣告完成—— 所以确认模式里区间可以反复雕塑,改完哪端都不会被自动关掉。

表单

name 就渲染两个常驻同名 hidden input(起、止各一,yyyy-MM-dd, 空端是 ''),FormData.getAll(name) 一次拿到两端。必填校验放表单层做。 与 TanStack Form 的绑定见 表单指南

禁用

与 DatePicker 相同:disabled 禁整个控件(hidden input 不参与提交); readOnly 只锁编辑,默认组合摘掉清除与日历按钮(InputGrouphas-disabled 变暗契约,理由见 DatePicker 的禁用节)。

什么时候用 DateRangePicker

起止成对出现(入住—退房、报表区间)用它;单个日期用 DatePickerformat / locale / disabledDates 与 DatePicker 完全同款,此处不再重复演示。

状态与 className

根是普通 <div>role="group"),className 就是字符串。

属性出现时机
data-empty没有值
data-open弹层开着
data-disableddisabled
data-readonlyreadOnly
data-slot是什么
date-range-picker
date-range-picker-trigger / -addon日历按钮及其 addon
date-range-picker-clear / -addon清除按钮及其 addon
date-range-picker-popup弹层
date-range-picker-footer确认模式的动作行
date-range-picker-footer-clear / date-range-picker-cancel / date-range-picker-close三个动作按钮

两个输入框都不带自己的 data-slot(input-group-control 焦点环契约, 同 DatePicker)。

键盘交互

按键效果
Tab起点 → 终点;换框时结算当前框的草稿
ArrowDown在任一输入框上打开弹层
Escape关闭弹层(值不动)
Enter结算当前框;弹层开着时同时关闭且不提交表单

Props

顺序规则:必填 → 非受控默认值 → 受控值 → 回调 → 行为开关 → 外观 → className

DateRangePicker

根。与 DatePicker 的根 同构, 差异只有值类型与占位:

Prop类型默认值说明
defaultValueDateRange | null非受控初始值
valueDateRange | null受控值;null 是受控空值
onValueChange(value, details) => void半程也会触发;details.cancel() 拒绝
startPlaceholderstring起点输入框占位
endPlaceholderstring终点输入框占位
namestring有则渲染两个同名 hidden input
idstring转发给起点输入框
aria-labelstring组的无障碍名(根 role="group"
其余同 DatePicker(open 三件、format / locale / inputToValue / disabledDatesdisabled / readOnly / clearable / modalchildren)+ ComponentProps<'div'>契约同款;inputToValue 两端共用

DateRangePickerStartInput / DateRangePickerEndInput

两端的真 <input>,分别编辑 from / to,各带英文无障碍名可覆盖。都收 ComponentProps<typeof InputGroupInput>

DateRangePickerTrigger / DateRangePickerClear

与 DatePicker 的同名部件同款(清除的 aria-label 是 Clear dates,一次清 两端)。都收 ComponentProps<typeof InputGroupButton>

DateRangePickerPopup

DatePickerPopup 同款三层;普通 children 渲染在默认日历下方(放 Footer 的位置);函数 children 收 DateRangePickerCalendarPropsmode: 'range'、双月),spread 进自己的 <Calendar> 可改月数、布局等。

DateRangePickerFooter / FooterClear / Cancel / Close

日历下方的动作行(纯布局壳,组合即进入确认模式)与三个动作 按钮,形态与 DatePicker 的同名部件 完全同款:Footer 收 ComponentProps<'div'>,按钮收 ComponentProps<typeof Button>

17 个类型一并导出:DateRange / DateRangePickerProps / DateRangePickerState / DateRangePickerStartInputProps / DateRangePickerEndInputProps / DateRangePickerTriggerProps / DateRangePickerClearProps / DateRangePickerPopupProps / DateRangePickerFooterProps / DateRangePickerFooterClearProps / DateRangePickerCancelProps / DateRangePickerCloseProps / DateRangePickerCalendarProps / DateRangePickerChangeEventReason / DateRangePickerChangeEventDetails / DateRangePickerOpenChangeEventReason / DateRangePickerOpenChangeEventDetails