ProComponents, templates & AI tooling
HeroUI
27.7k

DateRangePicker

DateRangePicker 从 HeroUI v2 到 v3 的迁移指南。

完整的 API 参考、样式指南与高级示例请参阅 v3 DateRangePicker 文档。本指南只关注从 HeroUI v2 的迁移。

结构变化

在 v2 中,DateRangePicker 是单一自包含组件,通过 prop 配置标签、输入、日历与时间字段等:

import { DateRangePicker } from "@heroui/react";

export default function App() {
  return (
    <DateRangePicker label="Trip dates" />
  );
}

在 v3 中,DateRangePicker 采用“组合优先”的 API,需要你显式组合 DateFieldRangeCalendar

import { DateField, DateRangePicker, Label, RangeCalendar } from "@heroui/react";

export default function App() {
  return (
    <DateRangePicker>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator />
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose trip dates">
          <RangeCalendar.Header>
            <RangeCalendar.YearPickerTrigger>
              <RangeCalendar.YearPickerTriggerHeading />
              <RangeCalendar.YearPickerTriggerIndicator />
            </RangeCalendar.YearPickerTrigger>
            <RangeCalendar.NavButton slot="previous" />
            <RangeCalendar.NavButton slot="next" />
          </RangeCalendar.Header>
          <RangeCalendar.Grid>
            <RangeCalendar.GridHeader>
              {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
            </RangeCalendar.GridHeader>
            <RangeCalendar.GridBody>
              {(date) => <RangeCalendar.Cell date={date} />}
            </RangeCalendar.GridBody>
          </RangeCalendar.Grid>
        </RangeCalendar>
      </DateRangePicker.Popover>
    </DateRangePicker>
  );
}

主要变化

1. 组件结构

v2: 单一 DateRangePicker,两个输入、分隔符、日历、标签、popover 等主要由内部 prop 处理。
v3: 组合式 API:将 DateRangePickerDateFieldRangeCalendarLabel 等组合在一起;两个 DateField.Input 分别使用 slot="start"slot="end" 表示范围输入。

2. 组合结构说明

v3 组件说明
DateRangePicker根容器与状态持有者
DateRangePicker.Trigger打开日历 popover 的按钮
DateRangePicker.TriggerIndicator日历图标(默认)或自定义指示器
DateRangePicker.RangeSeparator开始/结束日期输入之间的分隔符(默认 " - "
DateRangePicker.Popover包裹范围日历的 popover
DateField.Group输入组外层
DateField.InputContainer开始/结束输入与分隔符容器
DateField.Input slot="start"开始日期的分段输入
DateField.Input slot="end"结束日期的分段输入
DateField.Segment单个日期片段(月/日/年等)
DateField.Suffix触发按钮的后缀插槽
RangeCalendar完整范围日历(参见 RangeCalendar 迁移
Label外部标签组件

3. Prop 变更

v2 propv3 对应说明
valuevalue相同
defaultValuedefaultValue相同
onChangeonChange相同
minValueminValue相同
maxValuemaxValue相同
isDisabledisDisabled相同
isReadOnlyisReadOnly相同
isRequiredisRequired相同
isInvalidisInvalid相同
isOpenisOpen相同
defaultOpendefaultOpen相同
onOpenChangeonOpenChange相同
granularitygranularity相同
hourCyclehourCycle相同
hideTimeZonehideTimeZone相同
shouldForceLeadingZerosshouldForceLeadingZeros相同
allowsNonContiguousRanges直接在 RangeCalendar 上设置
pageBehavior直接在 RangeCalendar 上设置
label使用 Label 组件
description使用 Description 组件
errorMessage使用 FieldError 组件
variant使用 DateField.Groupvariant prop,或 Tailwind CSS
color已移除(请用 Tailwind CSS)
size已移除(请用 Tailwind CSS)
radius已移除(请用 Tailwind CSS)
labelPlacement用 Tailwind CSS 控制布局
selectorIcon将 children 传给 DateRangePicker.TriggerIndicator
selectorButtonPlacement通过 DateField.PrefixDateField.Suffix 组合触发器位置
visibleMonths使用 RangeCalendarvisibleDuration prop
showMonthAndYearPickers使用 RangeCalendar.YearPickerTriggerRangeCalendar.YearPickerGrid
calendarProps直接将 prop 传给 RangeCalendar
popoverProps直接将 prop 传给 DateRangePicker.Popover
selectorButtonProps直接将 prop 传给 DateRangePicker.Trigger
timeInputProps时间输入需单独组合实现
calendarWidth已移除(请用 Tailwind CSS)
validate在外部处理校验
placeholderValueplaceholderValue相同
autoFocusautoFocus相同
disableAnimation已移除
classNames在各子组件上使用 className

迁移示例

基础 DateRangePicker

import { DateRangePicker } from "@heroui/react";

<DateRangePicker label="Trip dates" />
import { DateField, DateRangePicker, Label, RangeCalendar } from "@heroui/react";

<DateRangePicker>
  <Label>Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger>
        <DateRangePicker.TriggerIndicator />
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <DateRangePicker.Popover>
    <RangeCalendar aria-label="Choose trip dates">
      <RangeCalendar.Header>
        <RangeCalendar.YearPickerTrigger>
          <RangeCalendar.YearPickerTriggerHeading />
          <RangeCalendar.YearPickerTriggerIndicator />
        </RangeCalendar.YearPickerTrigger>
        <RangeCalendar.NavButton slot="previous" />
        <RangeCalendar.NavButton slot="next" />
      </RangeCalendar.Header>
      <RangeCalendar.Grid>
        <RangeCalendar.GridHeader>
          {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
        </RangeCalendar.GridHeader>
        <RangeCalendar.GridBody>
          {(date) => <RangeCalendar.Cell date={date} />}
        </RangeCalendar.GridBody>
      </RangeCalendar.Grid>
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>

受控状态

import { useState } from "react";
import { DateRangePicker } from "@heroui/react";
import { parseDate } from "@internationalized/date";

const [value, setValue] = useState({
  start: parseDate("2024-03-01"),
  end: parseDate("2024-03-14"),
});

<DateRangePicker
  label="Trip dates"
  value={value}
  onChange={setValue}
/>
import { useState } from "react";
import { DateField, DateRangePicker, Label, RangeCalendar } from "@heroui/react";
import { parseDate } from "@internationalized/date";

const [value, setValue] = useState({
  start: parseDate("2024-03-01"),
  end: parseDate("2024-03-14"),
});

<DateRangePicker value={value} onChange={setValue}>
  <Label>Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger>
        <DateRangePicker.TriggerIndicator />
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <DateRangePicker.Popover>
    <RangeCalendar aria-label="Choose trip dates">
      <RangeCalendar.Header>
        <RangeCalendar.Heading />
        <RangeCalendar.NavButton slot="previous" />
        <RangeCalendar.NavButton slot="next" />
      </RangeCalendar.Header>
      <RangeCalendar.Grid>
        <RangeCalendar.GridHeader>
          {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
        </RangeCalendar.GridHeader>
        <RangeCalendar.GridBody>
          {(date) => <RangeCalendar.Cell date={date} />}
        </RangeCalendar.GridBody>
      </RangeCalendar.Grid>
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>

描述与错误信息

<DateRangePicker
  label="Trip dates"
  description="Select your travel period"
  errorMessage="End date must be after start date"
  isInvalid={isInvalid}
/>
import { DateField, DateRangePicker, Label, Description, FieldError, RangeCalendar } from "@heroui/react";

<DateRangePicker isInvalid={isInvalid}>
  <Label>Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger>
        <DateRangePicker.TriggerIndicator />
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <Description>Select your travel period</Description>
  <FieldError>End date must be after start date</FieldError>
  <DateRangePicker.Popover>
    <RangeCalendar aria-label="Choose trip dates">
      {/* RangeCalendar compound components */}
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>

自定义选择器图标

import { Icon } from "@iconify/react";

<DateRangePicker
  label="Trip dates"
  selectorIcon={<Icon icon="gravity-ui:calendar" />}
/>
import { Icon } from "@iconify/react";

<DateRangePicker>
  <Label>Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger>
        <DateRangePicker.TriggerIndicator>
          <Icon icon="gravity-ui:calendar" />
        </DateRangePicker.TriggerIndicator>
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <DateRangePicker.Popover>
    <RangeCalendar aria-label="Choose trip dates">
      {/* RangeCalendar compound components */}
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>

多月份显示与年份选择器

<DateRangePicker
  label="Trip dates"
  visibleMonths={2}
  showMonthAndYearPickers
/>
<DateRangePicker>
  <Label>Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger>
        <DateRangePicker.TriggerIndicator />
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <DateRangePicker.Popover>
    <RangeCalendar aria-label="Choose trip dates" visibleDuration={{months: 2}}>
      <RangeCalendar.Header>
        <RangeCalendar.YearPickerTrigger>
          <RangeCalendar.YearPickerTriggerHeading />
          <RangeCalendar.YearPickerTriggerIndicator />
        </RangeCalendar.YearPickerTrigger>
        <RangeCalendar.NavButton slot="previous" />
        <RangeCalendar.NavButton slot="next" />
      </RangeCalendar.Header>
      <div className="flex gap-4">
        <RangeCalendar.Grid>
          <RangeCalendar.GridHeader>
            {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
          </RangeCalendar.GridHeader>
          <RangeCalendar.GridBody>
            {(date) => <RangeCalendar.Cell date={date} />}
          </RangeCalendar.GridBody>
        </RangeCalendar.Grid>
        <RangeCalendar.Grid offset={{months: 1}}>
          <RangeCalendar.GridHeader>
            {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
          </RangeCalendar.GridHeader>
          <RangeCalendar.GridBody>
            {(date) => <RangeCalendar.Cell date={date} />}
          </RangeCalendar.GridBody>
        </RangeCalendar.Grid>
      </div>
      <RangeCalendar.YearPickerGrid>
        <RangeCalendar.YearPickerGridBody>
          {(year) => <RangeCalendar.YearPickerCell date={year} />}
        </RangeCalendar.YearPickerGridBody>
      </RangeCalendar.YearPickerGrid>
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>

样式相关变化

v2:classNames prop

<DateRangePicker
  classNames={{
    base: "custom-base",
    label: "custom-label",
    selectorButton: "custom-trigger",
    selectorIcon: "custom-icon",
    separator: "custom-separator",
    popoverContent: "custom-popover",
    calendar: "custom-calendar",
  }}
/>

v3:直接使用 className

<DateRangePicker className="custom-base">
  <Label className="custom-label">Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator className="custom-separator" />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger className="custom-trigger">
        <DateRangePicker.TriggerIndicator className="custom-icon" />
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <DateRangePicker.Popover className="custom-popover">
    <RangeCalendar aria-label="Choose trip dates" className="custom-calendar">
      {/* RangeCalendar compound components */}
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>

组件结构(Anatomy)

v3 的 DateRangePicker 结构如下:

DateRangePicker (Root)
  ├── Label
  ├── DateField.Group
  │   ├── DateField.InputContainer
  │   │   ├── DateField.Input slot="start"
  │   │   │   └── DateField.Segment(每个 segment 的渲染 prop)
  │   │   ├── DateRangePicker.RangeSeparator
  │   │   └── DateField.Input slot="end"
  │   │       └── DateField.Segment(每个 segment 的渲染 prop)
  │   └── DateField.Suffix
  │       └── DateRangePicker.Trigger
  │           └── DateRangePicker.TriggerIndicator
  ├── Description (optional)
  ├── FieldError (optional)
  └── DateRangePicker.Popover
      └── RangeCalendar (参见 RangeCalendar 迁移指南)

总结

  1. 组件结构:由单一组件 → 组合 DateRangePickerDateFieldRangeCalendarLabel 等。
  2. 双输入:内置起止输入 → 两个 DateField.Input,分别使用 slot="start"slot="end"
  3. 分隔符:内置分隔 → DateRangePicker.RangeSeparator
  4. 标签label prop → Label 组件。
  5. 描述/错误:prop → DescriptionFieldError 组件。
  6. 日历:内置日历 → 在 DateRangePicker.Popover 内组合 RangeCalendar 及其子组件。
  7. 选择器图标selectorIcon prop → 将 children 传给 DateRangePicker.TriggerIndicator
  8. 年份选择器showMonthAndYearPickers prop → 使用 RangeCalendar.YearPickerTriggerRangeCalendar.YearPickerGrid
  9. 样式类 prop 移除variantcolorsizeradius → 使用 Tailwind CSS,或 DateField.Groupvariant
  10. classNames 移除:在各复合子组件上使用 className

本页目录