---
title: RangeCalendar 范围日历
description: 基于 React Aria RangeCalendar 的可组合日期范围选择器，包含月份网格、导航与年份选择支持。
links:
  rac: RangeCalendar
  source: range-calendar/range-calendar.tsx
  styles: range-calendar.css
  storybook: Components/Date and Time/RangeCalendar
---

## 用法

```tsx
import { RangeCalendar } from '@heroui/react';
```

<ComponentPreview name="range-calendar-basic" />

## 组件结构

```tsx
import {RangeCalendar} from '@heroui/react';

export default () => (
  <RangeCalendar aria-label="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>
)
```

## 示例

### 禁用

<ComponentPreview name="range-calendar-disabled" />

### 年份选择

`RangeCalendar.YearPickerTrigger`, `RangeCalendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern.

<ComponentPreview name="range-calendar-year-picker" />

### 默认值

<ComponentPreview name="range-calendar-default-value" />

### 受控组件

<ComponentPreview name="range-calendar-controlled" />

### 日期范围限制

<ComponentPreview name="range-calendar-min-max-dates" />

### 不可用日期

使用 `isDateUnavailable` 屏蔽周末、节假日或已预订等不可用日期。

<ComponentPreview name="range-calendar-unavailable-dates" />

### 基于锚点的不可用日期

选择范围时，`isDateUnavailable` 会收到第二个参数 `anchorDate`（首个选中日期）。可用它限制哪些结束日期有效（例如起始日期后 7 天内）。

<ComponentPreview name="range-calendar-anchor-unavailable-dates" />

### 月份周数

将 `weeksInMonth` 设为固定值（例如 `6`），在月份切换时保持网格高度稳定。

<ComponentPreview name="range-calendar-weeks-in-month" />

### 周视图

设置 `visibleDuration={{ weeks: n }}` 一次显示一个或多个周。导航按可见周范围前进。显示多周时，使用 `pageBehavior="single"` 每次移动一周。

<ComponentPreview name="range-calendar-week-view" />

### 日视图

设置 `visibleDuration={{ days: n }}` 显示连续日期的滚动窗口。导航按可见日范围前进。显示多天时，使用 `pageBehavior="single"` 每次移动一天。

<ComponentPreview name="range-calendar-day-view" />

### 非连续范围

启用 `allowsNonContiguousRanges` 以允许跨越不可用日期进行选择。

<ComponentPreview name="range-calendar-allows-non-contiguous-ranges" />

### 只读

<ComponentPreview name="range-calendar-read-only" />

### 无效状态

<ComponentPreview name="range-calendar-invalid" />

### 聚焦值

<ComponentPreview name="range-calendar-focused-value" />

### 单元格标记

可自定义 `RangeCalendar.Cell` 的子内容，并使用 `RangeCalendar.CellIndicator` 展示事件等元数据。

<ComponentPreview name="range-calendar-with-indicators" />

### 典型场景

<ComponentPreview name="range-calendar-booking-calendar" />

### 多月份展示

结合 `visibleDuration` 与 `offset` 渲染多个网格，适用于预订与规划场景。

<ComponentPreview name="range-calendar-multiple-months" />

### 国际化日历

默认情况下，RangeCalendar 使用用户 locale 的历法系统显示日期。可用 `I18nProvider` 包裹组件，并设置 [Unicode 历法 locale 扩展](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string) 覆盖。

以下示例展示印度历法系统：

<ComponentPreview name="range-calendar-international-calendar" />

**注意：** 无论显示 locale 如何，`onChange` 事件始终返回与 `value` 或 `defaultValue` 相同历法系统的日期（未提供 value 时为公历）。

## 自定义样式

### Tailwind CSS

<ComponentPreview name="range-calendar-custom-styles" />

### 全局 CSS

若要自定义组件类，可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。

```css
@layer components {
  .range-calendar {
    @apply w-80 rounded-2xl border border-border bg-surface p-3 shadow-sm;
  }

  .range-calendar__heading {
    @apply text-sm font-semibold text-default;
  }

  .range-calendar__cell[data-selected="true"] .range-calendar__cell-button {
    @apply bg-accent text-accent-foreground;
  }
}
```

## 样式参考

HeroUI 遵循 [BEM](https://getbem.com/) 方法论，确保组件变体与状态可复用且易于自定义。

### CSS 类

RangeCalendar 在 `packages/styles/components/range-calendar.css` 与 `packages/styles/components/calendar-year-picker.css` 中使用以下类：

- `.range-calendar` - 根容器。
- `.range-calendar__header` - 含导航按钮与标题的头部行。
- `.range-calendar__heading` - 当前月份标签。
- `.range-calendar__nav-button` - 上一月/下一月导航控件。
- `.range-calendar__grid` - 主体日期网格。
- `.range-calendar__grid-header` - 星期标题行外层。
- `.range-calendar__grid-body` - 日期行外层。
- `.range-calendar__header-cell` - 星期标题单元格。
- `.range-calendar__cell` - 可交互日期单元格外层。
- `.range-calendar__cell-button` - 单元格内的可交互日期按钮。
- `.range-calendar__cell-indicator` - 日期单元格内的圆点指示器。
- `.calendar-year-picker__trigger` - 年份选择器切换按钮。
- `.calendar-year-picker__trigger-heading` - 年份选择触发器内的标题文案。
- `.calendar-year-picker__trigger-indicator` - 年份选择触发器内的指示图标。
- `.calendar-year-picker__year-grid` - 可选年份的覆盖网格。
- `.calendar-year-picker__year-cell` - 单个年份选项。

### 交互状态

RangeCalendar 同时支持伪类与 React Aria 的 data 属性：

- **已选中**：`[data-selected="true"]`
- **范围起点**：`[data-selection-start="true"]`
- **范围终点**：`[data-selection-end="true"]`
- **范围内**：`[data-selection-in-range="true"]`
- **今天**：`[data-today="true"]`
- **不可用**：`[data-unavailable="true"]`
- **跨月**：`[data-outside-month="true"]`
- **悬停**：`:hover` 或 `[data-hovered="true"]`
- **按下**：`:active` 或 `[data-pressed="true"]`
- **焦点可见**：`:focus-visible` 或 `[data-focus-visible="true"]`
- **禁用**：`:disabled` 或 `[data-disabled="true"]`

## API 参考

### RangeCalendar
RangeCalendar 继承 React Aria [RangeCalendar](https://react-spectrum.adobe.com/react-aria/RangeCalendar.html) 的全部 props。

| Prop | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `value` | `RangeValue<DateValue> \| null` | - | 受控的选中范围。 |
| `defaultValue` | `RangeValue<DateValue> \| null` | - | 初始选中范围（非受控）。 |
| `onChange` | `(value: RangeValue<DateValue>) => void` | - | 选中变化时调用。 |
| `focusedValue` | `DateValue` | - | 受控的焦点日期。 |
| `onFocusChange` | `(value: DateValue) => void` | - | 焦点移动到其它日期时调用。 |
| `minValue` | `DateValue` | 历法感知的 `1900-01-01` | 可选的最早日期。 |
| `maxValue` | `DateValue` | 历法感知的 `2099-12-31` | 可选的最晚日期。 |
| `weeksInMonth` | `number` | - | 一个月的周数。该值会覆盖区域设置的默认值。 |
| `isDateUnavailable` | `(date: DateValue, anchorDate: CalendarDate \| null) => boolean` | - | 将日期标记为不可用。`anchorDate` 为当前范围选择中的首个日期。 |
| `firstDayOfWeek` | `'sun' \| 'mon' \| 'tue' \| 'wed' \| 'thu' \| 'fri' \| 'sat'` | - | 覆盖区域设置的一周起始日。 |
| `pageBehavior` | `'visible' \| 'single'` | `'visible'` | 翻页按可见范围或单步前进。 |
| `selectionAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | 初始渲染时按选中项对齐可见范围。 |
| `allowsNonContiguousRanges` | `boolean` | `false` | 允许范围跨越不可用日期。 |
| `isDisabled` | `boolean` | `false` | 禁用交互与选择。 |
| `isReadOnly` | `boolean` | `false` | 内容只读，不可更改选中。 |
| `isInvalid` | `boolean` | `false` | 标记为无效以配合校验样式。 |
| `visibleDuration` | `{months?: number; weeks?: number; days?: number}` | `{months: 1}` | 可见时间范围。使用 `{ months: n }` 为月视图，`{ weeks: n }` 为周视图，`{ days: n }` 为日视图。 |
| `defaultYearPickerOpen` | `boolean` | `false` | 内置年份选择器的初始展开状态。 |
| `isYearPickerOpen` | `boolean` | - | 受控的年份选择器展开状态。 |
| `onYearPickerOpenChange` | `(isOpen: boolean) => void` | - | 年份选择器展开状态变化时调用。 |

