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 | - | 选中范围变化时调用 |
isOpen | boolean | - | 受控 popover 打开状态 |
defaultOpen | boolean | false | 初始 popover 打开状态 |
onOpenChange | (isOpen: boolean) => void | - | popover 打开状态变化时调用 |
isDisabled | boolean | false | 禁用范围选择与触发器交互 |
isInvalid | boolean | - | 标记字段为无效以显示校验状态 |
minValue | DateValue | - | 最小可选日期 |
maxValue | DateValue | - | 最大可选日期 |
startName | string | - | HTML 表单提交时开始日期的 name |
endName | string | - | HTML 表单提交时结束日期的 name |
children | ReactNode | (values: DateRangePickerRenderProps) => ReactNode | - | 组合内容或 render 函数 |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, DateRangePickerRenderProps> | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
Composition Parts
| Component | Description |
|---|---|
DateRangePicker.Root | 根 date range picker 容器与状态所有者 |
DateRangePicker.Trigger | 触发按钮,通常渲染在 DateField.Suffix 内 |
DateRangePicker.TriggerIndicator | 带默认日历图标的指示器 slot |
DateRangePicker.RangeSeparator | 开始与结束日期输入之间的分隔符部分 |
DateRangePicker.Popover | RangeCalendar 内容的 Popover 包裹层 |
Related packages
@internationalized/date— 所有日期组件使用的日期类型(CalendarDate、CalendarDateTime、ZonedDateTime)与工具I18nProvider— 为子树覆盖 localeuseLocale— 读取当前 locale 与布局方向