表单
DatePicker
一个日期——或者一段日期——从日历里选。
什么时候用它
示例
default
Month and year are dropdowns — reaching two years back is one click, not twenty-four.
range
Last 30 days and its neighbours are one click; the grid is for everything else.
presets
Shortcuts are computed when clicked, so “today” means today even on a tab left open overnight.
说明
一个日期,从日历里选。
一个触发器加一个放在 Popover 里的 Calendar——与其说是个新组件,不如说是大家本来就会在每个界面上各拼一遍、还都拼得略有不同的那个组合。
它刻意不是“输入框加一个日历”。手打的日期需要解析,解析需要格式,而格式是一场没人赢得了的地区之争。当打字确实要紧的时候——比如很久以前的生日——日历的月份和年份是下拉框,那是同一条路径,但没有歧义。
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| label必填 | string | 给控件命名。必填——触发器上的文字是值,不是名字。 | |
| className | string | — | |
| defaultValue | Date | — | |
| disabled | boolean | false | — |
| disabledDates | ComponentProps<typeof Calendar>['disabled'] | 读者不能选的日期。原样传给日历。 | |
| format | (date: Date) => string | formatDate | 选中的日期在触发器上怎么打印。 |
| onValueChange | (value: Date | undefined) => void | — | |
| placeholder | string | 'Pick a date' | — |
| presets | boolean | DatePreset<Date>[] | 显示在日历格旁边的快捷项。传 `true` 用内置的那一套,传数组用你自己的,不传就没有。 | |
| value | Date | — |
组成部分
Composed at the call site rather than configured through props, so a layout this component did not anticipate is still expressible.
DateRangePicker
一段日期——一次入住、一个报表周期、一个筛选条件。
两个月并排,因为跨月的范围才是常见情况,而为了看清两端来回翻页正是范围选择器让人累的地方。在 sm 以下它们会堆叠,因为那里放不下两个——日历自己的 months 类已经带了这个规则,所以这里没什么要覆盖的。
面板会一直开着,直到两端都选完:一段范围在有第二个日期之前不算一个值,选完第一个就关掉意味着还得再打开一次才能选完。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| label必填 | string | — | |
| className | string | — | |
| defaultValue | DateRange | — | |
| disabled | boolean | false | — |
| disabledDates | ComponentProps<typeof Calendar>['disabled'] | — | |
| format | (date: Date) => string | formatDate | — |
| months | number | 2 | 并排显示几个月。在 `sm` 以下退回一个。 |
| onValueChange | (value: DateRange | undefined) => void | — | |
| placeholder | string | 'Pick a range' | — |
| presets | boolean | DatePreset<DateRange>[] | true | 显示在日历格旁边的快捷项——“最近 30 天”和它的邻居们。传 `true` 用内置那一套,传数组用你自己的。 这里默认开启,单日选择器上默认关闭,因为“最近 30 天”几乎就是范围选择器被问到的全部,而单个日期通常是某个具体的日子。 |
| value | DateRange | — |
再导出
RANGE_PRESETS = [
{ label: 'Last 7 days', value: () => daysAgo(7) },
{ label: 'Last 30 days', value: () => daysAgo(30) },
{ label: 'Last 90 days', value: () => daysAgo(90) },
{ label: 'Last 12 months', value: () => daysAgo(365) },
{
label: 'Month to date',
value: () => {
const to = new Date()
return { from: new Date(to.getFullYear(), to.getMonth(), 1), to }
},
},
{
label: 'Year to date',
value: () => {
const to = new Date()
return { from: new Date(to.getFullYear(), 0, 1), to }
},
},
]范围选择器几乎在每个出现它的界面上都会被要求的那几个快捷项,所以直接内置,而不是每个仪表盘再造一遍。
点击时才计算:渲染时就建好的预设列表,会把“今天”冻结在页面加载的那一刻,对任何开了一整夜的标签页来说都是错的。
DATE_PRESETS = [
{ label: 'Today', value: () => new Date() },
{
label: 'Tomorrow',
value: () => {
const date = new Date()
date.setDate(date.getDate() + 1)
return date
},
},
{
label: 'In a week',
value: () => {
const date = new Date()
date.setDate(date.getDate() + 7)
return date
},
},
{
label: 'In a month',
value: () => {
const date = new Date()
date.setMonth(date.getMonth() + 1)
return date
},
},
]单个日期的对应版本。
键盘操作
| 按键 | 作用 |
|---|---|
| EnterSpace | 打开日历。 |
| Escape | 不选日期直接关闭。 |
无障碍
- 触发器按访问者自己的地区格式打印日期,而不是写死的 dd/mm/yyyy。
- DateRangePicker 会等到两端都选完才关——一段范围在有第二个日期之前不算一个值。
- 右侧快捷选项是普通按钮,不是菜单:它们设的值和旁边的日历格设的是同一个,所以它们属于同一个控件,Tab 也在同一轮里走到。
- 快捷值是点击那一刻才算的,所以标签页开了一整夜,“今天”仍然是今天。