### 组合部件

| 组件 | 描述 |
|-----------|-------------|
| `RangeCalendar.Header` | 导航与标题的头部容器。 |
| `RangeCalendar.Heading` | 可见范围的格式化标题。支持 `offset`（多月份布局）与 `format`（月/年/日格式选项）。 |
| `RangeCalendar.NavButton` | 上一页/下一页导航（`slot="previous"` 或 `slot="next"`）。 |
| `RangeCalendar.Grid` | 单个月的日期网格（多月份布局支持 `offset`）。 |
| `RangeCalendar.GridHeader` | 星期标题容器。 |
| `RangeCalendar.GridBody` | 日期单元格主体容器。 |
| `RangeCalendar.HeaderCell` | 星期标签单元格。 |
| `RangeCalendar.Cell` | 单个日期单元格。 |
| `RangeCalendar.CellIndicator` | 用于自定义元数据的可选指示元素。 |
| `RangeCalendar.YearPickerTrigger` | 切换年份选择模式的触发器。 |
| `RangeCalendar.YearPickerTriggerHeading` | 年份选择触发器内的本地化标题内容。 |
| `RangeCalendar.YearPickerTriggerIndicator` | 年份选择触发器内的切换图标。 |
| `RangeCalendar.YearPickerGrid` | 年份选择覆盖网格容器。 |
| `RangeCalendar.YearPickerGridBody` | 年份网格单元格的 body 渲染器。 |
| `RangeCalendar.YearPickerCell` | 单个年份选项单元格。 |

