日期区间的完整控件,DatePicker 的双端版。
两个输入框各管一端,都可以直接键入;日历填的是焦点所在的那一端,还差
一端时焦点自动跳过去,所以连点两下就凑齐一个区间(见活动端)。
值的键名是 react-day-picker 的 from / to,原样传给日历;部件名用它 UI
侧的词(range_start / range_end):StartInput 编辑 from,EndInput
编辑 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-press;DateRangePickerCancel、Esc、
点击外部都丢弃暂存。Footer 是纯布局壳,动作部件与文案由使用方组合,契约与
DatePicker 的确认模式完全同款。
手势本身两个模式一样(都走活动端),差别只在什么时候提交:即时 模式凑齐两端就提交并关闭,确认模式下点选一直是暂存,由确定按钮宣告完成—— 所以确认模式里区间可以反复雕塑,改完哪端都不会被自动关掉。
表单
有 name 就渲染两个常驻同名 hidden input(起、止各一,yyyy-MM-dd,
空端是 ''),FormData.getAll(name) 一次拿到两端。必填校验放表单层做。
与 TanStack Form 的绑定见
表单指南。
禁用
与 DatePicker 相同:disabled 禁整个控件(hidden input 不参与提交);
readOnly 只锁编辑,默认组合摘掉清除与日历按钮(InputGroup 的
has-disabled 变暗契约,理由见
DatePicker 的禁用节)。
什么时候用 DateRangePicker
起止成对出现(入住—退房、报表区间)用它;单个日期用
DatePicker。format / locale /
disabledDates 与 DatePicker 完全同款,此处不再重复演示。
状态与 className
根是普通 <div>(role="group"),className 就是字符串。
两个输入框都不带自己的 data-slot(input-group-control 焦点环契约,
同 DatePicker)。
键盘交互
Props
顺序规则:必填 → 非受控默认值 → 受控值 → 回调 → 行为开关 → 外观 → className。
DateRangePicker
根。与 DatePicker 的根 同构, 差异只有值类型与占位:
DateRangePickerStartInput / DateRangePickerEndInput
两端的真 <input>,分别编辑 from / to,各带英文无障碍名可覆盖。都收
ComponentProps<typeof InputGroupInput>。
DateRangePickerTrigger / DateRangePickerClear
与 DatePicker 的同名部件同款(清除的 aria-label 是 Clear dates,一次清
两端)。都收 ComponentProps<typeof InputGroupButton>。
DateRangePickerPopup
与 DatePickerPopup 同款三层;普通 children 渲染在默认日历下方(放
Footer 的位置);函数 children 收 DateRangePickerCalendarProps
(mode: '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。