RangeCalendar 范围日历
基于 React Aria RangeCalendar 的可组合日期范围选择器,包含月份网格、导航与年份选择支持。
用法
import { RangeCalendar } from '@lenso/ui';此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";
/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { RangeCalendar } from "@lenso/ui";
export function Basic() { return ( <RangeCalendar aria-label="Trip dates" firstDayOfWeek="mon"> <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> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import {RangeCalendar} from '@lenso/ui';
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>)示例
禁用
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function Disabled() { return ( <div {...stylex.props(layout.stack)}> <RangeCalendar isDisabled aria-label="Trip dates"> <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote>Range calendar is disabled</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
年份选择
RangeCalendar.YearPickerTrigger, RangeCalendar.YearPickerGrid, and their body/cell subcomponents provide an integrated year navigation pattern.
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { RangeCalendar } from "@lenso/ui";import { RangeYearHeader, RangeGrid, RangeYearGrid } from "./demo-parts";export function YearPicker() { return ( <RangeCalendar aria-label="Trip dates"> <RangeYearHeader /> <RangeGrid /> <RangeYearGrid /> </RangeCalendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
默认值
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { RangeCalendar } from "@lenso/ui";import { parseDate } from "@internationalized/date";import { RangeHeader, RangeGrid } from "./demo-parts";export function DefaultValue() { return ( <RangeCalendar aria-label="Trip dates" defaultValue={{ end: parseDate("2025-02-12"), start: parseDate("2025-02-03") }} firstDayOfWeek="mon" > <RangeHeader /> <RangeGrid /> </RangeCalendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
受控组件
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Button. */import { Button, ButtonGroup, RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, parseDate, startOfMonth, startOfWeek, today, type DateValue,} from "@internationalized/date";import { useState } from "react";import { useLocale } from "react-aria-components/I18nProvider";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function Controlled() { const [value, setValue] = useState<{ start: DateValue; end: DateValue } | null>(null); const [focusedDate, setFocusedDate] = useState<DateValue>(() => parseDate("2025-12-25")); const { locale } = useLocale(); return ( <div {...stylex.props(layout.stack)}> <ButtonGroup variant="tertiary"> <Button onClick={() => setFocusedDate(today(getLocalTimeZone()))}>This week</Button> <Button onClick={() => setFocusedDate(startOfWeek(today(getLocalTimeZone()).add({ weeks: 1 }), locale)) } > Next week </Button> <Button onClick={() => setFocusedDate(startOfMonth(today(getLocalTimeZone()).add({ months: 1 })))} > Next month </Button> </ButtonGroup> <RangeCalendar aria-label="Trip dates" firstDayOfWeek="mon" focusedValue={focusedDate} value={value} onChange={setValue} onFocusChange={setFocusedDate} > <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote> Selected range: {value ? `${value.start.toString()} -> ${value.end.toString()}` : "(none)"} </CalendarNote> <div {...stylex.props(layout.controls)}> <Button size="sm" variant="secondary" onClick={() => { const start = today(getLocalTimeZone()); setValue({ end: start.add({ days: 6 }), start }); setFocusedDate(start); }} > Set 1 week </Button> <Button size="sm" variant="secondary" onClick={() => { const start = parseDate("2025-12-20"); setValue({ end: parseDate("2025-12-31"), start }); setFocusedDate(start); }} > Set Holidays </Button> <Button size="sm" variant="tertiary" onClick={() => setValue(null)}> Clear </Button> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
日期范围限制
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function MinMaxDates() { const now = today(getLocalTimeZone()); const maxDate = now.add({ months: 3 }); return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Trip dates" maxValue={maxDate} minValue={now}> <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote>Select dates between today and {maxDate.toString()}</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
不可用日期
使用 isDateUnavailable 屏蔽周末、节假日或已预订等不可用日期。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function UnavailableDates() { const now = today(getLocalTimeZone()); const blockedRanges = [ [now.add({ days: 2 }), now.add({ days: 5 })], [now.add({ days: 12 }), now.add({ days: 13 })], ] as const; return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Trip dates" defaultValue={{ end: now.add({ days: 9 }), start: now.add({ days: 6 }) }} firstDayOfWeek="mon" isDateUnavailable={(date) => blockedRanges.some(([start, end]) => date.compare(start) >= 0 && date.compare(end) <= 0) } > <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote>Some days are unavailable</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
基于锚点的不可用日期
选择范围时,isDateUnavailable 会收到第二个参数 anchorDate(首个选中日期)。可用它限制哪些结束日期有效(例如起始日期后 7 天内)。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function AnchorUnavailableDates() { return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Trip dates" isDateUnavailable={(date, anchorDate) => anchorDate != null && Math.abs(date.compare(anchorDate)) > 7 } minValue={today(getLocalTimeZone())} > <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote> After selecting a start date, only dates within 7 days are available </CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
月份周数
将 weeksInMonth 设为固定值(例如 6),在月份切换时保持网格高度稳定。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function WeeksInMonth() { return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Trip dates" weeksInMonth={6}> <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote> Always shows 6 weeks per month to avoid layout shift when navigating </CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
周视图
设置 visibleDuration={{ weeks: n }} 一次显示一个或多个周。导航按可见周范围前进。显示多周时,使用 pageBehavior="single" 每次移动一周。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Select. */import { RangeCalendar } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { DurationSelect, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function WeekView() { const [weeks, setWeeks] = useState(1); return ( <div {...stylex.props(layout.controlsStack)}> <DurationSelect unit="weeks" value={weeks} onChange={setWeeks} /> <RangeCalendar key={weeks} aria-label="Trip dates" visibleDuration={{ weeks }}> <RangeHeader /> <RangeGrid /> </RangeCalendar> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
日视图
设置 visibleDuration={{ days: n }} 显示连续日期的滚动窗口。导航按可见日范围前进。显示多天时,使用 pageBehavior="single" 每次移动一天。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Select. */import { RangeCalendar } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { DurationSelect, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function DayView() { const [days, setDays] = useState(5); return ( <div {...stylex.props(layout.controlsStack)}> <DurationSelect unit="days" value={days} onChange={setDays} /> <RangeCalendar key={days} aria-label="Trip dates" visibleDuration={{ days }}> <RangeHeader /> <RangeGrid /> </RangeCalendar> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
非连续范围
启用 allowsNonContiguousRanges 以允许跨越不可用日期进行选择。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function AllowsNonContiguousRanges() { const now = today(getLocalTimeZone()); const blockedRanges = [ [now.add({ days: 2 }), now.add({ days: 5 })], [now.add({ days: 12 }), now.add({ days: 13 })], ] as const; return ( <div {...stylex.props(layout.stack)}> <RangeCalendar allowsNonContiguousRanges aria-label="Trip dates" defaultValue={{ end: now.add({ days: 9 }), start: now.add({ days: 1 }) }} firstDayOfWeek="mon" isDateUnavailable={(date) => blockedRanges.some(([start, end]) => date.compare(start) >= 0 && date.compare(end) <= 0) } > <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote>Non-contiguous ranges are allowed across unavailable dates</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
只读
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function ReadOnly() { const now = today(getLocalTimeZone()); return ( <div {...stylex.props(layout.stack)}> <RangeCalendar isReadOnly aria-label="Trip dates" defaultValue={{ start: now, end: now.add({ days: 4 }) }} > <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote>Range calendar is read-only</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
无效状态
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, today, type DateValue } from "@internationalized/date";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";const styles = stylex.create({ error: { fontSize: 14, color: "var(--danger)" } });export function Invalid() { const now = today(getLocalTimeZone()); const [value, setValue] = useState<{ start: DateValue; end: DateValue }>({ end: now.add({ days: 14 }), start: now.add({ days: 6 }), }); const isInvalid = value.end.compare(value.start) > 7; return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Trip dates" firstDayOfWeek="mon" isInvalid={isInvalid} value={value} onChange={setValue} > <RangeHeader /> <RangeGrid /> </RangeCalendar> {isInvalid ? ( <p {...stylex.props(styles.error)}>Maximum stay duration is 1 week</p> ) : ( <CalendarNote>Select a stay of up to 7 days</CalendarNote> )} </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
聚焦值
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Button. */import { Button, RangeCalendar } from "@lenso/ui";import { parseDate, type DateValue } from "@internationalized/date";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { CalendarNote, layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";export function FocusedValue() { const [focusedDate, setFocusedDate] = useState<DateValue>(() => parseDate("2025-06-15")); return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Trip dates" firstDayOfWeek="mon" focusedValue={focusedDate} onFocusChange={setFocusedDate} > <RangeHeader /> <RangeGrid /> </RangeCalendar> <CalendarNote>Focused: {focusedDate.toString()}</CalendarNote> <div {...stylex.props(layout.controls)}> <Button size="sm" variant="secondary" onClick={() => setFocusedDate(parseDate("2025-01-01"))} > Go to Jan </Button> <Button size="sm" variant="secondary" onClick={() => setFocusedDate(parseDate("2025-06-15"))} > Go to Jun </Button> <Button size="sm" variant="secondary" onClick={() => setFocusedDate(parseDate("2025-12-25"))} > Go to Christmas </Button> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
单元格标记
可自定义 RangeCalendar.Cell 的子内容,并使用 RangeCalendar.CellIndicator 展示事件等元数据。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, isToday } from "@internationalized/date";import { RangeHeader, RangeGrid } from "./demo-parts";const datesWithEvents = [3, 7, 12, 15, 21, 28];export function WithIndicators() { return ( <RangeCalendar aria-label="Trip dates"> <RangeHeader /> <RangeGrid cell={(date) => ( <RangeCalendar.Cell date={date}> {({ formattedDate }) => ( <> {formattedDate} {(isToday(date, getLocalTimeZone()) || datesWithEvents.includes(date.day)) && ( <RangeCalendar.CellIndicator /> )} </> )} </RangeCalendar.Cell> )} /> </RangeCalendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
典型场景
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Button. */import { Button, RangeCalendar } from "@lenso/ui";import { getLocalTimeZone, isWeekend, today, type DateValue } from "@internationalized/date";import { useState } from "react";import { useLocale } from "react-aria-components/I18nProvider";import * as stylex from "@stylexjs/stylex";import { layout } from "../calendar/demo-parts";import { RangeHeader, RangeGrid } from "./demo-parts";const blockedDates = [5, 6, 12, 13, 14, 20];export function BookingCalendar() { const [selectedRange, setSelectedRange] = useState<{ start: DateValue; end: DateValue } | null>( null, ); const { locale } = useLocale(); return ( <div {...stylex.props(layout.stack)}> <RangeCalendar aria-label="Booking range" isDateUnavailable={(date) => isWeekend(date, locale) || blockedDates.includes(date.day)} minValue={today(getLocalTimeZone())} value={selectedRange} onChange={setSelectedRange} > <RangeHeader /> <RangeGrid cell={(date) => ( <RangeCalendar.Cell date={date}> {({ formattedDate, isUnavailable }) => ( <> {formattedDate} {!isUnavailable && !isWeekend(date, locale) && blockedDates.includes(date.day) && <RangeCalendar.CellIndicator />} </> )} </RangeCalendar.Cell> )} /> </RangeCalendar> <div {...stylex.props(layout.bookingDetails)}> <div {...stylex.props(layout.legend)}> <span {...stylex.props(layout.legendItem)}> <span {...stylex.props(layout.mutedDot)} /> Blocked dates </span> <span {...stylex.props(layout.legendItem)}> <span {...stylex.props(layout.defaultDot)} /> Weekend/Unavailable </span> </div> {selectedRange ? ( <Button size="sm" variant="primary"> Book {selectedRange.start.toString()} {"->"} {selectedRange.end.toString()} </Button> ) : null} </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
多月份展示
结合 visibleDuration 与 offset 渲染多个网格,适用于预订与规划场景。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { containerType: "normal", width: "100%", maxWidth: "none", overflowX: "auto" }, months: { marginInline: "auto", display: "flex", width: "max-content", gap: 32 }, month: { width: 256 }, heading: { flex: "none" }, spacer: { width: 24, height: 24 },});export function MultipleMonths() { return ( <RangeCalendar aria-label="Trip dates" xstyle={styles.root} visibleDuration={{ months: 2 }}> <div {...stylex.props(styles.months)}> <div {...stylex.props(styles.month)}> <RangeCalendar.Header> <RangeCalendar.NavButton slot="previous" /> <RangeCalendar.Heading xstyle={styles.heading} /> <div {...stylex.props(styles.spacer)} /> </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> </div> <div {...stylex.props(styles.month)}> <RangeCalendar.Header> <div {...stylex.props(styles.spacer)} /> <RangeCalendar.Heading xstyle={styles.heading} offset={{ months: 1 }} /> <RangeCalendar.NavButton slot="next" /> </RangeCalendar.Header> <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> </div> </RangeCalendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
国际化日历
默认情况下,RangeCalendar 使用用户 locale 的历法系统显示日期。可用 I18nProvider 包裹组件,并设置 Unicode 历法 locale 扩展 覆盖。
以下示例展示印度历法系统:
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { RangeCalendar } from "@lenso/ui";import { I18nProvider } from "react-aria-components/I18nProvider";import { RangeYearHeader, RangeGrid, RangeYearGrid } from "./demo-parts";export function InternationalCalendar() { return ( <I18nProvider locale="hi-IN-u-ca-indian"> <RangeCalendar aria-label="Trip dates"> <RangeYearHeader /> <RangeGrid /> <RangeYearGrid /> </RangeCalendar> </I18nProvider> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
注意: 无论显示 locale 如何,onChange 事件始终返回与 value 或 defaultValue 相同历法系统的日期(未提供 value 时为公历)。
自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { RangeCalendar } from "@lenso/ui";import { parseDate } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ root: { width: 252, borderRadius: "var(--radius-xl)", borderWidth: 1, borderStyle: "solid", borderColor: "color-mix(in oklab,var(--border) 80%,transparent)", backgroundColor: "var(--surface)", padding: 12, boxShadow: "0 1px 2px 0 rgb(0 0 0 / .05),0 0 0 1px color-mix(in oklab,var(--success) 10%,transparent)", }, heading: { color: "var(--foreground)" }, nav: { borderRadius: "var(--radius-md)", color: "var(--success)", backgroundColor: { default: "transparent", ":hover": "var(--success-soft)", ":is([data-hovered])": "var(--success-soft)", }, }, cell: { borderRadius: { default: "var(--radius-md)", ":is([data-selected])": 0 }, borderTopLeftRadius: { default: "var(--radius-md)", ":is([data-selected])": 0, ":is([data-selection-start])": "var(--radius-md)", }, borderBottomLeftRadius: { default: "var(--radius-md)", ":is([data-selected])": 0, ":is([data-selection-start])": "var(--radius-md)", }, borderTopRightRadius: { default: "var(--radius-md)", ":is([data-selected])": 0, ":is([data-selection-end])": "var(--radius-md)", }, borderBottomRightRadius: { default: "var(--radius-md)", ":is([data-selected])": 0, ":is([data-selection-end])": "var(--radius-md)", }, backgroundColor: { default: null, ":is([data-selected])": "var(--success-soft)", ":is([data-selected][data-outside-month])": "color-mix(in oklab,var(--default) 20%,transparent)", }, }, button: { borderRadius: "var(--radius-md)", backgroundColor: { default: null, ":is([data-hovered]:not([data-selected]) > *)": "var(--default)", ":is([data-today] > *)": "var(--success-soft)", ":is([data-today][data-hovered]:not([data-selected]) > *)": "var(--success-soft-hover)", ":is([data-selection-start] > *,[data-selection-end] > *)": "var(--success)", ":is([data-selection-start][data-pressed] > *,[data-selection-end][data-pressed] > *)": "var(--success-hover)", }, color: { default: null, ":is([data-today] > *)": "var(--success-soft-foreground)", ":is([data-selection-start] > *,[data-selection-end] > *)": "var(--success-foreground)", }, },});export function CustomStyles() { return ( <RangeCalendar aria-label="Hotel stay" xstyle={styles.root} defaultValue={{ end: parseDate("2025-02-14"), start: parseDate("2025-02-08") }} firstDayOfWeek="mon" > <RangeCalendar.Header> <RangeCalendar.Heading xstyle={styles.heading} /> <RangeCalendar.NavButton xstyle={styles.nav} slot="previous" /> <RangeCalendar.NavButton xstyle={styles.nav} slot="next" /> </RangeCalendar.Header> <RangeCalendar.Grid> <RangeCalendar.GridHeader> {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>} </RangeCalendar.GridHeader> <RangeCalendar.GridBody> {(date) => ( <RangeCalendar.Cell date={date} xstyle={styles.cell} buttonXstyle={styles.button} /> )} </RangeCalendar.GridBody> </RangeCalendar.Grid> </RangeCalendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
若要自定义组件类,可使用 @layer components 指令。了解更多。
@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 方法论,确保组件变体与状态可复用且易于自定义。
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 的全部 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 与 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 | 是否属于相邻月份。 |
支持的历法系统及其标识符完整列表见:
Related packages
@internationalized/date— 各日期组件共用的日期类型(CalendarDate、CalendarDateTime、ZonedDateTime)与工具函数I18nProvider— 为子树覆盖语言环境useLocale— 读取当前语言环境与书写方向