DatePicker
DatePicker 从 HeroUI v2 到 v3 的迁移指南。
完整的 API 参考、样式指南与高级示例请参阅 v3 DatePicker 文档。本指南只关注从 HeroUI v2 的迁移。
结构变化
在 v2 中,DatePicker 是一个自包含组件,通过 props 处理标签、描述、日历和时间输入:
import { DatePicker } from "@heroui/react";
export default function App() {
return (
<DatePicker label="Birth date" />
);
}在 v3 中,DatePicker 改用组合优先的 API,你需要显式组合 DateField 与 Calendar:
import { DatePicker, DateField, Calendar, Label } from "@heroui/react";
export default function App() {
return (
<DatePicker>
<Label>Birth date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
<Calendar.Header>
<Calendar.YearPickerTrigger>
<Calendar.YearPickerTriggerHeading />
<Calendar.YearPickerTriggerIndicator />
</Calendar.YearPickerTrigger>
<Calendar.NavButton slot="previous" />
<Calendar.NavButton slot="next" />
</Calendar.Header>
<Calendar.Grid>
<Calendar.GridHeader>
{(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
</Calendar.GridHeader>
<Calendar.GridBody>
{(date) => <Calendar.Cell date={date} />}
</Calendar.GridBody>
</Calendar.Grid>
</Calendar>
</DatePicker.Popover>
</DatePicker>
);
}主要变化
1. 组件结构
v2: 单一 DatePicker 组件,所有部分(输入、日历、标签、popover)都通过 props 在内部处理
v3: 基于组合的 API,由 DatePicker、DateField、Calendar 和 Label 共同组成。每个部分都是独立组件。
2. 组成部分
| v3 组件 | 描述 |
|---|---|
DatePicker | 根容器与状态持有者 |
DatePicker.Trigger | 打开日历 popover 的按钮 |
DatePicker.TriggerIndicator | 日历图标(默认)或自定义指示器 |
DatePicker.Popover | 日历的 popover 包装层 |
DateField.Group | 输入组包装层 |
DateField.Input | 分段日期输入 |
DateField.Segment | 单独的日期片段(月、日、年) |
DateField.Suffix | 触发按钮的后缀 slot |
Calendar | 完整的日历组件(参见 Calendar 迁移) |
Label | 外部 Label 组件 |
3. Prop 变更
| v2 prop | v3 等效项 | 说明 |
|---|---|---|
value | value | 保持一致 |
defaultValue | defaultValue | 保持一致 |
onChange | onChange | 保持一致 |
minValue | minValue | 保持一致 |
maxValue | maxValue | 保持一致 |
isDateUnavailable | isDateUnavailable | 保持一致 |
isDisabled | isDisabled | 保持一致 |
isReadOnly | isReadOnly | 保持一致 |
isRequired | isRequired | 保持一致 |
isInvalid | isInvalid | 保持一致 |
granularity | granularity | 保持一致 |
hourCycle | hourCycle | 保持一致 |
hideTimeZone | hideTimeZone | 保持一致 |
shouldForceLeadingZeros | shouldForceLeadingZeros | 保持一致 |
pageBehavior | - | 直接设置在 Calendar 上 |
label | - | 使用 Label 组件 |
description | - | 使用 Description 组件 |
errorMessage | - | 使用 FieldError 组件 |
variant | - | 使用 DateField.Group 的 variant prop 或 Tailwind CSS |
color | - | 已移除(请改用 Tailwind CSS) |
size | - | 已移除(请改用 Tailwind CSS) |
radius | - | 已移除(请改用 Tailwind CSS) |
labelPlacement | - | 使用 Tailwind CSS 控制布局 |
startContent | - | 在 DateField.Group 内组合 |
endContent | - | 在 DateField.Group 内组合 |
selectorIcon | - | 将 children 传给 DatePicker.TriggerIndicator |
visibleMonths | - | 使用 Calendar 的 visibleDuration prop |
showMonthAndYearPickers | - | 使用 Calendar.YearPickerTrigger 和 Calendar.YearPickerGrid |
calendarProps | - | 直接将 props 传给 Calendar |
popoverProps | - | 直接将 props 传给 DatePicker.Popover |
selectorButtonProps | - | 直接将 props 传给 DatePicker.Trigger |
timeInputProps | - | 单独组合时间输入 |
CalendarBottomContent | - | 将内容放在 DatePicker.Popover 内、Calendar 之后 |
validate | - | 在外部处理校验 |
placeholderValue | placeholderValue | 保持一致 |
autoFocus | autoFocus | 保持一致 |
disableAnimation | - | 已移除 |
classNames | - | 在各个组件上使用 className |
迁移示例
基本日期选择器
import { DatePicker } from "@heroui/react";
<DatePicker label="Birth date" />import { DatePicker, DateField, Calendar, Label } from "@heroui/react";
<DatePicker>
<Label>Birth date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
<Calendar.Header>
<Calendar.YearPickerTrigger>
<Calendar.YearPickerTriggerHeading />
<Calendar.YearPickerTriggerIndicator />
</Calendar.YearPickerTrigger>
<Calendar.NavButton slot="previous" />
<Calendar.NavButton slot="next" />
</Calendar.Header>
<Calendar.Grid>
<Calendar.GridHeader>
{(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
</Calendar.GridHeader>
<Calendar.GridBody>
{(date) => <Calendar.Cell date={date} />}
</Calendar.GridBody>
</Calendar.Grid>
</Calendar>
</DatePicker.Popover>
</DatePicker>受控状态
import { useState } from "react";
import { DatePicker } from "@heroui/react";
import { parseDate } from "@internationalized/date";
const [value, setValue] = useState(parseDate("2024-03-07"));
<DatePicker
label="Date"
value={value}
onChange={setValue}
/>import { useState } from "react";
import { DatePicker, DateField, Calendar, Label } from "@heroui/react";
import { parseDate } from "@internationalized/date";
const [value, setValue] = useState(parseDate("2024-03-07"));
<DatePicker value={value} onChange={setValue}>
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
<Calendar.Header>
<Calendar.Heading />
<Calendar.NavButton slot="previous" />
<Calendar.NavButton slot="next" />
</Calendar.Header>
<Calendar.Grid>
<Calendar.GridHeader>
{(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
</Calendar.GridHeader>
<Calendar.GridBody>
{(date) => <Calendar.Cell date={date} />}
</Calendar.GridBody>
</Calendar.Grid>
</Calendar>
</DatePicker.Popover>
</DatePicker>带描述和错误信息
<DatePicker
label="Event date"
description="Choose a future date"
errorMessage="Date must be in the future"
isInvalid={isInvalid}
/>import { DatePicker, DateField, Calendar, Label, Description, FieldError } from "@heroui/react";
<DatePicker isInvalid={isInvalid}>
<Label>Event date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<Description>Choose a future date</Description>
<FieldError>Date must be in the future</FieldError>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
{/* Calendar compound components */}
</Calendar>
</DatePicker.Popover>
</DatePicker>自定义选择器图标
import { Icon } from "@iconify/react";
<DatePicker
label="Date"
selectorIcon={<Icon icon="gravity-ui:calendar" />}
/>import { Icon } from "@iconify/react";
<DatePicker>
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator>
<Icon icon="gravity-ui:calendar" />
</DatePicker.TriggerIndicator>
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
{/* Calendar compound components */}
</Calendar>
</DatePicker.Popover>
</DatePicker>带月份 / 年份选择器和日历底部内容
<DatePicker
label="Date"
showMonthAndYearPickers
CalendarBottomContent={
<button onClick={() => setValue(today(getLocalTimeZone()))}>Today</button>
}
/><DatePicker>
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
<Calendar.Header>
<Calendar.YearPickerTrigger>
<Calendar.YearPickerTriggerHeading />
<Calendar.YearPickerTriggerIndicator />
</Calendar.YearPickerTrigger>
<Calendar.NavButton slot="previous" />
<Calendar.NavButton slot="next" />
</Calendar.Header>
<Calendar.Grid>
<Calendar.GridHeader>
{(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
</Calendar.GridHeader>
<Calendar.GridBody>
{(date) => <Calendar.Cell date={date} />}
</Calendar.GridBody>
</Calendar.Grid>
<Calendar.YearPickerGrid>
<Calendar.YearPickerGridBody>
{(year) => <Calendar.YearPickerCell date={year} />}
</Calendar.YearPickerGridBody>
</Calendar.YearPickerGrid>
</Calendar>
<button onClick={() => setValue(today(getLocalTimeZone()))}>Today</button>
</DatePicker.Popover>
</DatePicker>样式变化
v2:classNames prop
<DatePicker
classNames={{
base: "custom-base",
selectorButton: "custom-trigger",
selectorIcon: "custom-icon",
popoverContent: "custom-popover",
calendar: "custom-calendar",
}}
/>v3:直接使用 className prop
<DatePicker className="custom-base">
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger className="custom-trigger">
<DatePicker.TriggerIndicator className="custom-icon" />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover className="custom-popover">
<Calendar aria-label="Choose date" className="custom-calendar">
{/* Calendar compound components */}
</Calendar>
</DatePicker.Popover>
</DatePicker>组件结构
v3 DatePicker 遵循以下结构:
DatePicker (Root)
├── Label
├── DateField.Group
│ ├── DateField.Input
│ │ └── DateField.Segment (render prop per segment)
│ └── DateField.Suffix
│ └── DatePicker.Trigger
│ └── DatePicker.TriggerIndicator
├── Description (optional)
├── FieldError (optional)
└── DatePicker.Popover
├── Calendar (see Calendar migration guide)
└── [Custom bottom content]总结
- 组件结构:单一组件 → 由
DatePicker、DateField、Calendar和Label组合而成 - 标签:
labelprop →Label组件 - 描述 / 错误信息:props →
Description和FieldError组件 - 日历:内置日历 → 在
DatePicker.Popover内组合带有自身复合组件的Calendar - 选择器图标:
selectorIconprop → 将 children 传给DatePicker.TriggerIndicator - 年份选择器:
showMonthAndYearPickersprop → 使用Calendar.YearPickerTrigger和Calendar.YearPickerGrid - 日历底部内容:
CalendarBottomContentprop → 将内容放在DatePicker.Popover内、Calendar之后 - 已移除样式 prop:
variant、color、size、radius→ 使用 Tailwind CSS 或DateField.Group的变体 - 已移除 classNames:在各个复合组件上使用
className - 多个月份:
visibleMonthsprop → 使用Calendar的visibleDurationprop