Skip to content
Lenso UI

DateRangePicker 日期范围选择器

基于 React Aria DateRangePicker,通过 DateField 与 RangeCalendar 组合的可组合日期范围选择器

用法

import { DateField, DateRangePicker, Label, RangeCalendar } from '@lenso/ui';

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
/** Adapted from the pinned HeroUI v3.2.6 basic.json. Copyright NextUI Inc. Apache-2.0. Local RAC label and StyleX replace source Label and utility classes. */import { DateField, DateRangePicker, RangeCalendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ field: { width: 320 } });export function Basic() {  return (    <DateRangePicker xstyle={styles.field} endName="endDate" startName="startDate">      <DateRangePicker.Label>Trip dates</DateRangePicker.Label>      <DateField.Group fullWidth>        <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.Suffix>          <DateRangePicker.Trigger>            <DateRangePicker.TriggerIndicator />          </DateRangePicker.Trigger>        </DateField.Suffix>      </DateField.Group>      <DateRangePicker.Popover>        <RangeCalendar aria-label="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.YearPickerGrid>            <RangeCalendar.YearPickerGridBody>              {({ year }) => <RangeCalendar.YearPickerCell year={year} />}            </RangeCalendar.YearPickerGridBody>          </RangeCalendar.YearPickerGrid>        </RangeCalendar>      </DateRangePicker.Popover>    </DateRangePicker>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

组件结构

DateRangePicker 采用组合优先 API。显式组合 DateField 与 RangeCalendar 以控制结构与样式。

import {DateField, DateRangePicker, Label, RangeCalendar} from '@lenso/ui';
export default () => (  <DateRangePicker>    <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>)

示例

禁用

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { Disabled } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

受控组件

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { Controlled } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

表单校验

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { WithValidation } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

格式选项

使用 granularity、hourCycle、hideTimeZone、shouldForceLeadingZeros 等 props 控制 DateRangePicker 值的显示方式。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** Adapted from HeroUI v3.2.6 format-options.json. Copyright NextUI Inc. Apache-2.0. */import { DateRangePicker, Separator, TimeField } from "@lenso/ui";import {  DateFormatter,  getLocalTimeZone,  parseDate,  parseZonedDateTime,  type DateValue,} from "@internationalized/date";import { useLocale } from "react-aria-components/I18nProvider";import { useMemo, useState } from "react";import * as stylex from "@stylexjs/stylex";import {  FormatControls,  formatStyles,  type Granularity,  type HourCycle,} from "../date-picker/format-controls";import { PickerCalendar, RangeInput } from "./parts";
export function FormatOptions() {  const [granularity, setGranularity] = useState<Granularity>("minute");  const [hourCycle, setHourCycle] = useState<HourCycle>(12);  const [hideTimeZone, setHideTimeZone] = useState(false);  const [shouldForceLeadingZeros, setShouldForceLeadingZeros] = useState(false);  const { locale } = useLocale();  const dateFormatter = new DateFormatter(locale, {    day: "numeric",    month: "short",    year: "numeric",  });  const defaultValue = useMemo<{ start: DateValue; end: DateValue }>(    () =>      granularity === "day"        ? { start: parseDate("2025-02-03"), end: parseDate("2025-02-10") }        : {            start: parseZonedDateTime(`2026-02-03T08:45:00[${getLocalTimeZone()}]`),            end: parseZonedDateTime(`2026-02-10T18:45:00[${getLocalTimeZone()}]`),          },    [granularity],  );  const timeGranularity = granularity !== "day" ? granularity : undefined;  return (    <div {...stylex.props(formatStyles.rangeStack)}>      <DateRangePicker        key={granularity}        xstyle={formatStyles.rangeField}        defaultValue={defaultValue}        startName="startDate"        endName="endDate"        granularity={granularity}        hourCycle={hourCycle}        hideTimeZone={hideTimeZone}        shouldForceLeadingZeros={shouldForceLeadingZeros}      >        {({ state }) => (          <>            <DateRangePicker.Label>Date range</DateRangePicker.Label>            <RangeInput container />            <DateRangePicker.Popover xstyle={formatStyles.rangePopover}>              <PickerCalendar fill />              {timeGranularity && (                <div {...stylex.props(formatStyles.times)}>                  <div {...stylex.props(formatStyles.timeRow)}>                    <span {...stylex.props(formatStyles.label)}>Start Time</span>                    <TimeField                      aria-label="Start Time"                      granularity={timeGranularity}                      hourCycle={hourCycle}                      hideTimeZone={hideTimeZone}                      name="startTime"                      shouldForceLeadingZeros={shouldForceLeadingZeros}                      value={state.timeRange?.start ?? null}                      onChange={(value) => state.setTime("start", value)}                    >                      <TimeField.Group variant="secondary">                        <TimeField.Input>                          {(segment) => <TimeField.Segment segment={segment} />}                        </TimeField.Input>                      </TimeField.Group>                    </TimeField>                  </div>                  <div {...stylex.props(formatStyles.timeRow)}>                    <span {...stylex.props(formatStyles.label)}>End Time</span>                    <TimeField                      aria-label="End Time"                      granularity={timeGranularity}                      hourCycle={hourCycle}                      hideTimeZone={hideTimeZone}                      name="endTime"                      shouldForceLeadingZeros={shouldForceLeadingZeros}                      value={state.timeRange?.end ?? null}                      onChange={(value) => state.setTime("end", value)}                    >                      <TimeField.Group variant="secondary">                        <TimeField.Input>                          {(segment) => <TimeField.Segment segment={segment} />}                        </TimeField.Input>                      </TimeField.Group>                    </TimeField>                  </div>                </div>              )}              <span {...stylex.props(formatStyles.selected)}>                Selected:{" "}                {state.value?.start && state.value.end                  ? dateFormatter.formatRange(                      state.value.start.toDate(getLocalTimeZone()),                      state.value.end.toDate(getLocalTimeZone()),                    )                  : "No date selected"}              </span>            </DateRangePicker.Popover>          </>        )}      </DateRangePicker>      <Separator xstyle={formatStyles.separator} />      <span {...stylex.props(formatStyles.heading)}>Format Options</span>      <FormatControls        range        {...{          granularity,          setGranularity,          hourCycle,          setHourCycle,          hideTimeZone,          setHideTimeZone,          shouldForceLeadingZeros,          setShouldForceLeadingZeros,        }}      />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

表单示例

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { FormExample } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义指示器

未提供 children 时,DateRangePicker.TriggerIndicator 渲染默认 IconCalendar。传入 children 可替换。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { WithCustomIndicator } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

渲染函数

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** Adapted from HeroUI v3.2.6 render-function.json. Copyright NextUI Inc. Apache-2.0. RAC 1.21 owns the native DOM render contract. */import { DateRangePicker } from "@lenso/ui";import { PickerCalendar, RangeInput, styles } from "./parts";
export function RenderFunction() {  return (    <DateRangePicker      xstyle={styles.field}      startName="startDate"      endName="endDate"      render={(props) => <div data-custom="foo" {...props} />}    >      <DateRangePicker.Label>Trip dates</DateRangePicker.Label>      <RangeInput />      <DateRangePicker.Popover>        <PickerCalendar />      </DateRangePicker.Popover>    </DateRangePicker>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

国际化日历

默认情况下,DateRangePicker 使用用户 locale 的日历系统显示日期。可用 I18nProvider 包裹并设置 Unicode 日历 locale 扩展 覆盖。

以下示例展示印度日历系统:

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { InternationalCalendar } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Note: 无论显示的 locale 如何,onChange 事件始终返回与 value 或 defaultValue 相同日历系统的日期(未提供 value 时为 Gregorian)。

完整支持的日历系统及其标识符列表请参阅:

自定义样式

Tailwind CSS

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";/** HeroUI v3.2.6 adapted example. Copyright NextUI Inc. Apache-2.0. */export { CustomStyles } from "./scenarios";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

全局 CSS

使用 @layer components 自定义 DateRangePicker 基础类。

@layer components {  .date-range-picker {    @apply inline-flex flex-col gap-1;  }
  .date-range-picker__trigger {    @apply inline-flex items-center justify-between;  }
  .date-range-picker__trigger-indicator {    @apply text-muted;  }
  .date-range-picker__range-separator {    @apply px-2 text-default;  }
  .date-range-picker__popover {    @apply min-w-[var(--trigger-width)] p-0;  }}

样式参考

HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。

CSS 类

DateRangePicker 在 packages/styles/components/date-range-picker.css 中使用以下类:

  • .date-range-picker - 根包裹层
  • .date-range-picker__trigger - 打开 popover 的触发器部分
  • .date-range-picker__trigger-indicator - 默认/自定义指示器 slot
  • .date-range-picker__range-separator - 开始与结束日期输入之间的分隔符
  • .date-range-picker__popover - Popover 内容包裹层

交互状态

DateRangePicker 支持 React Aria data 属性与伪状态:

  • Open:触发器上 [data-open="true"]
  • Disabled:触发器上 [data-disabled="true"] 或 [aria-disabled="true"]
  • Focus visible:触发器上 :focus-visible 或 [data-focus-visible="true"]
  • Hover:触发器上 :hover 或 [data-hovered="true"]

API 参考

DateRangePicker

DateRangePicker 继承 React Aria DateRangePicker 的所有 props。

Prop类型默认值描述
value{ start: DateValue; end: DateValue } | null-受控选中日期范围值
defaultValue{ start: DateValue; end: DateValue } | null-非受控模式下的默认选中范围
onChange(value: { start: DateValue; end: DateValue } | null) => void-选中范围变化时调用
isOpenboolean-受控 popover 打开状态
defaultOpenbooleanfalse初始 popover 打开状态
onOpenChange(isOpen: boolean) => void-popover 打开状态变化时调用
isDisabledbooleanfalse禁用范围选择与触发器交互
isInvalidboolean-标记字段为无效以显示校验状态
minValueDateValue-最小可选日期
maxValueDateValue-最大可选日期
startNamestring-HTML 表单提交时开始日期的 name
endNamestring-HTML 表单提交时结束日期的 name
childrenReactNode | (values: DateRangePickerRenderProps) => ReactNode-组合内容或 render 函数
renderDOMRenderFunction<keyof React.JSX.IntrinsicElements, DateRangePickerRenderProps>-使用自定义 render 函数覆盖默认 DOM 元素

Composition Parts

ComponentDescription
DateRangePicker.Root根 date range picker 容器与状态所有者
DateRangePicker.Trigger触发按钮,通常渲染在 DateField.Suffix 内
DateRangePicker.TriggerIndicator带默认日历图标的指示器 slot
DateRangePicker.RangeSeparator开始与结束日期输入之间的分隔符部分
DateRangePicker.PopoverRangeCalendar 内容的 Popover 包裹层
  • @internationalized/date — 所有日期组件使用的日期类型(CalendarDate、CalendarDateTime、ZonedDateTime)与工具
  • I18nProvider — 为子树覆盖 locale
  • useLocale — 读取当前 locale 与布局方向

相关组件