### 年份选择器子组件

年份选择器子组件继承 React Aria [`useCalendarHeading`](https://react-aria.adobe.com/useCalendar#usecalendarheading) 与 [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#usecalendaryearpicker) 的格式化属性。

| 组件 | 属性 | 类型 | 默认值 | 描述 |
|------|------|------|--------|------|
| `RangeCalendar.YearPickerTriggerHeading` | `format` | `DateFormatterOptions` | - | 自定义月/年标题（如 `{month: 'short'}`）。 |
| `RangeCalendar.YearPickerTriggerHeading` | `offset` | `{months?: number}` | - | 相对聚焦日期偏移标题（多月布局）。 |
| `RangeCalendar.YearPickerGrid` | `format` | `DateFormatterOptions` | `{year: 'numeric'}` | 自定义年份单元格标签（纪元、历法系统等）。 |
| `RangeCalendar.YearPickerGrid` | `visibleYears` | `number` | min–max 跨度或 `20` | 滑动窗口中显示的年份数量。当同时设置 `minValue` 与 `maxValue` 时，默认为二者之间的完整范围。 |

### RangeCalendar.Cell Render
当 `RangeCalendar.Cell` 的 `children` 为函数时，可使用 React Aria 的渲染参数：

| Prop | 类型 | 描述 |
|------|------|------|
| `formattedDate` | `string` | 单元格日期的本地化文案。 |
| `isSelected` | `boolean` | 该日期是否已选中。 |
| `isSelectionStart` | `boolean` | 是否为选中范围的起点。 |
| `isSelectionEnd` | `boolean` | 是否为选中范围的终点。 |
| `isUnavailable` | `boolean` | 该日期是否不可用。 |
| `isDisabled` | `boolean` | 单元格是否禁用。 |
| `isOutsideMonth` | `boolean` | 是否属于相邻月份。 |

支持的历法系统及其标识符完整列表见：
- [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations)
- [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars)

### Related packages

- [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — 各日期组件共用的日期类型（`CalendarDate`、`CalendarDateTime`、`ZonedDateTime`）与工具函数
- [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — 为子树覆盖语言环境
- [`useLocale`](https://react-aria.adobe.com/useLocale) — 读取当前语言环境与书写方向

## 相关组件

<RelatedComponents component="range-calendar" />